phorth---(その1.2)Evaluator

評価器(Evaluator)

ASTが作れるようになったので,評価器の番です.harrorth--では省いた「新しいオペレータの定義」があるので,その部分を作らないといけません.

新しいオペレータは「名前」,「内容」そしてその間の「対応関係」であり,また定義したらそれを覚えておかないといけません.HarrorthそのままでData.Mapを使います.こういうのは連想配列で扱うのが楽なので,Haskellでの連想配列Data.Mapの出番です.

Liblary Documentによると``Since many function names (but not the type name) clash with Prelude names, this module is usually imported qualified,''とのことなので,qualified-asで使うことにします.

import qualified Data.Map as Map

type Stack = [Literal]
type Dict  = Map.Map Name AST

harrorth--ではInterpのフィールドはstack::[Literal]だけでしたが,これをsack::Stackと書き換えます.またユーザ定義のオペレータを記録するための辞書のフィールドdict::Dictを追加します.

data Interp = Interp{ stack::Stack,
                      dict ::Dict } deriving Show -- interpreter

そしてharrorth--から,インタープリタの更新関数interpとASTの各要素を処理するprocExpをもってきておきます.ただし,今回はオリジナルのharrorthと同じくIOモナドにしてdo記法にします.

interp:: Interp -> AST -> IO Interp
interp i (x:xs) = do i' <- procExp i x
                     interp i' xs
interp i [] = return i

procExp:: Interp -> Exp -> IO Interp
procExp i@Interp {stack=xs}   (Push lit)     = return i{stack=lit:xs}
procExp i@Interp {stack=x:xs} (Invoke "pop") = return i{stack=xs}

なお,最低限のpopとpushもいれておきます.procExpでパターンマッチの仕方を変えてます.Interpのフィールドを増やしたので,procExp Interp{stack=xs}の形ではエラーになってしまいます.「dictはどうした!?」というわけです.そこで,iでうけとめてアズパターンで欲しい部分だけうけとります.

ASTの要素の一つNewWord Name ASTに対するprocExpを考えます.これはNameというキーで内容がASTであるような辞書のエントリを作ることなので,

procExp i@Interp {dict = dict}  (NewWord name ast)
     = return i{dict = Map.insert name ast dict }

となります.ここで,insertは

insert :: Ord k => k -> a -> Map k a -> Map k a

であり,insert name ast dictはキーがname,内容がastのエントリをdictに挿入するしてできあがったものを返します.

そして,これで登録された新しいオペレータを実行するのもやはりprocExpです.「組み込まれたオペレータ」に対するマッチでマッチしなければ,次は新しく定義されたものを辞書から探す必要があります.名前をキーに辞書から探すには(Map.!)を使います

procExp i@Interp{dict=dict} (Invoke name) = interp i (dict Map.! name)

main

コンパイルできるようにmain関数を作ります.ほとんどharrorth--と同じですが,インタープリータの中身を変えた(辞書を追加した)ので,辞書の初期状態を与える必要があります.Data.Mapの関数emptyを使って空の辞書を与えることにします.

また,printInerpはdo記法にしました.これはinterpの型をInterp -> AST -> IO Interpとしたため,直接printさせることができない(IO InterpはShowクラスのインスタンスではない)ので,IOを外してprintにおくるためです.

main = do src <- getLine
          case (parse phorthProg "" src) of
              Left err -> do putStr "parse Error ar"
                             print err
              Right x -> printInterp x

printInterp:: AST -> IO ()
printInterp ast = do fin <- interp Interp{ stack=[], dict=Map.empty} ast 
                     print fin

コンパイルしてみます.

ghc --make -o phorth phorth.hs
[1 of 1] Compiling Main             ( phorth.hs, phorth.o )
Linking phorth.exe ...

実行してみます.

phorth
1 2 /a {10 20} def a a a
Interp {stack = [20,10,20,10,20,10,2,1], dict = fromList [("a",[Push 10,Push 20])]}

phorth
1 2 /a {10 20 /b {100 200} def   } def a b
Interp {stack = [200,100,20,10,2,1], dict = fromList [("a",[Push 10,Push 20,NewWord "b" [Push 100,Push 200]]),("b",[Push 100,Push 200])]}

期待したうごきになっています.

次はもうちょっといろいろできるように追加・改造してみます.

この段階の全ソースは以下の通りです.

import Text.ParserCombinators.Parsec
import qualified Data.Map as Map

-- AST
type AST = [Exp]
data Exp =   Push Literal
           | Invoke Word 
           | NewWord Name AST
           deriving Show
type Literal = Integer
type Word = String
type Name = String

type Stack = [Literal]
type Dict  = Map.Map Name AST

-- Parser
phorthProg::Parser AST
phorthProg = do -- skipMany space
                ast <- phorth
                eof
                return ast

phorth::Parser AST
phorth = do skipMany space
            sepEndBy1 phorthExp (skipMany1 space)

phorthExp::Parser Exp
phorthExp = literal <|> word <|> newWord

literal::Parser Exp
literal = (many1 digit) >>= \x ->return (Push (read x))

word::Parser Exp
word = many1 letter >>= \x ->return (Invoke x)

newWord::Parser Exp
newWord =do char '/'
            name <- many1 letter
            skipMany space
            char '{'
            ast <- phorth
            char '}'
            skipMany space
            string "def"
            return $ NewWord name ast

-- Evaluator

data Interp = Interp{  stack::Stack
                      ,dict ::Dict
  } deriving (Show) -- interpreter

interp:: Interp -> AST -> IO Interp
interp i (x:xs) = do i' <- procExp i x
                     interp i' xs
interp i [] = return i

procExp:: Interp -> Exp -> IO Interp
procExp i@Interp {stack=xs}    (Push lit)     = return i{stack=lit:xs}
procExp i@Interp {stack=x:xs}  (Invoke "pop") = return i{stack=xs}
procExp i@Interp {dict = dict} (NewWord name ast) 
        = return  i{dict = Map.insert name ast dict }
procExp i@Interp{dict=dict} (Invoke name) = interp i (dict Map.! name)

-- main

main = do src <- getLine
          case (parse phorthProg "" src) of
              Left err -> do putStr "parse Error ar"
                             print err
              Right x -> printInterp x

printInterp:: AST -> IO ()
printInterp ast = do fin <- interp Interp{ stack=[], dict=Map.empty} ast 
                     print fin