非破壊Hash
ひさしぶりにParsecをいじってみた.
前回,(http://d.hatena.ne.jp/hoge1e3/20080129)
var a; a=3; var b; b=a+3;
というプログラムがあったときに,a や b にアドレスを割り付けたい,と言ったので,この部分の実装をやろうとした,が失敗.
var a;
という変数宣言を読ませる部分.ここで,
「a は アドレス 0 に割り当てる」
という処理をしたい.
その後,var b; がきたら
「b は アドレス 1 に割り当てる」
という処理をしたい.
Parsec には getState / setState があるから,状態にアドレスの連番とハッシュテーブル( 変数名 - アドレス ) をもたせたいと思って,次のように状態を定義し,
data ParserState = PStat Int (HashTable String Int) -- HashTable は 標準のData.HashTable
変数宣言を解析する部分を次のようにした.
vardecl :: Parser Decl vardecl = do { tok "var" ; name <- symbol; tok ";" ; st <- getState ; h <- (getSyms st) ; update h name (getNum st) ; setState (PStat ((getNum st)+1) h ) ; return $ VarDecl name (getNum st) }
が,
Couldn't match expected type `GenParser Char ParserState t' against inferred type `HashTable String Int'
これはdo文はdo文でもパーサのdo文だから,ここで IO を挟んだりはできない,ということらしい.
getState, setStateと馴染むためには,おそらくData.HashTableのようなIOベースのハッシュテーブルじゃなくて,非破壊的なハッシュテーブル,つまり
update :: Hash k v -> k -> v -> Hash k v
のように,
「もとのHash の内容にそっくりだけど,キーk に 値v を設定している点だけがちがう新しい Hash」
を返す,というメソッドがあればいいのに.
多分メモリを食うし速度も遅いからないのだろうか.
属性文法の面倒な点
Attribute Grammars を読んでみる.これの22ページあたりに出てくる文法をとりあげてみる.
- Symbol-table を毎回書かなければいけない
- add-item て? 副作用があっていいの?
::= program is Symbol-table( ) <- add-item( (Name( ), program), empty-table) ::= begin end Symbol-table( ) <- table-union( Symbol-table( ), Symbol-table( )) ::= := Symbol-table( ) <- Symbol-table( ) Type( ) <- lookup-type( Name( ), Symbol-table( )) ::= write Symbol-table( ) <- Symbol-table( ) Type( ) <- integer
コード生成部できた
Parsec で作る簡単なコンパイラ.
- 結局,字句解析のかわりに tok str というあやしいコンビネータ(str を読んで,その後ろのスペースをとばす) を定義
- コード生成部は toCodable クラスを使う.これで,命令のリストが作られる.
- toCodable :: a -> CodeState -> [String]
- CodeState は,現在打ち出しているコードが右辺値か左辺値かを示す.
module Parse where import Text.ParserCombinators.Parsec --main r :: String -> IO () r input = case parse source "lisp" input of Left err -> putStrLn $ "No match: " ++ show err Right val -> putStrLn $ "Found value" ++ (show $ map (toCode RVal) val) --util mny :: Parser a -> Parser [a] mny p = many (do { spc; p } ) toks1 :: Parser Char -> Parser String toks1 p = do {res<-many1 p ; spc; return res } spc :: Parser () spc = skipMany space tok :: String -> Parser String tok str = do { s <- string str; spc ; return s } --grammar source :: Parser [Decl] source = do { bodies <- mny ( vardecl <|> statement) ; return bodies } vardecl :: Parser Decl vardecl = do { tok "var" ; name <- symbol; tok ";" ; return $ VarDecl name } statement :: Parser Decl statement = do { e <- expr ; tok ";" ; return $ StmtDecl $ ExprStmt e } expr :: Parser Expr expr = do { left <- add ; option left (do { tok "=" ; a<-add ; return (BinOp left "=" a) }) } add = do { left <- element ; rights <- mny (do { op <- operators ; e <- element ; return (op,e) } ) ; return (makeBinOp left rights) } operators :: Parser String operators = tok "+" <|> tok "-" element :: Parser Expr element = intConst <|> varRef varRef :: Parser Expr varRef = do { n <- symbol ; return $ VarRef n } intConst :: Parser Expr intConst = do { v <- digits ; return $ IntConst v } symbol :: Parser String symbol = toks1 letter digits :: Parser Int digits = do { d <- toks1 (oneOf "0123456789") ; return (read d::Int) -- ??? } --semantics makeBinOp :: Expr -> [(String,Expr)] -> Expr makeBinOp left [ ] = left makeBinOp left ((op,elem):rest) = makeBinOp (BinOp left op elem) rest data Decl = VarDecl String | StmtDecl Statement -- ! data Statement = ExprStmt Expr data Expr = BinOp Expr String Expr | IntConst Int | VarRef String data CodeState = LVal | RVal class ToCodable a where toCode:: CodeState -> a -> [String] instance ToCodable Statement where toCode c (ExprStmt e) = (toCode c e) ++ ["POP"] instance ToCodable Expr where toCode c (BinOp l "=" r) = (toCode LVal l) ++ (toCode RVal r) ++ ["="] toCode c (BinOp l o r) = (toCode RVal l) ++ (toCode RVal r) ++ [o] toCode c (IntConst i) = [show i] toCode RVal (VarRef name) = [name , "Read"]; toCode LVal (VarRef name) = [name , "Addr"]; instance ToCodable Decl where toCode c (StmtDecl st) = toCode c st toCode c (VarDecl name) = [ ]
実行例
Parse> r "var a; a=3; var b; b=a+3;" Found value[ [ ],["a","Addr","3","=","POP"],[ ], ["b","Addr","a","Read","3","+","=","POP"] ] Parse>
toString と show と
HaskellでtoString - 西尾泰和のはてなダイアリー にて,この前の 「Haskell で toStringを定義する方法 」を書いてくれた.
確かにtoString は実装できるのだけど,すでに show という手段があるのだから,
- 通常はデフォルトのshow を使って
- ある一部のクラスについてはshow をカスタマイズしたい
という書き方ができるとなおよい(JavaはtoStringをオーバーライドすればできる),これがどうもうまくいかない
data Bar = Bar Int data Foo = Foo Bar deriving Show show Foo b = "" ++ show b ++" " main = show (Foo (Bar 5))
こんなエラーがでる.
Ambiguous occurrence `show' It could refer to either `show', defined at C:/eclipse/workspace/haskell/src/Test.hs:5:3 or `show', imported from Prelude
ようは show の定義が2つあるからどっちを採用すればわからん,ということらしい.
よりspecific なほうを採用する,というOO な考えはやっぱり関数型言語にはないのだろうか.
Parsec続き
代入式らしいものは書けるようになった.あと型クラスを使って toCode (コードを生成する関数)を実装してみる.うーんやっぱり 型なのに「instance」と書くのはまだ抵抗がある.
あと Foo {bar:: Int} という記法は扱いがすごい面倒なことがわかった.特に関数を書くときに
hoge Foo {bar:: b} = fuga b
みたいに書かないといけないから.やっぱりこう書かせてよー(まだ言っている)
hoge f::Foo = fuga (f.bar)
module Parse where import Text.ParserCombinators.Parsec --main r :: String -> String r input = case parse source "lisp" input of Left err -> "No match: " ++ show err Right val -> "Found value" ++ (show $ map toCode val) --util mny :: Parser a -> Parser [a] mny p = many (do { spc; p } ) toks1 :: Parser Char -> Parser String toks1 p = do {res<-many1 p ; spc; return res } spc :: Parser () spc = skipMany space tok :: String -> Parser String tok str = do { s <- string str; spc ; return s } --grammar source :: Parser [Decl] source = do { bodies <- mny ( vardecl <|> statement) ; return bodies } vardecl :: Parser Decl vardecl = do { tok "var" ; name <- symbol; tok ";" ; return $ VarDecl {vdName = name } } statement :: Parser Decl statement = do { e <- expr ; tok ";" ; return $ StmtDecl $ ExprStmt e } expr :: Parser Expr expr = do { left <- add ; option left (do { tok "=" ; a<-add ; return (BinOp left "=" a) }) } add = do { left <- element ; rights <- mny (do { op <- operators ; e <- element ; return (op,e) } ) ; return (makeBinOp left rights) } operators :: Parser String operators = tok "+" <|> tok "-" element :: Parser Expr element = intConst <|> varRef varRef :: Parser Expr varRef = do { n <- symbol ; return VarRef {vrName=n} } intConst :: Parser Expr intConst = do { v <- digits ; return $ IntConst v } symbol :: Parser String symbol = toks1 letter digits :: Parser Int digits = do { d <- toks1 (oneOf "0123456789") ; return (read d::Int) -- ??? } --semantics makeBinOp :: Expr -> [(String,Expr)] -> Expr makeBinOp left [] = left makeBinOp left ((op,elem):rest) = makeBinOp (BinOp left op elem) rest data Decl = VarDecl { vdName:: String } | StmtDecl Statement -- ! deriving Show data Statement = ExprStmt Expr deriving Show data Expr = BinOp Expr String Expr | IntConst Int | VarRef { vrName:: String } deriving Show class (Show a)=> Codable a where toCode:: a -> String toCode a=show a instance Codable Statement where toCode (ExprStmt e) = "STM "++ (toCode e) ++";" instance Codable Expr where toCode (BinOp l o r) = (toCode l) ++ (toCode r) ++ o toCode a=show a instance Codable Decl where toCode (StmtDecl st) = toCode st toCode a=show a