HaskellとParsecでLisp REPL その2(Listも使えるREPL)
LISPとはそもそもLISt Processingの略なので、リストを全く扱えない状態からいち早く脱却したい。
というわけで次はリストの実装。ユーザから(a1 a2 a3)
といった文字列を受けとり、List
型として表現できるようにする。
gistからちゃんとしたgithub repoに変えてみた。リリースタグにリンクできるだけでなく、前回からの変更点も:
このようにリンクできるのがうれしい。
LispVal
型の拡張
Atom
型だけだった代数型データにList
を追加:
data LispVal = Atom String | List [LispVal]
List
はLispVal
のリストを保有する。
show
が使えるようにshowVal
も拡張:
showVal (List contents) = "(" ++ (unwords $ map show contents) ++ ")"
パーサの拡張
トップレベルのパーサparseExpr
がList
を認識するよう拡張:
parseExpr :: Parser LispVal parseExpr = parseAtom <|> parseList
まずはparseAtom
を試し、そのパースが失敗した場合parseList
を試す。
parseList :: Parser LispVal parseList = do { char '('; xs <- sepBy parseExpr (skipMany1 space); char ')'; return $ List xs; }
評価器・REPL・main
eval
やREPL、main
は変更の必要なし。現在リストは変換されず、そのまま値として扱われる。
使用例
これだけの変更で、ユーザがリストを入力するとちゃんとパースされて表示される:
~$ ./v2 >> (a b c) (a b c) >> (a (b c)) (a (b c)) >> (a (b (c (d)))) (a (b (c (d)))) >> (a b 2) No match: "lisp" (line 1, column 6): unexpected "2" expecting space, letter or "(" >> (+ a1 a2) (+ a1 a2) >> (+ 1 2) No match: "lisp" (line 1, column 4): unexpected "1" expecting space, letter or "(" >> quit ~$
再帰的な(a (b (c (d))))
といった構造もちゃんとパースできている。しかしまだ数字はパースされない。次回は数字も受け入れるようにする。