HaskellでLisp to LLVM IRコンパイラ その5 ( LispValの定義、パーサ作成とSSA中間言語への変換)
LispValという再帰的なデータ型を定義して、まずコードをそのLispValとしてパースする。流れはインタプリタを作った時とまったく同じ。
インタプリタの場合はそこでeval
を適用したが、コンパイラの場合はそこからSSA中間言語に変換を加えていく。
LispVal
型
まずデータ型の定義:
data LispVal = Number Integer instance Show LispVal where show = showLispVal showLispVal :: LispVal -> String showLispVal (Number n) = show n
まだ非常に機能が制限されていて数字を(別の行にあるなら複数)扱うだけなので、LispValも数字だけ。
パーサ
Parsecを使ってMonad的にパーサを書く:
readLispVals :: String -> [LispVal] readLispVals input = case parse parseLispVals "lispval" input of Left err -> error $ "Error parsing LispVal: " ++ show err Right vals -> vals parseLispVals :: Parser [LispVal] parseLispVals = endBy parseLispVal (char '\n') parseLispVal :: Parser LispVal parseLispVal = parseNum parseNum :: Parser LispVal parseNum = do s <- many1 digit return $ Number (read s)
parseNum
関数にdo
記法中毒が現れてる。書き直したい・・・
LispVal
からSSA中間言語への変換
まずは一つのLispValをSSAデータ型へと変換する関数:
lispValToSSA :: [Integer] -> LispVal -> [SSA] lispValToSSA is (Number n) = [Alloc s, Store s (show n), Load a s] where t = concat $ tail $ concat $ [["_", show i] | i <- is] s = 's':t a = 'a':t
引数が二つある。変換元のLispVal
とSSA中間言語で変数名に使うための数字のリストだ。その数字のリストをt
という"1_2_3"のような文字列に変換していて、t
の先頭にスタック変数を表すsか一時変数を表すaをつけて、Alloc
、Store
、Load
の命令を作成している(その三つを使ってLLVM IRで数字を表現している)
しかしソースには行ごとにLispVal
が含まれ得るので、実際には[LispVal] -> [SSA]
の関数が必要:
lispValsToSSAs :: [LispVal] -> [SSA] lispValsToSSAs = concat . reverse . zipWith lispValToSSA [[x] | x <- [1..]] . reverse
一番最後の行のLispVal
から順に変数名用の数字を振りつつ前述のlispValToSSA
を適用し、SSA型のリストのリストを最後にconcat
でSSA型のリストにしている。
次回
ようやくLispで書かれた(?)ソースからLLVM IRへの変換パスがすべて定義できた。次回はその変換の流れを見ていきたい。