phorth---(その1.3)オペレータ定義の仕様変更1
Parserの改造
新しいオペレータを定義する際に
/a { 1 2 } def
という構文を使って,これをパースして
[NewWord "a" [Push 1 ,Push 2]]
というASTを構築しました.これを評価すると
Interp {stack = [], dict = fromList [("a",[Push 1,Push 2])]}
という状態のインタプリータができます.しかし,これではちょっとオペレータdefが特別扱いされすぎです.オペレータはスタックを消費して何かの変化を起こすものと統一したいです.
ということで,まずExp型を変更します.今までは
data Exp = Push Literal | Invoke Word | NewWord Name AST deriving Show type Literal = Integer type Word = String type Name = String
でした.これを
data Exp = Push Literal | Invoke Word | Name Word | Block AST deriving Show type Literal = Integer type Word = String
とすることにします.NewWordを外して新しくName Word,Block ASTを作ります.これは「新しいオペレータの名前」と「コードのブロック」(新しいオペレータの内容)を表します.
これに対応して,パーサも変更します.
phorthExp::Parser Exp -- phorthExp = literal <|> word <|> newWord phorthExp = literal <|> word <|> name <|> block
newWordパーサをなくして,name,blockパーサを考えます.
nameパーサ
/aをパースして, Name "a"を作るパーサです.
name::Parser Exp name =do char '/' name <- many1 letter return $ Name name
blockパーサ
{ }をパースして,Block ASTを作るパーサです.
部分はphorthの任意のコードなので,再帰的なパーサになります.
block::Parser Exp block = do char '{' ast <- phorth char '}' return $ Block ast
パーサを実行すると
Prelude> :l parsetest.hs [1 of 1] Compiling Main ( parsetest.hs, interpreted ) Ok, modules loaded: Main. *Main> parseTest phorthProg "" "1 2 /a {1 2 /b {3 2} def } def" [Push 1,Push 2,Name "a",Block [Push 1,Push 2,Name "b",Block [Push 3,Push 2],Invoke "def"],Invoke "def"]
これは問題ないですが,
*Main> parseTest phorthProg "1 2 /a{1 2 /b {3 2} def } def" parse error at (line 1, column 7): unexpected "{" expecting letter, space or end of input *Main> parseTest phorthProg "" "1 2 /a {1 2 /b {3 2} def }def" parse error at (line 1, column 27): unexpected "d" expecting space or end of input
・・・問題ありです.「{」の前,「}」の後は空白がなくてもきちんと区切られるべきです.これを修正するには
phorth::Parser AST phorth = do skipMany space -- sepEndBy1 phorthExp (skipMany1 space) sepEndBy1 phorthExp (skipMany space)
とすればよいでしょう.ついでにオペレータの名前の文字種として数字も許可します.ただし,オペレータの最初の文字はアルファベットだけに制限します.これにともない,wordパーサも変更します.オペレータの名前を構成する文字列をパースする部分は共通なので一個のパーサopnameにまとめました.
opname::Parser String opname = do first <- letter others <- many (letter <|> digit) return $ first:others word::Parser Exp word = opname >>= \x -> return $ Invoke x name::Parser Exp name = char '/' >> opname >>= \x -> return $ Name x word::Parser Exp -- word = many1 letter >>= \x ->return (Invoke x) word = do first <- letter others <- many (letter <|> digit) return $ first:others
これで
*Main> parseTest phorthProg "1 2 /a{1 2 /b12 {3 2 }def }def" [Push 1,Push 2,Name "a",Block [Push 1,Push 2,Name "b12",Block [Push 3,Push 2],Invoke "def"],Invoke "def"]
とできます.
まとめると・・
import Text.ParserCombinators.Parsec -- AST type AST = [Exp] data Exp = Push Literal | Invoke Word | Name Word | Block AST deriving Show type Literal = Integer type Word = String -- Parser phorthProg::Parser AST phorthProg = do ast <- phorth eof return ast phorth::Parser AST phorth = do skipMany space sepEndBy1 phorthExp (skipMany space) phorthExp::Parser Exp phorthExp = literal <|> word <|> name <|> block literal::Parser Exp literal = (many1 digit) >>= \x ->return (Push (read x)) opname::Parser String opname = do first <- letter others <- many (letter <|> digit) return $ first:others word::Parser Exp word = opname >>= \x -> return $ Invoke x name::Parser Exp name = char '/' >> opname >>= \x -> return $ Name x block::Parser Exp block = do char '{' ast <- phorth char '}' return $ Block ast