1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

48時間でSchemeを書こう/構文解析3

Last updated at Posted at 2015-06-30

参考URL

48時間でSchemeを書こう/構文解析

#ソース
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をパースできる関数ができあがりました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?