Hatena::ブログ(Diary)

らくがきえんじん

リンク切れ記事の多くは http://d.hatena.ne.jp/keigoi/ に移動しています. たとえば http://d.hatena.ne.jp/syd_syd/20080302#p1 の記事は http://d.hatena.ne.jp/keigoi/20080302#p1 にあります.

2008-08-13

Language.C を使ってみる with Data.Generics

Language.CHaskell用の、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でコンパイルできるように変換する。未完。


C言語のコードをアレしてコレしてイチャイチャできたらなあという要望はどの業界でもありそうなので、これを機に皆でHaskellとLanguage.Cを使い始めるとよいと思います。


Language.C のインストール

  • happyをインストール
  • alexをインストール
  • haddockをインストール(haddockにリファレンスを生成させたい場合)

この後

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

moveVarDecls.hs

使い方

このように変数の宣言をブロックの頭にもってくる.これだけでは 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 _ = []