参考URL
#ソース
https://github.com/0zawa/scheme_in_48_hours/blob/master/parsing.hs
Parsec
Haskellのパーサライブラリです。
パーサを作るのにいろいろと便利な機能があるようです。
以降、Parsecの機能を使ってSchemeパーサを実装していきます。
Parsecのインポート
import Text.ParserCombinators.Parsec
とします。
今回はspaces関数の衝突防止のため下記のようにして
spacesを除いてimportします。
import Text.ParserCombinators.Parsec hiding (spaces)
Parser型
パースを行う関数はParser aを返します。
aは任意の型です。
ここではParserの詳細は深く追わず、Parser aは
- String型の入力を受け取る。
- パース結果(a型)を返す。
- パースされなかった文字列もどこかに保持している。
ものだと仮定して進めます。
記号の認識
Parsecに用意されているoneOf関数を使って、指定した記号を認識する関数を作成します。
symbol :: Parser Char
symbol = oneOf "!#$%&|*+-/:<=>?@^_~"
oneOf関数は指定した文字列(ここでは"!#$%&|*+-/:<=>?@^_~")が
入力文字列の先頭に含まれるかどうかパースし、
含まれていればその文字を(Parserに入れて)返します。
こうして作成したパース関数はParsecのparse関数と
組み合わせて使います。
parse関数は
- Parser型を返すパース関数
- パーサーの名前
- 入力文字列(String型)
を引数に取り、Either型を返します。
パース関数にoneOfを指定する場合は
parse (oneOf "#!$") "test" "#123"
のようにします。
上記の結果Either型のRightに"#"が入ったものが戻り値になります。
Schemeで使用可能な記号を認識する
oneOf関数を使って定義したsymbolとparseを使ってSchemeで使用可能な記号をパースする関数を作ります。
readExpr :: String -> String
readExpr input = case parse symbol "lisp" input of
Left err -> "No match: " ++ show err
Right val -> "Found value"
symbolで定義した記号が入力文字列の先頭に含まれていなかった場合、
parse関数はEither型のLeftにエラーを入れて返します。
結果としてreadExpr関数は
入力した文字列の先頭に
- symbolで定義した記号が含まれれば"Found Value"
- 含まれていなければ"No match: (エラー理由)"
の文字列を返す関数になります。
この関数に標準入力で取得した文字列を渡すようにして、
main :: IO ()
main = do args <- getArgs
putStrLn (readExpr (args !! 0))
ビルドして実行すると挙動を確認できます。
% ghc parsing.hs
% ./parsing $
Found value
% ./parsing a
No match: "lisp" (line 1, column 1):
unexpected "a"
ここまでがパーサーの入門的な基本説明です。
このあとLispの型を定義して本格的なパーサーの作成に進んでいきます。