参考URL
#ソース
https://github.com/0zawa/scheme_in_48_hours/blob/master/parsing.hs
#LispVal
Lispで使われる型を定義します。
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool
このそれぞれの型についてパーサ関数を定義していきます。
#String
parseString :: Parser LispVal
parseString = do char '"'
x <- many (noneOf "\"")
char '"'
return $ String x
char関数は入力文字列の先頭が引数の文字と等しければ、
入力値からその文字を消費して返します。
(残りの文字列は次の処理に渡されます。)
noneOf関数は入力の先頭が引数の文字と異なれば
その文字を消費して返します。
many関数と組み合わせて
many (noneOf "\"")
とすると、ダブルクオートでない連続した文字列を
パースする関数になります。
パースした結果の文字列はxに格納されます。
結果として、parseString関数は
「"」から「"」までの文字列をStringコンストラクタで
LispValにして、それをParserに包んで返す関数になります。
Atom
parseAtom :: Parser LispVal
parseAtom = do first <- letter <|> symbol
rest <- many (letter <|> digit <|> symbol)
let atom = first:rest
return $ case atom of
"#t" -> Bool True
"#f" -> Bool False
_ -> Atom atom
ここで使われている<|>演算子は1つ目のパーサーが失敗したら、
2つ目のパーサを実行する演算子です。
1つ目が成功した場合は、2つ目は実行されません。
> parse (letter <|> digit) "test" "123"
Right '1'
> parse (letter <|> digit) "test" "a"
Right 'a'
letter関数は大文字と小文字の英字、
digit関数は数字をそれぞれパースする関数です。
結果としてparseAtom関数は
- 最初の一文字が英字かsymbolで指定した記号
- 二文字目以降は英字か数字かsymbolで指定した記号の繰り返し
の場合に成功し、パースした結果を結びつけて「#t/#f」なら
BoolコンスクタでLispVal型に、それ以外ならAtomコンストラクタで
LispValに格納して、Parserに包んで返します。
Number
parseNumberはStringやAtomと比べるとやや複雑です。
parseNumber :: Parser LispVal
parseNumber = liftM (Number . read) $ many1 digit
many1 digitは数字の1回以上の繰り返しで、
(Number . read)は文字列を数値に変えてNumberコンストラクタで
LipValに変換します。
ただ、many1 digitの戻り値はStringではなくParser Stringなので
そのままでは(Number . read)に適用できません。
そこでLiftMを使うと、(Number . read)をParserの中身に適用できます。
(LiftMは第一引数の関数を第二引数のPaserの中身に適用する関数です。)
結果、parseNumberは数値をNumberコンストラクタで
LispValに変換して、Parserで包んで返す関数になります。
#パーサーの組み合わせ
これまでに定義したString,Atom,Numberのパーサーを<|>を使って
組み合わせます。
parseExpr :: Parser LispVal
parseExpr =
parseAtom
<|> parseString
<|> parseNumber
これでAtom,String,Numberをパースできる関数ができあがりました。