今回は micro Scheme に「マクロ (macro)」という機能を追加しましょう。define で定義できる関数は引数を評価するタイプで、シンタックス形式のように引数を評価しない関数を定義することはできません。Scheme (Lisp) でプログラミングする場合、ほとんどの処理は define で定義する関数で作ることができますが、シンタックス形式のように引数を評価しない関数を定義した方が便利な場合もあります。このようなとき、役に立つのがマクロです。
Scheme の場合、マクロは二種類あります。一つは仕様書 (R5RS など) で定義されているマクロで、これを「健全なマクロ」といいます。これに対し、昔から Lisp で使われているマクロを「伝統的なマクロ」といいます。多くの Scheme 処理系では、どちらのマクロも使えるようになっています。健全なマクロは作るのが大変なので、今回は伝統的なマクロを追加することにします。
まずは最初に伝統的なマクロについて簡単に説明します。
Lisp ではマクロを関数のように定義します。伝統的なマクロを定義するには define-macro を使います。
(define-macro マクロ名 (lambda (<仮引数> ...) S式 ...))
define-macro の構文は define と同じです。define-macro で定義されたマクロは、次のような特徴を持ちます。
この 2 番目の機能が Lisp におけるマクロの特徴です。これを図に示すと、次のようになります。
[S式] ─ 評価 -> [新しいS式] ─ 評価 -> [マクロの返り値] (マクロ展開) 図 : マクロの動作
S 式を評価することで新しい S 式を組み立てます。この部分がマクロ展開に相当します。そして、その S 式を評価した値がマクロの返り値となります。S 式を組み立てるということは、自動的にプログラムを作ることと同じですね。これは、リストにプログラムとデータの 2 つの役割を持たせている Lisp だからこそ可能なことなのです。
まず、マクロと関数の違いを理解するために、数を 2 乗する処理をマクロと関数で作ってみましょう。関数は簡単ですね。
リスト : 数を 2 乗する関数 (define square (lambda (x) (* x x)))
マクロは次のように定義します。
リスト : 数を 2 乗するマクロ (define-macro m-square (lambda (x) (list '* x x)))
マクロ名は m-square としました。それでは、引数に (+ 1 2) を与えて m-square を評価してみます。
(m-square (+ 1 2)) 仮引数 x に (+ 1 2) がセット(評価されないことに注意) マクロの本体 (list '* x x) を評価する => (* (+ 1 2) (+ 1 2)) (S式が組み立てられる) => 9 (S式を評価した結果) 図 : マクロの実行
関数であれば引数 (+ 1 2) が評価されて、その返り値である 3 が square に渡されますね。マクロの場合、引数は評価されないので、仮引数 x には S 式である (+ 1 2) がそのままセットされます。
次に、マクロ本体を評価します。マクロを使いこなすポイントですが、まず評価したい S 式を組み立てることを考えます。最初の評価で S 式を組み立て、それを評価することで目的の処理を実現するのがマクロなのです。
この場合、引数の 2 乗する (* x x) という S 式を作ればいいわけです。list は引数を要素とする新しいリストを返す関数でしたね。この場合、シンボル * と x の値である (+ 1 2) が要素となったリストが返されます。
これでマクロ展開が終了しました。マクロの仮引数は、マクロ展開されるときだけ有効です。マクロ展開されたS式を評価するときは、それらの値は破棄されます。あとは、この S 式を評価して 9 という値が結果となります。
ところで、マクロを定義するとき、S 式を組み立てるため list をたくさん使うことになり少々面倒です。実は、「バッククォート ( ` )」という機能を使うと、S 式を簡単に組み立てることができます。
バッククォートはクォート ( ' ) と同様に引数の評価を行いません。ですが、バッククォートの中でコンマ ( , ) で始まる S 式があると、その S 式を評価した値で置き換えられます。簡単な例を示しましょう。
gosh> (define var 'pen) var gosh> var pen gosh> `(this is a ,var) (this is a pen)
変数 var にはシンボル pen がセットされています。次の S 式の中で ,var は var を評価した値、つまり pen に置き換わるのです。また、S 式の評価結果がリストの場合は、コンマアットマーク (,@) を使うことができます。,@ を使うと、リストをはずした値と置き換わります。,@ を使う場合、値がリストでなければエラーになります。次の例を見てください。
gosh> (define var '(pen)) var gosh> var (pen) gosh> `(this is a ,var) (this is a (pen)) gosh> `(this is a ,@var) (this is a pen)
今度は変数 var にリスト (pen) がセットされました。次の S 式の中で ,varは (pen) に置き換わります。そして、その次の S 式の中では、,@var は pen に置き換わるのです。それから、コンマやコンマアットマークはバッククォートの中でしか使うことができません。ほかの S 式の中で評価した場合はエラーとなります。ご注意ください。
昔の Lisp 処理系では、引数を評価するタイプを EXPR 型や SUBR 型、引数を評価しないタイプを NEXPR 型や FSUBR 型と呼び、ユーザーが NEXPR 型の関数を定義することができました。Scheme や Common Lisp の場合、ユーザーが定義できるのは関数とマクロだけです。シンタックス形式の関数を定義する場合はマクロを使うことになります。
マクロを実行する場合、必ずマクロ展開が行われるため、通常の関数よりも実行時間は遅くなります。だったら、NEXPR 型の関数を定義できるようにした方が実行速度の点で有利なはずです。ところが、Scheme や Common Lisp では必要最低限のシンタックス形式を定義し、よく使われる制御構造はマクロで定義されています。これではインタプリタでの動作が遅くなります。
では、なぜ実行速度が遅くなるのにマクロを使っているのでしょう。それは、Common Lisp や多くの Scheme 処理系がコンパイラの使用を前提としているからです。たとえば、Gauche はプログラムをバイトコードにコンパイルしてから実行します。Common Lisp では、CLISP がバイトコードに、SBCL がネイティブコードにプログラムをコンパイルします。
プログラムでマクロを呼び出している場所は、コンパイル時にマクロ展開されるため、コンパイル済みのコードにはマクロ呼び出しがなくなってしまうのです。つまり、コンパイル済みのコードは、マクロを呼び出す処理とマクロ展開の処理がなくなることにより、確実にインタプリタよりも高速に実行することができるのです。逆にいえば、コンパイラを使わないとマクロを効果的に使うことはできません。ご注意くださいませ。
それではプログラムを作りましょう。まず最初に、マクロを表すデータ型を定義します。
リスト : S 式の定義 type ScmFunc = Env -> SExpr -> Scm SExpr data SExpr = INT !Integer | REAL !Double | SYM String | STR String | CELL SExpr SExpr | NIL | PRIM ScmFunc | SYNT ScmFunc | CLOS SExpr LEnv | MACR SExpr
ScmFunc は PRIM と SYNT の関数の型を表します。今回から PRIM の関数にも環境 Env を渡すように修正します。マクロはシンタックス形式 define-macro で定義します。マクロはラムダ式で表します。マクロのデータ型は MACR SExpr で、引数の SExpr にはラムダ式の評価結果であるクロージャをセットします。この処理を関数 evalDefM で行います。
リスト : マクロの定義 evalDefM :: Env -> SExpr -> Scm SExpr evalDefM env (CELL sym@(SYM name) (CELL expr NIL)) = do v <- eval env expr lift $ H.insert (fst env) name (MACR v) return sym evalDefM _ _ = throwError "invalid define-macro form"
処理内容は evalDef とほぼ同じですが、eval の返り値 v (クロージャ) を MACR に格納し、それと変数名 name をタプルにまとめて大域変数の環境に追加します。
次はバッククオートで使用する記号を処理するため、S 式を読み込む関数 readSExpr を修正します。バッククオートで使う記号 (` , ,@) は省略形で、次に示す S 式に変換されます。
`(...) : (quasiquote (...)) ,expr : (unquote expr) ,@expr : (unquote-splicing expr)
この変換処理を関数 readSExpr で行います。プログラムは次のようになります。
リスト : S 式の読み込み quote = SYM "quote" quasiquote = SYM "quasiquote" unquote = SYM "unquote" unquoteSplicing = SYM "unquote-splicing" readSExpr :: String -> Parser (SExpr, String) readSExpr [] = throwError $ strMsg "EOF" readSExpr (x:xs) | isSpace x = readSExpr xs ・・・ 省略 ・・・ | 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) '`' -> readSExpr xs >>= \(e, ys) -> e `seq` ys `seq` return (CELL quasiquote (CELL e NIL), ys) ',' -> if not (null xs) && head xs == '@' then readSExpr (tail xs) >>= \(e, ys) -> e `seq` ys `seq` return (CELL unquoteSplicing (CELL e NIL), ys) else readSExpr xs >>= \(e, ys) -> e `seq` ys `seq` return (CELL unquote (CELL e NIL), ys) _ -> parseError xs ("unexpected token: " ++ show x)
考え方は quote の処理と同じです。x が記号 ` であれば、readSExpr xs で次の S 式を読み込み、CELL quasiquote (CELL e NIL) を返します。記号 , の場合は、次の記号をチェックして、@ であれば CELL unquoteSplicing (CELL e NIL) を返し、そうでなければ CELL unquote (CELL e NIL) を返します。
次は eval にマクロを評価する処理を追加します。
リスト : eval の修正 eval :: Env -> SExpr -> Scm SExpr eval env NIL = return NIL ・・・ 省略 ・・・ eval env (CELL func args) = do v <- eval env func case v of SYNT f -> f env args MACR f -> do expr <- apply env f args eval env expr _ -> do vs <- evalArguments env args apply env v vs
引数 func の評価結果がマクロ MACR f の場合、f にはクロージャが格納されているので、それを apply で評価します。このとき、引数 args は評価しないでそのまま渡すことに注意してください。apply の返り値は S 式なので、それを eval で再度評価します。
もし、S 式の中でマクロが使われていたら、そこでまたマクロ展開が行われ、組み立てられた S 式が評価されます。これでマクロの再帰呼び出しも処理することができます。実行速度は遅くなりますが、たったこれだけの処理でマクロの強力な機能を実現することができます。
次は高階関数 apply を作りましょう。Scheme の関数 apply は次のように使います。
apply func args-list
apply は最初の引数 func を関数として呼び出します。このとき、第 2 引数のリストの要素が func の引数として渡されます。apply は func の評価結果を返します。簡単な使用例を示しましょう。
gosh> (apply + '(1 2 3)) 6 gosh> (apply car '((1 2 3))) 1
また apply は次のように、func と args-list の間に引数を与えることができます。
gosh> (apply + 4 5 6 '(1 2 3)) 21
apply はリストに格納されている要素を引数として関数に渡す場合に便利です。
プログラムは次のようになります。
リスト : 高階関数 apply apply' :: ScmFunc apply' _ (CELL _ NIL) = throwError $ "apply : " ++ errNEA apply' env (CELL func args) = do apply env func $ iter args where iter (CELL NIL NIL) = NIL iter (CELL xs@(CELL _ _) NIL) = xs iter (CELL x xs) = CELL x (iter xs) apply' _ _ = throwError $ "apply : " ++ errNEA
局所関数 iter で、引数を順番に最後尾の引数 (リスト) に追加します。あとは、iter の返り値を apply に渡して関数 func を呼び出すだけです。
次はエラーメッセージを表示する micro Scheme の関数 error を作ります。
リスト : エラーの表示 error' :: ScmFunc error' _ (CELL (STR x) NIL) = throwError $ "ERROR: " ++ x error' _ (CELL (STR x) (CELL y _)) = throwError $ "ERROR: " ++ x ++ " " ++ show y error' _ (CELL x _) = throwError $ "ERROR: " ++ show x error' _ _ = throwError "ERROR: "
関数 error は第 1 引数が文字列で、第 2 引数が S 式です。第 2 引数は省略することができます。第 1 引数が文字列で無い場合はそれをそのまま表示します。引数が無い場合は "ERROR: " だけを表示します。あとは throwError でエラーを返すだけです。
最後にファイルに書かれたプログラムを読み込む micro Scheme の関数 load を作りましょう。ファイル名は文字列で指定します。プログラムは次のようになります。
リスト : ファイルの読み込み (load) load :: ScmFunc load env (CELL (STR filename) _) = do xs <- lift $ readFile filename r <- lift $ iter xs if r then return true else return false where iter :: String -> IO Bool iter xs = case readSExpr xs of Left (ParseErr xs' mes) -> if mes == "EOF" then return True else do print mes return False Right (expr, xs') -> do result <- runExceptT $ eval env expr case result of Left mes -> do print mes return False Right _ -> iter xs' load _ _ = throwError "invalid load form"
処理内容は repl とほぼ同じです。局所関数 iter でファイルの終了 "EOF" を検出するまでプログラムを読み込み、それを eval で評価するだけです。途中でエラーが返された場合はエラーメッセージを表示して false を返します。プログラムを正常にロードできた場合は true を返します。
それでは実行してみましょう。まず最初に簡単なマクロを試してみます。
Scm> (define-macro m-square (lambda (x) (list '* x x))) m-square Scm> (m-square 10) 100 Scm> (define-macro add (lambda (xs) (cons '+ xs))) add Scm> (add (1 2 3 4 5)) 15
正常に動作していますね。次は apply を試してみましょう。
Scm> (apply + '(1 2 3 4 5)) 15 Scm> (apply + 10 11 12 '(1 2 3 4 5)) 48 Scm> (define length (lambda (xs) (if (pair? xs) (+ 1 (length (cdr xs))) 0))) length Scm> (length '(1 2 3 4 5)) 5 Scm> (define avg (lambda (xs) (/ (apply + xs) (length xs)))) avg Scm> (avg '(1.0 2.0 3.0 4.5 5.7 6.9)) 3.85
関数 length はリストの要素の個数を返します。length と apply を使うと、リストの要素の平均値を求める関数 avg は簡単に作成することができます。
今回はここまでです。次回はバッククォートの処理を実装して、micro Scheme 用の簡単なライブラリを作ってみましょう。
-- -- mscheme.hs : microScheme インタプリタ -- -- Copyright (C) 2013-2021 Makoto Hiroi -- import Data.Char import Data.IORef import qualified Data.HashTable.IO as H import Control.Monad.Except import Control.Monad.Trans import Control.Monad.IO.Class import System.IO -- S 式の定義 type ScmFunc = Env -> SExpr -> Scm SExpr data SExpr = INT !Integer | REAL !Double | SYM String | STR String | CELL SExpr SExpr | NIL | PRIM ScmFunc | SYNT ScmFunc | CLOS SExpr LEnv | MACR SExpr -- 等値の定義 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 = ExceptT String IO a -- ローカル環境の定義 type LEnv = [(String, IORef SExpr)] pushLEnv :: String -> SExpr -> LEnv -> IO LEnv pushLEnv s v env = do a <- v `seq` newIORef v return ((s, a):env) lookupLEnv :: String -> LEnv -> IO (Maybe SExpr) lookupLEnv s env = case lookup s env of Nothing -> return Nothing Just v -> do a <- readIORef v return (Just a) updateLEnv :: String -> SExpr -> LEnv -> IO (LEnv) updateLEnv s v env = case lookup s env of Nothing -> pushLEnv s v env Just a -> do writeIORef a v return env -- グローバルな環境 type HashTable k v = H.BasicHashTable k v type GEnv = HashTable String SExpr -- 両方の環境を保持する type Env = (GEnv, LEnv) -- 真偽値 true = SYM "true" false = SYM "false" -- Primitive の定義 errNUM = "Illegal argument, Number required" errINT = "Illegal argument, Integer required" errNEA = "Not enough arguments" errCELL = "Illegal argument, List required" errZERO = "Divide by zero" -- リスト操作 car, cdr, cons, pair :: ScmFunc car _ NIL = throwError $ "car : " ++ errNEA car _ (CELL (CELL a _) _) = return a car _ _ = throwError $ "car : " ++ errCELL cdr _ NIL = throwError $ "cdr : " ++ errNEA cdr _ (CELL (CELL _ d) _) = return d cdr _ _ = throwError $ "cdr : " ++ errCELL cons _ (CELL a (CELL b _)) = return (CELL a b) cons _ _ = throwError $ "cons : " ++ errNEA pair _ NIL = throwError $ "pair? : " ++ errNEA pair _ (CELL (CELL _ _) _) = return true pair _ _ = return false -- 畳み込み foldCell :: (SExpr -> SExpr -> Scm SExpr) -> SExpr -> SExpr -> Scm SExpr foldCell _ a NIL = return a foldCell f a (CELL x rest) = do v <- f a x foldCell f v rest foldCell _ _ _ = throwError $ errCELL -- 四則演算 adds, subs, muls, divs, mod' :: ScmFunc add, sub, mul, div' :: SExpr -> SExpr -> Scm SExpr add (INT x) (INT y) = return (INT (x + y)) add (INT x) (REAL y) = return (REAL (fromIntegral x + y)) add (REAL x) (INT y) = return (REAL (x + fromIntegral y)) add (REAL x) (REAL y) = return (REAL (x + y)) add _ _ = throwError $ "+ : " ++ errNUM adds _ xs = foldCell add (INT 0) xs sub (INT x) (INT y) = return (INT (x - y)) sub (INT x) (REAL y) = return (REAL (fromIntegral x - y)) sub (REAL x) (INT y) = return (REAL (x - fromIntegral y)) sub (REAL x) (REAL y) = return (REAL (x - y)) sub _ _ = throwError $ "- : " ++ errNUM subs _ NIL = throwError $ "- : " ++ errNEA subs _ (CELL (INT a) NIL) = return (INT (-a)) subs _ (CELL (REAL a) NIL) = return (REAL (-a)) subs _ (CELL a rest) = foldCell sub a rest mul (INT x) (INT y) = return (INT (x * y)) mul (INT x) (REAL y) = return (REAL (fromIntegral x * y)) mul (REAL x) (INT y) = return (REAL (x * fromIntegral y)) mul (REAL x) (REAL y) = return (REAL (x * y)) mul _ _ = throwError $ "- : " ++ errNUM muls _ xs = foldCell mul (INT 1) xs div' _ (INT 0) = throwError errZERO div' _ (REAL 0) = throwError errZERO div' (INT x) (INT y) = return (INT (x `div` y)) div' (INT x) (REAL y) = return (REAL (fromIntegral x / y)) div' (REAL x) (INT y) = return (REAL (x / fromIntegral y)) div' (REAL x) (REAL y) = return (REAL (x / y)) div' _ _ = throwError $ "- : " ++ errNUM divs _ NIL = throwError $ "/ : " ++ errNEA divs _ (CELL a NIL) = div' (INT 1) a divs _ (CELL a rest) = foldCell div' a rest mod' _ NIL = throwError $ "mod : " ++ errNEA mod' _ (CELL _ NIL) = throwError $ "mod : " ++ errNEA mod' _ (CELL _ (CELL (INT 0) _)) = throwError errZERO mod' _ (CELL _ (CELL (REAL 0) _)) = throwError errZERO mod' _ (CELL (INT x) (CELL (INT y) _)) = return (INT (mod x y)) mod' _ _ = throwError $ "mod : " ++ errINT -- 等値の判定 eq', equal' :: ScmFunc eq' _ (CELL x (CELL y _)) = if x == y then return true else return false eq' _ _ = throwError $ "eq : " ++ errNEA 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' _ _ = throwError $ "equal : " ++ errNEA -- 数値の比較演算子 compareNum :: SExpr -> SExpr -> Scm Ordering compareNum (INT x) (INT y) = return $ compare x y compareNum (INT x) (REAL y) = return $ compare (fromIntegral x) y compareNum (REAL x) (INT y) = return $ compare x (fromIntegral y) compareNum (REAL x) (REAL y) = return $ compare x y compareNum _ _ = throwError errNUM compareNums :: (Ordering -> Bool) -> SExpr -> Scm SExpr compareNums _ NIL = throwError errNEA compareNums _ (CELL _ NIL) = throwError errNEA compareNums p (CELL x (CELL y NIL)) = do r <- compareNum x y if p r then return true else return false compareNums p (CELL x ys@(CELL y _)) = do r <- compareNum x y if p r then compareNums p ys else return false compareNums _ _ = throwError "invalid function form" eqNum, ltNum, gtNum, ltEq, gtEq :: ScmFunc eqNum _ = compareNums (== EQ) ltNum _ = compareNums (== LT) gtNum _ = compareNums (== GT) ltEq _ = compareNums (<= EQ) gtEq _ = compareNums (>= EQ) -- apply apply' :: ScmFunc apply' _ (CELL _ NIL) = throwError $ "apply : " ++ errNEA apply' env (CELL func args) = do apply env func $ iter args where iter (CELL NIL NIL) = NIL iter (CELL xs@(CELL _ _) NIL) = xs iter (CELL x xs) = CELL x (iter xs) apply' _ _ = throwError $ "apply : " ++ errNEA -- エラー error' :: ScmFunc error' _ (CELL (STR x) NIL) = throwError $ "ERROR: " ++ x error' _ (CELL (STR x) (CELL y _)) = throwError $ "ERROR: " ++ x ++ " " ++ show y error' _ (CELL x _) = throwError $ "ERROR: " ++ show x error' _ _ = throwError "ERROR: " -- load load :: ScmFunc load env (CELL (STR filename) _) = do xs <- lift $ readFile filename r <- lift $ iter xs if r then return true else return false where iter :: String -> IO Bool iter xs = case readSExpr xs of Left (ParseErr xs' mes) -> if mes == "EOF" then return True else do print mes return False Right (expr, xs') -> do result <- runExceptT $ eval env expr case result of Left mes -> do print mes return False Right _ -> iter xs' load _ _ = throwError "invalid load form" -- -- S 式の表示 -- showCell :: SExpr -> String showCell (CELL a d) = show a ++ case d of NIL -> "" PRIM _ -> "<primitive>" CLOS _ _ -> "<closure>" SYNT _ -> "<syntax>" MACR _ -> "<macro>" 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 (MACR _) = "<macro>" 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" quasiquote = SYM "quasiquote" unquote = SYM "unquote" unquoteSplicing = SYM "unquote-splicing" 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) '`' -> readSExpr xs >>= \(e, ys) -> e `seq` ys `seq` return (CELL quasiquote (CELL e NIL), ys) ',' -> if not (null xs) && head xs == '@' then readSExpr (tail xs) >>= \(e, ys) -> e `seq` ys `seq` return (CELL unquoteSplicing (CELL e NIL), ys) else readSExpr xs >>= \(e, ys) -> e `seq` ys `seq` return (CELL unquote (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 :: Env -> SExpr -> Scm SExpr eval env NIL = return NIL eval env v@(INT _) = return v eval env v@(REAL _) = return v eval env v@(STR _) = return v eval env (SYM name) = do a <- liftIO $ lookupLEnv name $ snd env case a of Nothing -> do b <- liftIO $ H.lookup (fst env) name case b of Nothing -> throwError $ "unbound variable: " ++ name Just v -> return v Just v -> return v eval env (CELL func args) = do v <- eval env func case v of SYNT f -> f env args MACR f -> do expr <- apply env f args eval env expr _ -> do vs <- evalArguments env args apply env v vs -- 引数の評価 evalArguments :: Env -> SExpr -> Scm SExpr evalArguments env NIL = return NIL evalArguments env (CELL expr rest) = do v <- eval env expr vs <- evalArguments env rest return (CELL v vs) evalArguments _ _ = throwError "invalid function form" -- 変数束縛 makeBindings :: LEnv -> SExpr -> SExpr -> Scm LEnv makeBindings lenv NIL _ = return lenv makeBindings lenv (SYM name) rest = lift $ pushLEnv name rest lenv makeBindings lenv (CELL (SYM name) parms) (CELL v args) = do lenv' <- makeBindings lenv parms args lift (pushLEnv name v lenv') makeBindings lenv _ NIL = throwError errNEA makeBindings lenv _ _ = throwError "invalid arguments form" -- 関数適用 apply :: Env -> SExpr -> SExpr -> Scm SExpr apply env func actuals = case func of PRIM f -> f env actuals CLOS (CELL parms body) lenv0 -> do lenv1 <- makeBindings lenv0 parms actuals evalBody (fst env, lenv1) body _ -> throwError $ "Not Function: " ++ show func -- 本体の評価 evalBody :: Env -> SExpr -> Scm SExpr evalBody env (CELL expr NIL) = eval env expr evalBody env (CELL expr rest) = do eval env expr evalBody env rest evalBody _ _ = throwError "invalid body form" -- -- シンタックス形式 -- -- quote evalQuote :: Env -> SExpr -> Scm SExpr evalQuote env (CELL expr _) = return expr evalQuote _ _ = throwError "invalid quote form" -- define evalDef :: Env -> SExpr -> Scm SExpr evalDef env (CELL sym@(SYM name) (CELL expr NIL)) = do v <- eval env expr lift $ H.insert (fst env) name v return sym evalDef _ _ = throwError "invalid define form" -- define-macro evalDefM :: Env -> SExpr -> Scm SExpr evalDefM env (CELL sym@(SYM name) (CELL expr NIL)) = do v <- eval env expr lift $ H.insert (fst env) name (MACR v) return sym evalDefM _ _ = throwError "invalid define-macro form" -- if evalIf :: Env -> SExpr -> Scm SExpr evalIf env (CELL pred (CELL thenForm rest)) = do v <- eval env pred if v /= false then eval env thenForm else case rest of CELL elseForm _ -> eval env elseForm _ -> return false evalIf _ _ = throwError $ "if : " ++ errNEA -- lambda evalLambda :: Env -> SExpr -> Scm SExpr evalLambda env expr = return (CLOS expr (snd env)) -- set! evalSet :: Env -> SExpr -> Scm SExpr evalSet env (CELL (SYM name) (CELL expr _)) = do v <- eval env expr a <- lift $ lookupLEnv name (snd env) case a of Nothing -> do b <- lift $ H.lookup (fst env) name case b of Nothing -> throwError $ "unbound variable: " ++ name Just _ -> do lift $ H.insert (fst env) name v return v Just _ -> do lift $ updateLEnv name v (snd env) return v evalSet _ _ = throwError "invalid set! form" -- -- 大域変数の初期化 -- initGEnv :: [(String, SExpr)] initGEnv = [("true", true), ("false", false), ("quote", SYNT evalQuote), ("define", SYNT evalDef), ("lambda", SYNT evalLambda), ("if", SYNT evalIf), ("set!", SYNT evalSet), ("define-macro", SYNT evalDefM), ("eq?", PRIM eq'), ("equal?", PRIM equal'), ("pair?", PRIM pair), ("+", PRIM adds), ("-", PRIM subs), ("*", PRIM muls), ("/", PRIM divs), ("mod", PRIM mod'), ("=", PRIM eqNum), ("<", PRIM ltNum), (">", PRIM gtNum), ("<=", PRIM ltEq), (">=", PRIM gtEq), ("car", PRIM car), ("cdr", PRIM cdr), ("cons", PRIM cons), ("load", PRIM load), ("apply", PRIM apply'), ("error", PRIM error')] -- read-eval-print-loop repl :: Env -> String -> IO () repl env xs = do putStr "Scm> " hFlush stdout case readSExpr xs of Left (ParseErr xs' mes) -> do putStrLn mes repl env $ dropWhile (/= '\n') xs' Right (expr, xs') -> do result <- runExceptT $ eval env expr case result of Left mes -> putStrLn mes Right v -> print v repl env xs' main :: IO () main = do xs <- hGetContents stdin ht <- H.fromList initGEnv :: IO (GEnv) repl (ht, []) xs