micro Scheme の続きです。今回は S 式を評価する処理を作りましょう。
最初に、関数の定義をデータ型 SExpr に追加します。次のリストを見てください。
リスト : S 式の定義
import qualified Data.Map as M
data SExpr = INT Integer
| REAL Double
| SYM String
| STR String
| CELL SExpr SExpr
| NIL
| PRIM (SExpr -> Scm SExpr)
| SYNT (GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv))
| CLOS SExpr LEnv
-- 評価器の定義
type Scm a = Either String a
-- エラーの送出
scmError :: String -> Scm a
scmError s = Left s
-- 局所変数の環境
type LEnv = [(String, SExpr)]
-- 大域変数の環境
type GEnv = M.Map String SExpr
PRIM はプリミティブ (primitive) の略で、eq?, car, cdr, cons など基本的な関数を表します。引数は評価して、その結果をコンスセル (CELL) に格納して渡します。SYNT はシンタックス形式 (syntax) の略で、quote, if, define など基本的なシンタックス形式を表します。引数は評価せずにそのままシンタックス形式に渡します。このとき、いっしょに環境 (environment) も渡します。CLOS はクロージャ (closure) を表します。Scheme の場合、ラムダ式を評価するとクロージャが生成され、その時点で定義されている局所変数の環境 LEnv がクロージャに保存されます。
LEnv は連想リスト [(String, SExpr)] で表します。今回の micro Scheme は変数の値を書き換えるシンタックス形式 set! を実装しないので、単純な連想リストでも大丈夫です。グローバルな環境 (大域変数) はモジュール Data.Map の Map を使って表します。Scm は S 式を評価する関数の返り値のデータ型を表します。エラーを表すため Either モナドを使います。scmError はエラーを送出する関数で、引数 s を Left に包んで返すだけです。
micro Scheme インタプリタの主役は関数 eval と apply です。eval は S 式と環境を受け取り、渡された環境の下で S 式を評価します。eval の仕事は簡単です。S 式が自己評価フォームであれば、それをそのまま返します。シンボル (変数) であれば環境からその値を求めて返します。リストの場合はちょっと複雑です。先頭の要素を評価して、それが関数値であればそれを呼び出します。プリミティブとクロージャの処理は apply で行います。
プログラムは次のようになります。
リスト : S 式の評価
eval :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
eval genv lenv NIL = return (NIL, genv)
eval genv lenv v@(INT _) = return (v, genv)
eval genv lenv v@(REAL _) = return (v, genv)
eval genv lenv v@(STR _) = return (v, genv)
eval genv lenv (SYM name) =
case lookup name lenv of
Nothing -> case M.lookup name genv of
Nothing -> scmError $ "unbound variable: " ++ name
Just v -> return (v, genv)
Just v -> return (v, genv)
eval genv lenv (CELL func args) = do
(v, genv1) <- eval genv lenv func
case v of
SYNT f -> f genv1 lenv args
_ -> do (vs, genv2) <- evalArguments genv1 lenv args
apply genv2 lenv v vs
eval の引数 genv, lenv が環境で、第 2 引数が評価する S 式です。返り値は評価結果と大域変数の環境です。S 式が NIL, INT, REAL, STR の場合は自己評価フォームなので、S 式をそのまま返します。S 式がシンボルの場合は環境 lenv から局所変数を探します。局所変数が見つからない場合は環境 genv から大域変数を探します。それでも変数が見つからない場合はエラーを返します。
S 式がリストの場合は、その先頭要素 func を eval で評価して値を v にセットします。v が SYNT f の場合は、シンタックス形式 f を呼び出します。このとき、引数として args と genv1, lenv をそのまま渡します。そうでなければ、引数 args を関数 evalArguments で評価して、その結果を apply に渡して関数 v を呼び出します。
引数を評価する関数 evalArguments は簡単です。次のリストを見てください。
リスト : 引数の評価 evalArguments :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv) evalArguments genv lenv NIL = return (NIL, genv) evalArguments genv lenv (CELL expr rest) = do (v, genv1) <- eval genv lenv expr (vs, genv2) <- evalArguments genv1 lenv rest return (CELL v vs, genv2) evalArguments _ _ _ = scmError "invalid function form"
S 式が NIL の場合は NIL を返します。これが再帰呼び出しの停止条件になります。S 式が CELL の場合、CAR の要素 expr を eval で評価します。次に、残りの S 式を evalArguments で評価します。そして、2 つの返り値を CELL に格納して返します。これで引数を評価して、その結果をリストに格納して返すことができます。S 式が NIL, CELL 以外の値はエラーを返します。
次は関数 apply を作りましょう。
リスト : 関数適用
apply :: GEnv -> LEnv -> SExpr -> SExpr -> Scm (SExpr, GEnv)
apply genv lenv func actuals =
case func of
PRIM f -> do v <- f actuals
return (v, genv)
CLOS (CELL parms body) lenv0 -> do
lenv1 <- makeBindings lenv0 parms actuals
evalBody genv lenv1 body
_ -> scmError $ "Not Function: " ++ show func
引数 func が PRIM の場合、実引数リスト actuals にプリミティブ f を適用して値 v を求め、モナドに包んで返します。func が CLOS の場合はちょっと複雑です。actuals と仮引数リスト parms と環境 lenv0 を makeBindings に渡して新しい環境 lenv1 を生成します。クロージャが保持している環境 lenv0 も新しい環境 lenv1 に追加されることに注意してください。
ラムダ式の本体は複数の S 式を格納することができます。これらの S 式の評価は関数 evalBody で行います。evalBody は複数の S 式を順番に eval で評価し、最後に評価した S 式の結果を返します。グローバルの環境は genv と同じですが、ローカルな環境を新しく生成された lenv1 に切り替えます。この環境でラムダ式の本体が評価されます。
次は変数束縛を生成する関数 makeBindings を作ります。
リスト : 変数束縛 makeBindings :: LEnv -> SExpr -> SExpr -> Scm LEnv makeBindings lenv NIL _ = return lenv makeBindings lenv (SYM name) rest = return ((name, rest):lenv) makeBindings lenv (CELL (SYM name) parms) (CELL v args) = do lenv' <- makeBindings lenv parms args return ((name, v):lenv') makeBindings lenv _ NIL = scmError errNEA makeBindings lenv _ _ = scmError "invalid arguments form"
仮引数リストが NIL の場合はクロージャの環境 lenv をそのまま返します。残っている実引数は捨て去ることに注意してください。仮引数リストが SYM name の場合、仮引数リストはドットリストまたはシンボルだけの場合です。残っている実引数リスト rest と name をタプルに格納し、それを lenv に追加します。どちらも CELL の場合は、name と v をタプルに格納し、それを makeBindings の返り値に追加します。それ以外の場合はエラーを返します。
次はラムダ式の本体を評価する関数 evalBody を作ります。
リスト : 本体の評価 evalBody :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv) evalBody genv lenv (CELL expr NIL) = eval genv lenv expr evalBody genv lenv (CELL expr rest) = do (_, genv1) <- eval genv lenv expr evalBody genv1 lenv rest evalBody _ _ _ = scmError "invalid body form"
evalBody は簡単で、先頭から順番にコンスセルの要素 expr を取り出し、それを eval で評価するだけです。途中の返り値は捨てて、最後の S 式の値だけを返します。
次は等値を判定する述語 eq? と equal? を作ります。
リスト : 等値の判定
-- 真偽値
true = SYM "true"
false = SYM "false"
-- 等値の定義
instance Eq SExpr where
INT x == INT y = x == y
REAL x == REAL y = x == y
SYM x == SYM y = x == y
STR x == STR y = x == y
NIL == NIL = True
_ == _ = False
eq' :: SExpr -> Scm SExpr
eq' (CELL x (CELL y _)) =
if x == y then return true else return false
eq' _ = scmError $ "eq : " ++ errNEA
equal' :: SExpr -> Scm SExpr
equal' (CELL x (CELL y _)) =
if iter x y then return true else return false
where iter (CELL a b) (CELL c d) = iter a c && iter b d
iter x y = x == y
equal' _ = scmError $ "equal : " ++ errNEA
真偽値はシンボル true と false で表します。true と false の値は自分自身になるよう初期化します。つまり、自己評価フォームと同じ働きになります。次に、SExpr を型クラス Eq のインスタンスに設定します。INT, REAL, SYM の場合は、演算子 == で比較します。NIL と NIL は True を返します。それ以外の場合は False を返します。
Scheme の述語 eq? (Lisp の eq) はデータの同一性を判定します。つまり、データが配置されたメモリのアドレスが等しい場合に真を返します。この仕様を Haskell で実現するのはちょっと難しいので、データの値を使ってチェックすることにします。これは Scheme の述語 eqv? (Lisp の eql) と同じ動作になります。
関数 eq' は実引数リストから二つの要素 x, y を取り出して演算子 == でチェックするだけです。関数 equal' はリストの要素が eq' を満たせば真を返します。実際の判定は局所関数 iter で行います。どちらの引数も CELL の場合は、CAR 部と CDR 部を iter で比較します。そうでなければ、引数を演算子 == で比較します。
次はシンタックス形式を処理する関数を作りましょう。
リスト : シンタックス形式
-- quote
evalQuote :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalQuote genv lenv (CELL expr _) = return (expr, genv)
evalQuote _ _ _ = scmError "invalid quote form"
-- define
evalDef :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalDef genv lenv (CELL sym@(SYM name) (CELL expr NIL)) = do
(v, genv1) <- eval genv lenv expr
return (sym, M.insert name v genv1)
evalDef _ _ _ = scmError "invalid define form"
-- if
evalIf :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalIf genv lenv (CELL pred (CELL thenForm rest)) = do
(v, genv1) <- eval genv lenv pred
if v /= false
then eval genv1 lenv thenForm
else case rest of
CELL elseForm _ -> eval genv1 lenv elseForm
_ -> return (false, genv1)
evalIf _ _ _ = scmError $ "if : " ++ errNEA
-- lambda
evalLambda :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalLambda genv lenv expr = return (CLOS expr lenv, genv)
quote の処理は関数 evalQuote で行います。引数にはリスト (expr) が渡されるので、リストの先頭要素 expr を返すだけです。define の処理は関数 evalDefine で行います。define は変数 name と値 expr を大域変数に追加します。値 expr を eval で評価して、M.insert で Map に追加します。このとき新しい Map が生成されるので、その Map を返すことに注意してください。
if の処理は関数 evalIf で行います。最初に述語 pred を eval で評価し、それが false でなければ then 節を評価して返します。そうでなければ、else 節を有無をチェックして、else 節があればそれを評価します。else 節が無い場合は false を返します。ラムダ式の処理は関数 evalLambda で行います。これはクロージャを生成する処理です。ローカルな環境 lenv と引数の S 式 expr を CLOS に格納して返します。
次はリストを操作する関数 (primitive) を作りましょう。
リスト : リスト操作の基本関数 car :: SExpr -> Scm SExpr car NIL = scmError $ "car : " ++ errNEA car (CELL (CELL a _) _) = return a car _ = scmError $ "car : " ++ errCELL cdr :: SExpr -> Scm SExpr cdr NIL = scmError $ "cdr : " ++ errNEA cdr (CELL (CELL _ d) _) = return d cdr _ = scmError $ "cdr : " ++ errCELL cons :: SExpr -> Scm SExpr cons (CELL a (CELL b _)) = return (CELL a b) cons _ = scmError $ "cons : " ++ errNEA pair :: SExpr -> Scm SExpr pair NIL = scmError $ "pair? : " ++ errNEA pair (CELL (CELL _ _) _) = return true pair (CELL _ _) = return false
PRIM の場合、引数はコンスセルに格納されています。car は先頭要素がコンスセルであれば CAR 部に格納されているデータを返します。cdr は CDR 部に格納されているデータを返します。引数がコンスセルでない場合はエラーを返します。cons は第 1 引数 a と第 2 引数 b を取り出して、CELL に格納して返します。pair は第 1 引数が CELL ならば true を返します。そうでなければ false を返します。どの関数も引数の個数が足りない場合はエラーを返します。引数の数が多い場合は無視することとします。
最後に REPL (read - eval - print - loop) を作ります。
リスト : REPL (read - eval - print - loop)
initGEnv :: GEnv
initGEnv = M.fromList [("true", true),
("false", false),
("quote", SYNT evalQuote),
("define", SYNT evalDef),
("lambda", SYNT evalLambda),
("if", SYNT evalIf),
("eq?", PRIM eq'),
("equal?", PRIM equal'),
("pair?", PRIM pair),
("car", PRIM car),
("cdr", PRIM cdr),
("cons", PRIM cons)]
-- read-eval-print-loop
repl :: GEnv -> LEnv -> String -> IO ()
repl genv lenv xs = do
putStr "Scm> "
hFlush stdout
case readSExpr xs of
Left (ParseErr xs' mes) -> do putStrLn mes
repl genv lenv $ dropWhile (/= '\n') xs'
Right (expr, xs') -> do case eval genv lenv expr of
Left mes -> do putStrLn mes
repl genv lenv xs'
Right (v, genv1) -> do print v
repl genv1 lenv xs'
main :: IO ()
main = do
xs <- hGetContents stdin
repl initGEnv [] xs
repl はプロンプト Scm> を出力してから readSExpr を呼び出して S 式を読み込みます。エラーが返ってきた場合はエラーメッセージを出力し、改行までの入力を読み捨ててから repl を再帰呼び出しします。S 式 expr を読み込んだら、eval を呼び出して expr を評価します。エラーの場合はエラーメッセージを出力して repl を再帰呼び出しします。正常に評価できた場合は、その値 v を表示して repl を再帰呼び出しします。このとき、大域変数の環境は eval が返した genv1 を渡すことに注意してください。
main は hGetContents で標準入力から文字列を読み込み、それを repl に渡します。大域変数の環境は initGEnv で局所関数の環境は空リストになります。
それでは実行してみましょう。
Scm> () () Scm> 1 1 Scm> 1.2345 1.2345 Scm> "hello, world" "hello, world" Scm> 'a a Scm> (quote a) a Scm> '(1 2 3 4 5) (1 2 3 4 5) Scm> (cons 1 ()) (1) Scm> (cons 1 2) (1 . 2) Scm> (car '(a b c d)) a Scm> (cdr '(a b c d)) (b c d) Scm> (cons 'a '(b c d)) (a b c d) Scm> (eq? 1 1) true Scm> (eq? 1 2) false Scm> (eq? 1.234 1.234) true Scm> (eq? 1.234 1.2) false Scm> (eq? 'a 'a) true Scm> (eq? 'a 'b) false Scm> (eq? "abc" "abc") true Scm> (eq? "abc" "ABC") false Scm> (equal? '(1 2 3 4) '(1 2 3 4)) true Scm> (equal? '(1 2 3 4) '(1 2 3 4.0)) false Scm> (equal? '((1 2) (3 4)) '((1 2) (3 4))) true Scm> (equal? '((1 2) (3 4)) '((1 2) (3 . 4))) false Scm> (pair? '(a b c)) true Scm> (pair? 'a) false
quote, if, car, cdr, cons, eq?, equal?, pair? は正常に動作していますね。次は lambda と define を試してみます。
Scm> (define a 'b) a Scm> a b Scm> (lambda (x) x) <closure> Scm> ((lambda (x) x) 1) 1 Scm> ((lambda (x) x) 'a) a Scm> (define list (lambda x x)) list Scm> (list 1 2 3 4 5) (1 2 3 4 5) Scm> (list 'a 'b 'c 'd 'e) (a b c d e)
define で変数 a を定義します。シンボル a を入力すると、その値を求めることができます。lambda はクロージャを生成します。リストの先頭要素にラムダ式を指定すると、それを呼び出すことができます。そして、define と lambda を使って関数を定義することができます。
次は、レキシカルスコープとクロージャが正常に動作するか試してみましょう。
Scm> (define x 'a) x Scm> x a Scm> (define foo (lambda () x)) foo Scm> (foo) a Scm> (define bar (lambda (x) (foo))) bar Scm> (bar 'b) a
まず大域変数 x を a に初期化します。次に、関数 foo を定義します。foo の引数はないので、x は大域変数を参照します。したがって、foo を評価すると返り値は a になります。次に、関数 bar から foo を呼び出します。bar の仮引数は x ですが、(bar 'b) を評価すると a が返ってきます。確かにレキシカルスコープになっています。
今度はクロージャの動作を確かめます。
Scm> (define baz (lambda (x) (lambda (y) (cons x y)))) baz Scm> (define baz-a (baz 'a)) baz-a Scm> (baz-a 'b) (a . b) Scm> (baz-a 'c) (a . c)
関数 baz はクロージャを生成して返します。このとき、baz の引数 x の値がクロージャに保存されます。(baz 'a) の返り値を baz-a にセットすると、baz-a は a と baz-a の引数を組にしたものを返す関数となります。したがって、(baz-a 'b) は (a . b) を、(baz-a 'c) は (a . c) を返します。クロージャも正常に動作していますね。
define で定義する関数は再帰呼び出しが可能です。簡単なリスト操作関数を再帰定義で作ってみました。プログラムリストと実行結果を示します。
リスト : リストの結合
(define append
(lambda (xs ys)
(if (pair? xs)
(cons (car xs) (append (cdr xs) ys))
ys)))
Scm> (define append (lambda (xs ys) (if (pair? xs) (cons (car xs) (append (cdr xs) ys)) ys))) append Scm> (append '(a b c d) '(e f g h)) (a b c d e f g h) Scm> (append '((a b) (c d)) '((e f) (g h))) ((a b) (c d) (e f) (g h))
リスト : リストの反転
(define reverse
(lambda (xs)
(if (pair? xs)
(append (reverse (cdr xs)) (list (car xs)))
())))
Scm> (define reverse (lambda (xs) (if (pair? xs) (append (reverse (cdr xs)) (list (car xs))) ()))) reverse Scm> (reverse '(a b c d e f)) (f e d c b a) Scm> (reverse '((a b) (c d) (e f))) ((e f) (c d) (a b))
もちろん、高階関数も定義することができます。
リスト : マッピング
(define map
(lambda (f xs)
(if (pair? xs)
(cons (f (car xs)) (map f (cdr xs)))
())))
Scm> (define map (lambda (f xs) (if (pair? xs) (cons (f (car xs)) (map f (cdr xs))) ()))) map Scm> (map (lambda (x) (cons x x)) '(1 2 3 4 5)) ((1 . 1) (2 . 2) (3 . 3) (4 . 4) (5 . 5)) Scm> (map car '((a 1) (b 2) (c 3) (d 4) (e 5))) (a b c d e) Scm> (map cdr '((a 1) (b 2) (c 3) (d 4) (e 5))) ((1) (2) (3) (4) (5))
リスト : フィルター
(define filter
(lambda (f xs)
(if (pair? xs)
(if (f (car xs))
(cons (car xs) (filter f (cdr xs)))
(filter f (cdr xs)))
())))
Scm> (define filter (lambda (f xs) (if (pair? xs) (if (f (car xs)) (cons (car xs) (filter f (cdr xs))) (filter f (cdr xs))) ()))) filter Scm> (filter (lambda (x) (eq? x 'a)) '(a b c a b c a b c)) (a a a)
リスト : 畳み込み
(define fold-left
(lambda (f a xs)
(if (pair? xs)
(fold-left f (f a (car xs)) (cdr xs))
a)))
(define fold-right
(lambda (f a xs)
(if (pair? xs)
(f (car xs) (fold-rightf a (cdr xs)))
a)))
Scm> (define fold-left (lambda (f a xs) (if (pair? xs) (fold-left f (f a (car xs)) (cdr xs)) a))) fold-left Scm> (fold-left (lambda (a x) (cons x a)) () '(1 2 3 4 5)) (5 4 3 2 1) Scm> (define fold-right (lambda (f a xs) (if (pair? xs) (f (car xs) (fold-right f a (cdr xs))) a))) fold-right Scm> (fold-right cons () '(1 2 3 4 5)) (1 2 3 4 5)
今回はここまでです。次回は変数の値を書き換えるシンタックス形式 set! と数の算術演算、比較演算を追加します。
--
-- mscheme1.hs : microScheme インタプリタ
--
-- Copyright (C) 2013-2021 Makoto Hiroi
--
import Data.Char
import qualified Data.Map as M
import System.IO
-- S 式の定義
data SExpr = INT Integer
| REAL Double
| SYM String
| STR String
| CELL SExpr SExpr
| NIL
| PRIM (SExpr -> Scm SExpr)
| SYNT (GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv))
| CLOS SExpr LEnv
-- 等値の定義
instance Eq SExpr where
INT x == INT y = x == y
REAL x == REAL y = x == y
SYM x == SYM y = x == y
STR x == STR y = x == y
NIL == NIL = True
_ == _ = False
-- パーサエラーの定義
data ParseErr = ParseErr String String deriving Show
-- パーサの定義
type Parser a = Either ParseErr a
-- エラーの送出
parseError :: String -> String -> Parser a
parseError x s = Left (ParseErr x s)
-- 評価器の定義
type Scm a = Either String a
-- エラーの送出
scmError :: String -> Scm a
scmError s = Left s
-- ローカル環境の定義
type LEnv = [(String, SExpr)]
-- グローバルな環境
type GEnv = M.Map String SExpr
-- 真偽値
true = SYM "true"
false = SYM "false"
-- Primitive の定義
errNEA = "Not enough arguments"
errCELL = "Illegal argument, List required"
errINT = "Illegal argument, Integer required"
errNUM = "Illegal argument, Number required"
-- リスト操作
car :: SExpr -> Scm SExpr
car NIL = scmError $ "car : " ++ errNEA
car (CELL (CELL a _) _) = return a
car _ = scmError $ "car : " ++ errCELL
cdr :: SExpr -> Scm SExpr
cdr NIL = scmError $ "cdr : " ++ errNEA
cdr (CELL (CELL _ d) _) = return d
cdr _ = scmError $ "cdr : " ++ errCELL
cons :: SExpr -> Scm SExpr
cons (CELL a (CELL b _)) = return (CELL a b)
cons _ = scmError $ "cons : " ++ errNEA
-- 述語
eq' :: SExpr -> Scm SExpr
eq' (CELL x (CELL y _)) =
if x == y then return true else return false
eq' _ = scmError $ "eq : " ++ errNEA
equal' :: SExpr -> Scm SExpr
equal' (CELL x (CELL y _)) =
if iter x y then return true else return false
where iter (CELL a b) (CELL c d) = iter a c && iter b d
iter x y = x == y
equal' _ = scmError $ "equal : " ++ errNEA
pair :: SExpr -> Scm SExpr
pair NIL = scmError $ "pair? : " ++ errNEA
pair (CELL (CELL _ _) _) = return true
pair (CELL _ _) = return false
--
-- S 式の表示
--
showCell :: SExpr -> String
showCell (CELL a d) =
show a ++ case d of
NIL -> ""
PRIM _ -> "<primitive>"
CLOS _ _ -> "<closure>"
SYNT _ -> "<syntax>"
INT x -> " . " ++ show x
REAL x -> " . " ++ show x
SYM x -> " . " ++ x
STR x -> " . " ++ show x
_ -> " " ++ showCell d
showCell xs = show xs
instance Show SExpr where
show (INT x) = show x
show (REAL x) = show x
show (SYM x) = x
show (STR x) = show x
show NIL = "()"
show (SYNT _) = "<syntax>"
show (PRIM _) = "<primitive>"
show (CLOS _ _) = "<closure>"
show xs = "(" ++ showCell xs ++ ")"
--
-- S 式の読み込み
--
isAlpha' :: Char -> Bool
isAlpha' x = elem x "!$%&*+-/:<=>?@^_~"
isIdent0 :: Char -> Bool
isIdent0 x = isAlpha x || isAlpha' x
isIdent1 :: Char -> Bool
isIdent1 x = isAlphaNum x || isAlpha' x
isREAL :: Char -> Bool
isREAL x = elem x ".eE"
quote = SYM "quote"
isNUM :: String -> Bool
isNUM (x:_) = isDigit x
isNUM _ = False
getNumber :: String -> Parser (SExpr, String)
getNumber xs =
let (s, ys) = span isDigit xs
in if not (null ys) && isREAL (head ys)
then case reads xs of
[] -> parseError "" "" -- ありえないエラー
[(y', ys')] -> return (REAL y', ys')
else return (INT (read s), ys)
readSExpr :: String -> Parser (SExpr, String)
readSExpr [] = parseError "" "EOF"
readSExpr (x:xs)
| isSpace x = readSExpr xs
| isDigit x = getNumber (x:xs)
| isIdent0 x = if x == '+' && isNUM xs
then getNumber xs
else if x == '-' && isNUM xs
then do (y, ys) <- getNumber xs
case y of
INT x -> return (INT (- x), ys)
REAL x -> return (REAL (- x), ys)
else let (name, ys) = span isIdent1 (x:xs)
in return (SYM name, ys)
| otherwise =
case x of
'(' -> readCell 0 xs
';' -> readSExpr $ dropWhile (/= '\n') xs
'"' -> case reads (x:xs) of
[] -> parseError "" ""
[(y, ys)] -> y `seq` ys `seq` return (STR y, ys)
'\'' -> readSExpr xs >>=
\(e, ys) -> e `seq` ys `seq` return (CELL quote (CELL e NIL), ys)
_ -> parseError xs ("unexpected token: " ++ show x)
readCell :: Int -> String -> Parser (SExpr, String)
readCell _ [] = parseError "" "EOF"
readCell n (x:xs)
| isSpace x = readCell n xs
| otherwise =
case x of
')' -> xs `seq` return (NIL, xs)
'.' -> if n == 0
then parseError xs "invalid dotted list"
else do (e, ys) <- readSExpr xs
case dropWhile isSpace ys of
')':zs -> return (e, zs)
_ -> parseError xs "invalid dotted list"
'(' -> do (a, ys) <- readCell 0 xs
(d, zs) <- readCell 1 ys
return (CELL a d, zs)
_ -> do (a, ys) <- readSExpr (x:xs)
(d, zs) <- readCell 1 ys
return (CELL a d, zs)
--
-- S 式の評価
--
eval :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
eval genv lenv NIL = return (NIL, genv)
eval genv lenv v@(INT _) = return (v, genv)
eval genv lenv v@(REAL _) = return (v, genv)
eval genv lenv v@(STR _) = return (v, genv)
eval genv lenv (SYM name) =
case lookup name lenv of
Nothing -> case M.lookup name genv of
Nothing -> scmError $ "unbound variable: " ++ name
Just v -> return (v, genv)
Just v -> return (v, genv)
eval genv lenv (CELL func args) = do
(v, genv1) <- eval genv lenv func
case v of
SYNT f -> f genv1 lenv args
_ -> do (vs, genv2) <- evalArguments genv1 lenv args
apply genv2 lenv v vs
-- 引数の評価
evalArguments :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalArguments genv lenv NIL = return (NIL, genv)
evalArguments genv lenv (CELL expr rest) = do
(v, genv1) <- eval genv lenv expr
(vs, genv2) <- evalArguments genv1 lenv rest
return (CELL v vs, genv2)
evalArguments _ _ _ = scmError "invalid function form"
-- 変数束縛
makeBindings :: LEnv -> SExpr -> SExpr -> Scm LEnv
makeBindings lenv NIL _ = return lenv
makeBindings lenv (SYM name) rest = return ((name, rest):lenv)
makeBindings lenv (CELL (SYM name) parms) (CELL v args) = do
lenv' <- makeBindings lenv parms args
return ((name, v):lenv')
makeBindings lenv _ NIL = scmError errNEA
makeBindings lenv _ _ = scmError "invalid arguments form"
-- 関数適用
apply :: GEnv -> LEnv -> SExpr -> SExpr -> Scm (SExpr, GEnv)
apply genv lenv func actuals =
case func of
PRIM f -> do v <- f actuals
return (v, genv)
CLOS (CELL parms body) lenv0 -> do
lenv1 <- makeBindings lenv0 parms actuals
evalBody genv lenv1 body
_ -> scmError $ "Not Function: " ++ show func
-- 本体の評価
evalBody :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalBody genv lenv (CELL expr NIL) = eval genv lenv expr
evalBody genv lenv (CELL expr rest) = do
(_, genv1) <- eval genv lenv expr
evalBody genv1 lenv rest
evalBody _ _ _ = scmError "invalid body form"
--
-- シンタックス形式
--
-- quote
evalQuote :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalQuote genv lenv (CELL expr _) = return (expr, genv)
evalQuote _ _ _ = scmError "invalid quote form"
-- define
evalDef :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalDef genv lenv (CELL sym@(SYM name) (CELL expr NIL)) = do
(v, genv1) <- eval genv lenv expr
return (sym, M.insert name v genv1)
evalDef _ _ _ = scmError "invalid define form"
-- if
evalIf :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalIf genv lenv (CELL pred (CELL thenForm rest)) = do
(v, genv1) <- eval genv lenv pred
if v /= false
then eval genv1 lenv thenForm
else case rest of
CELL elseForm _ -> eval genv1 lenv elseForm
_ -> return (false, genv1)
evalIf _ _ _ = scmError $ "if : " ++ errNEA
-- lambda
evalLambda :: GEnv -> LEnv -> SExpr -> Scm (SExpr, GEnv)
evalLambda genv lenv expr = return (CLOS expr lenv, genv)
--
-- 大域変数の初期化
--
initGEnv :: GEnv
initGEnv = M.fromList [("true", true),
("false", false),
("quote", SYNT evalQuote),
("define", SYNT evalDef),
("lambda", SYNT evalLambda),
("if", SYNT evalIf),
("eq?", PRIM eq'),
("equal?", PRIM equal'),
("pair?", PRIM pair),
("car", PRIM car),
("cdr", PRIM cdr),
("cons", PRIM cons)]
--
-- read-eval-print-loop
--
repl :: GEnv -> LEnv -> String -> IO ()
repl genv lenv xs = do
putStr "Scm> "
hFlush stdout
case readSExpr xs of
Left (ParseErr xs' mes) -> do putStrLn mes
repl genv lenv $ dropWhile (/= '\n') xs'
Right (expr, xs') -> do case eval genv lenv expr of
Left mes -> do putStrLn mes
repl genv lenv xs'
Right (v, genv1) -> do print v
repl genv1 lenv xs'
main :: IO ()
main = do
xs <- hGetContents stdin
repl initGEnv [] xs