Language.C を使ってみる with Data.Generics
Language.CはHaskell用の、C言語のソースコードを構文解析するライブラリ。
構文木はHaskellのデータとして操作可能で、これのおかげでC言語のコードを色々と操作できる。意味解析に役立ついくつかの補助関数も定義されているようだ(よく調べてない)。
やったこと、動機
- JHC (Haskellのコンパイラ;ISO C互換のコードを吐く)は C99のコードを吐くようだ
- ツールの制約でgcc 2.95(19991024)しか手元にない。 gcc 2.95はC89しか受け付けないようだ
- jhc が吐くコードを gcc 2.95 でコンパイルできるよう自動変換したい
そこで Haskell用の Cパーザである Language.C を使って、JHCが吐くコードを gcc 2.95でコンパイルできるように変換する。未完。
- C89では 変数宣言が ブロックの頭以外に来たらエラー。全ての変数宣言をブロックの頭に移動させるコードを書いた。
- Data.Generics という 木構造のトラバースに超便利なライブラリを使った。scrap your boilerplate(SYB)と呼ばれるアレだ。このページのサンプルコードだけで十分に価値は理解できるとおもう
C言語のコードをアレしてコレしてイチャイチャできたらなあという要望はどの業界でもありそうなので、これを機に皆でHaskellとLanguage.Cを使い始めるとよいと思います。
Language.C のインストール
この後
darcs get http://code.haskell.org/language-c cd language-c runhaskell Setup.hs configure runhaskell Setup.hs build sudo runhaskell Setup.hs install runhaskell Setup.hs haddock # リファレンスが欲しい場合
ソースdownload
使い方
このように変数の宣言をブロックの頭にもってくる.これだけでは jhcの吐くコードは gcc 2.95 ではコンパイルできないようだがそれはまた
-bash-3.2$ cat test.c void f2() { } void f1() { { int y=0, z=y+1; f2(); int x=1; } f2(); int y=2; for(int i=0; i<100; i++) { } } -bash-3.2$ ./moveVarDecls test.c void f2() { } void f1() { int y; { int y, z; int x; y = 0; z = y + 1; f2(); x = 1; } f2(); y = 2; for (int i = 0; i < 100; i++) { } }
ソース
module Main where import System import Data.Generics import Language.C import Language.C.System.GCC -- 第一引数のファイルを読み込み標準出力に出力 main = do (filename:[]) <- getArgs parseMyFile filename >>= (printMyAST . moveVarDecls) -- Language.C を使ってソースを parse parseMyFile :: FilePath -> IO CTranslUnit parseMyFile input_file = do parse_result <- parseCFile (newGCC "gcc") Nothing [] input_file -- プリプロセスに使うgcc (cpp?) のパスを設定しよう case parse_result of Left parse_err -> error (show parse_err) Right ast -> return ast -- 表示 printMyAST :: CTranslUnit -> IO () printMyAST ctu = (print . pretty) ctu -- Data.Generics (scrap your boilerplate) を使った、構文木のトラバース moveVarDecls :: CTranslUnit -> CTranslUnit moveVarDecls = everywhere (mkT moveVarDecls_) -- ブロックの内容を宣言と代入文に分ける. moveVarDecls_ :: [CBlockItem] -> [CBlockItem] moveVarDecls_ bs = concat decls++concat stmts -- 宣言の後にブロックが来る where (decls,stmts) = unzip (map splitVarDecls bs) -- 1つの変数宣言を宣言と代入文に分割する splitVarDecls :: CBlockItem -> ([CBlockItem],[CBlockItem]) splitVarDecls (CBlockDecl (CDecl sp assign ninfo)) = ([mkDecl sp assign ninfo], mkStmts assign ninfo) splitVarDecls x = ([],[x]) -- 宣言文から代入文を除去する. -- sp 型、記憶子、修飾子のリスト -- assign 変数名および代入文 -- ninfo ノード情報 mkDecl :: [CDeclSpec] -> [(Maybe CDeclr, Maybe CInit, Maybe CExpr)] -> NodeInfo -> CBlockItem mkDecl sp assign ninfo = CBlockDecl (CDecl sp (map mkAssign assign) ninfo) where -- declr 変数名と、constやポインタ等の修飾子 (int *x; における *xの部分) -- init 代入文の右辺 -- expr 構造体のフィールド宣言におけるビット長(ここではNothing) mkAssign (a@(_, Nothing, _)) = a mkAssign (declr, Just init, expr) = (declr, Nothing, expr) -- 代入文を除去 -- 宣言文から代入文を抜き出す. -- assign 変数名および代入文 -- ninfo ノード情報 mkStmts :: [(Maybe CDeclr, Maybe CInit, Maybe CExpr)] -> NodeInfo -> [CBlockItem] mkStmts assign ninfo = concatMap mkExpr assign where mkExpr (Just (CDeclr (Just name) _ _ _ v_ninfo), Just (CInitExpr expr i_ninfo), _) = [CBlockStmt (CExpr (Just (CAssign CAssignOp (CVar name v_ninfo) expr i_ninfo)) ninfo)] -- 代入文を生成 mkExpr _ = []