LoginSignup
2
2

More than 5 years have passed since last update.

48時間でSchemeを書こう/REPLの作成

Posted at

参考URL

48時間でSchemeを書こう/REPLの作成

ソース

REPL

REPLとはRead-Eval-Print-Loopの略です。
その名の通り、読んで、評価して、表示、を繰り返すプログラムです。

まず、「読む」部分を作っていきます。

Read

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

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

flushStrは文字列をputStrで表示した後、
バッファに残っている文字列を標準出力に書き出します。

readPromptはpromptをflushStrで表示させた後、
getLineで標準入力から一行読み込みます。

promptには"Lisp>>> "のような入力を促すための
文字列が渡されます。

Eval

次に評価部分を作ります。

evalString :: String -> IO String
evalString expr = return $ extractValue $ trapError (liftM show $ readExpr expr >>= eval)

evalString関数は複雑なので少しずつみていきます。

readExpr expr >>= eval

まず引数で渡された文字列をreadExprで
ThrowsError LispValに変換します。
続いてevalで評価して、結果のThrowsError LispValを得ます。

liftM show $ readExpr expr >>= eval

evalの結果に対しliftM showを適用し、
ThrowsError Stringを得ます。
liftMはThrowsErrorの中身に対して、与えられた
関数を適用します。

trapError (liftM show $ readExpr expr >>= eval)

trapErrorはエラーを文字列に変換するために適用します。
この結果必ずRight値を持ったThrowsErrror Stringになります。

extractValue $ trapError (liftM show $ readExpr expr >>= eval)

extractValueはThrowsErrorのRight値を取り出す関数です。
trapErrorの結果のThrowsError Stringから
Stringを取り出します。

evalString :: String -> IO String
return $ extractValue $ trapError (liftM show $ readExpr expr >>= eval)

最後に結果の文字列をreturnでIOに包んで返却します。

Print

次にこの結果を表示する関数を定義します。

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

Loop

最後に、これまで作った処理をループさせてREPLを完成させます。

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_関数はまず、promptで渡されたアクションを実行して、
結果をactionに格納します。

promptには先ほど定義したreadPrompt関数が渡されます。

readPrompt "Lisp>>> " 

actionにはreadPromptの実行結果が格納されるので、
標準入力から一行読み込んだ文字列が格納されることになります。

続いて、結果をpredに渡して真偽値を判定します。
predには

== "quit"

が渡されます。

読み込んだ文字列が"quit"だった際はreturn ()で
REPLを抜けます。
それ以外の場合は結果をactionに渡して、その後
until_を再帰的に呼び出します。

actionには

evalAndPrint

が渡されます。
evalAndPrintに標準入力から取得した文字列が渡され、
その文字列が評価されて、最後に結果が表示されます。

until_を呼び出す関数も定義します。

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

最後にrunRepl関数をmainから呼び出すようにして完成です。

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"

プログラムに引数が渡された場合は、その文字列を評価、表示し、
渡されなかった場合は、REPLを実行します。
引数が2つ以上渡されていた場合はエラーにして終了させます。

2
2
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
2
2