「Write Yourself a Scheme in 48 Hours」 を写経してみる(8) : REPLを構築する。

REPLとは Read-eval-print loopの略。読み込んで評価をしてそれを表示・・・その繰り返しをする対話型評価環境を作ります。

import IO hiding (try) -- Parsec の try 関数と重複するので.

flushStr は引数の文字列を表示する。

flushStr :: String -> IO ()
flushStr str = putStr str >> hFlush stdout

> flushStr "test\n"
-- > test

readPrompt は引数の文字列を表示して入力された文字列を返す。

readPrompt :: String -> IO String
readPrompt prompt = flushStr prompt >> getLine

> readPrompt "lisp >>>"
lisp >>>input
-- > "input"

evalString は引数の文字列をパース、評価。例外を処理した文字列を返す。
extractValue では Right を取り去る。

evalString :: String -> IO String
evalString expr = return $ extractValue $ trapError (liftM show $ readExpr expr >>= eval) 
> evalString "(+ 1 2 3)"
-- > "6"

evalAndPrint は evalString を表示。

evalAndPrint :: String -> IO ()
evalAndPrint expr =  evalString expr >>= putStrLn

> evalAndPrint "(+ 1 2 3)"
-- > 6

until_ は再帰でループします。
until_ の _ は値を返さない繰り返し処理のときに使われる命名規則です。( sequence_ 等)
pred : ループの終了条件
prompt : 読み込みをするための関数
action : 評価実行をする関数

until_ :: Monad m => (a -> Bool) -> m a -> (a -> m ()) -> m ()
until_ pred prompt action = do 
  result <- prompt
  if pred result 
     then return ()
     else action result >> until_ pred prompt action

>  until_ (== "quit") (readPrompt "Lisp>>> ") evalAndPrint
Lisp>>> (car '(a b))
a
Lisp>>> quit

REPL の定義

runRepl :: IO ()
runRepl = until_ (== "quit") (readPrompt "Lisp>>> ") evalAndPrint

引数なしで起動されてときは REPL モード。
引数がひとつのときはその引数を評価。
それ以外はエラーを表示します。

main :: IO ()
main = do args <- getArgs
          case length args of
              0 -> runRepl
              1 -> evalAndPrint $ args !! 0
              otherwise -> putStrLn "Program takes only 0 or 1 argument"
$ ghc -package parsec -fglasgow-exts -o lisp listing7.hs

listing7.hs:180:0:
    Warning: Pattern match(es) are overlapped
             In the definition of `cdr': cdr [DottedList [xs] x] = ...

$ rlwrap ./lisp
Lisp>>> 123
123
Lisp>>> (+ 1 2 3)
6
Lisp>>> quit

$ rlwrap ./lisp (+ 1 2)
3

rlwrap はキーボードからコマンドへの入力に GNU readline ライブラリを使えるようにするラッパーです。コマンドごとに個別の入力履歴を記録し、これまでに入力した単語やユーザが指定したファイルに含まれる単語を使って Tab 補完をすることができます。(SourceForge.JP > ソフトウェアを探す > rlwrap > 概要)