HaskellでBrainf*ckコンパイラ
最近Haskell熱が再燃中ってこともありふと思い立って。 インタプリタだけでなく、x86_64アセンブリコードを出力する機能も実装しました。 (今さらなテーマなのは重々承知…)
https://github.com/jou4/Brainf
FreeBSD 9.0, Ubuntu 12.10, Mac OS 10.6.8で動作することを確認しています。
haskell-platformをインストール済みであればソースコードをダウンロードしてmakeすれば使える状態になると思います。
使い方は以下のとおり。
$ ./bf -e ">++++++++++[<++++++++>-]<."
ファイルも渡せます。
$ ./bf example/hello.bf
$ ./bf < example/hello.bf
アセンブリを出力するにはオプション[-s]を渡します。
$ ./bf -s example/hello.bf -o output.s
$ gcc output.s
$ ./a.out
オプション[-o]で出力ファイルパスを指定しない場合はカレントディレクトリに[output.s]という名前でファイルを作成します。
以下は実装してみてのメモ。
ジャンプ命令'['と']'に囲まれた部分を1ブロックとして扱った
ジャンプ命令ごとに対応する括弧を探すのや アセンブリ出力でラベルを埋め込む際に面倒になるかなと思われたので 内部コードに変換する処理の中で対応する括弧をひとまとめにしてみました。 ただ実装してみてあまり必要なかったような気もしています。
30000バイトのメモリを確保
Wikipediaに「少なくとも30000個の要素を持つバイトの配列」と書かれていたので。 データポインタ用には8バイト確保して、-8(%rbp)に配置しました。 その下に30000バイトを確保して、%rspからのオフセットでアクセスしています。
Haskell版getopt
マニュアルに 書かれていたexampleを少し書き換えたらそのまま使えました。使いやすいし便利なライブラリだと思います。 以下はオプションを定義しているコードの抜粋です。
options :: [OptDescr (Options -> Options)] options = [ Option ['e'] ["expr"] (ReqArg (\e opts -> opts { optExpr = Just e }) "EXPRESSION") "expression" , Option ['s'] ["assembly"] (NoArg (\opts -> opts { optAssembly = True })) "emit assembly code flag" ]
Cプリプロセッサを使ってみた
アセンブリを出力するにあたりOSごとにパラメータを変えたかったので。 以下のような使い方ができます。
{-# OPTIONS_GHC -cpp #-} #if defined(linux_HOST_OS) -- Linux #elif defined(freebsd_HOST_OS) -- FreeBSD #elif defined(darwin_HOST_OS) -- MacOSX #elif defined(mingw32_HOST_OS) -- Windows #else -- Unknown #endif
環境ごとにコードを変える方法はこんなんで良かったのかな。 より適切な方法があれば知りたいです。 それとCではお馴染みの、定義したマクロを変数代入の右辺の値として利用するというようなことはできませんでした。
Stateモナド便利
定義を理解するのは苦労すると思いますが使う分には簡単だし便利。 ただ単体で使うよりもIOモナドとの併用が多いような。 なので私がよく使うのはStateTの方ですね。
連続するインクリメント/デクリメントはまとめられた
素直に書き下しましたが、Brainf*ckの仕様上インクリメント/デクリメントが 連続するのでまとめた方がすっきりするなあと実装を終えて思いました。
Windowsのアセンブリがわからない
syscallとかどうすればいいのか。環境も時間もないので放置。
組んでいて楽しかったです。 そろそろオレオレ言語コンパイラの設計と開発も再開したいな。 Haskellで書きなおすのもありだなと。