Arantium Maestum

プログラミング、囲碁、読書の話題

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

引数が二つある。変換元のLispValSSA中間言語で変数名に使うための数字のリストだ。その数字のリストをtという"1_2_3"のような文字列に変換していて、tの先頭にスタック変数を表すsか一時変数を表すaをつけて、AllocStoreLoadの命令を作成している(その三つを使ってLLVM IRで数字を表現している)

しかしソースには行ごとにLispValが含まれ得るので、実際には[LispVal] -> [SSA]の関数が必要:

lispValsToSSAs :: [LispVal] -> [SSA]
lispValsToSSAs = concat . reverse . zipWith lispValToSSA [[x] | x <- [1..]] . reverse

一番最後の行のLispValから順に変数名用の数字を振りつつ前述のlispValToSSAを適用し、SSA型のリストのリストを最後にconcatSSA型のリストにしている。

次回

ようやくLispで書かれた(?)ソースからLLVM IRへの変換パスがすべて定義できた。次回はその変換の流れを見ていきたい。