Arantium Maestum

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

HaskellとParsecでLisp REPL その2(Listも使えるREPL)

LISPとはそもそもLISt Processingの略なので、リストを全く扱えない状態からいち早く脱却したい。

というわけで次はリストの実装。ユーザから(a1 a2 a3)といった文字列を受けとり、List型として表現できるようにする。

gistからちゃんとしたgithub repoに変えてみた。リリースタグにリンクできるだけでなく、前回からの変更点も:

github.com

このようにリンクできるのがうれしい。

LispVal型の拡張

Atom型だけだった代数型データにListを追加:

data LispVal = Atom String
             | List [LispVal]

ListLispValのリストを保有する。

showが使えるようにshowValも拡張:

showVal (List contents) = "(" ++ (unwords $ map show contents) ++ ")"

パーサの拡張

トップレベルのパーサparseExprListを認識するよう拡張:

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))))といった構造もちゃんとパースできている。しかしまだ数字はパースされない。次回は数字も受け入れるようにする。