Arantium Maestum

プログラミング、囲碁、読書の話題

HaskellとParsecでLisp REPL その5(Bool型の追加)

今回の変更点

github.com

ブール値の追加。データ型とパーサの拡張のみ。

データ型

data LispVal = Atom String
             | List [LispVal]
             | Number Integer
             | Bool Bool

ちゃんとshowできるようにしておく:

showVal (Bool True) = "#t"
showVal (Bool False) = "#f"

パーサ拡張

Bool型はユーザ入力が"#t"ならBool True、"#f"ならBool Falseである。このパーサはちょっとだけややこしい。

というのは、今までの三つの種類のデータ型はそもそも先頭の文字をパースした時点でどの要素になるかがわかったのだが、Bool型の場合Atom型とかぶってしまっている。

まず、Atom型のパーサのほうがBool型よりも許容度が高いので("#t"はAtom型としてもパース可能)、パースを試す順番はBoolのほうが先でなければいけない:

parseExpr = parseBool
        <|> parseAtom
        <|> parseList
        <|> parseNumber

これで"#t"がAtom型として認識されてしまう問題は回避できる。

しかし、まだ気をつけないといけない。

今までのような実装だとたとえば"#define"のような文字列をパースする時に、parseBoolが"#"は処理し"d"を読んだ時点で諦めるので、"#"文字が次のparseAtomに渡されない問題が発生する。

parseBool :: Parser LispVal
parseBool = (try parseTrue) <|> (try parseFalse)

parseBooltryを使うことによって解決する。tryはバックトラックしてくれるので、parseTrueを試してみて失敗したら元の文字まで戻ってparseFalseに回し、それも失敗したらまた元の文字まで戻って次のパーサに回す。

parseTrue/parseFalseの定義:

parseTrue :: Parser LispVal
parseTrue = string "#t" >> return (Bool True)

parseFalse :: Parser LispVal
parseFalse = string "#f" >> return (Bool False)

ParsecのParserはモナドなので>>とかreturnがでてくる。"#t"や"#f"のパースが成功したら、マッチした文字列は使わず直接Bool True/Bool FalseをParserモナドに入れて返す。

評価器・REPL・Main

例によって変更なし

次回

ブール値が表現できるようになったので、ブール値を返す比較演算子を定義する。