BLOG

この記事では僕がスタックマシン用にC言語のコンパイラを作ったときに使ってみたフロントエンドのライブラリのメモ書きを残してます。あくまでも「手っ取り早く動かしたい」って人向けなので「きちんと自分で実装して理解を深めたい」っていう人向けではないです(そもそもそういう人はライブラリを探さないとは思いますが。)

あともし他にもおすすめがあったら教えてほしいです…。


PLY

これはC言語に用途が限定されませんが、言わずとしれたlex-yaccのpython実装バージョンです。簡単な言語はある程度これでパースできます。かなり汎用性が高いので検索すると結構日本語でも情報が出てきます。

ただ、C言語にしっかり対応しようとすると、typedefされたキーワードに関するキャスト「(type)expr」がパースできない問題で使えなくなります。lexとyaccを同時に動かしてシンボルテーブルを共有することでこの問題を解決することができるらしいのですが、このライブラリでそれをする方法はちょっと分かりませんでした。僕は最初これに気づかずしばらくこれで実装してました。その時のコードはここにありますが、あくまでも参考程度って感じです。

大事なことは公式リファレンスがよくまとまっています。


PCPP

pythonで動くCのプリプロセッサ(CPP)です。便利。助かる。

ただドキュメントがあまり充実してないっぽい?

使い方

import io
from pcpp import Preprocessor

# :: ファイルから使う場合 ::
cpp = Preprocessor()
cpp.add_path("/mylibpath") #option
g = io.StringIO("")

with open(args.filename, mode="r") as f:
    cpp.parse(f)

cpp.write(g)
processed = g.getvalue()

# :: 文字列を直接入れる場合 ::
source = "~~source~~"
cpp = Preprocessor()
cpp.parse(source)
g = io.StringIO("")
cpp.write(g)
processed = g.getvalue()

その他の設定項目はpcppのここを見ると一応わかります。


pycparser

pythonで実装されたCのパーサー。すごい。もうこれでいいじゃん。

もともとはCコードのビジュアライゼーションなどに使う用途っぽい? AST木を出力してくれるのですが、デフォルトの出力は少し癖が強くてコンパイラに使うには苦痛を伴います。が、それでも便利なので使いました。

カスタマイズ方法

コンパイラを自作するにあたり独自の構文や組み込み関数を追加したいケースは多いと思います。

pycparserは以下のステップでカスタマイズできます。

  • 1: _c_ast.cfgに追加したい構文の構造定義を追記する
  • 2: _ast_gen.pyを実行するとc_ast.pyに定義した構文のクラスの記述が自動生成される
  • 3: c_parser.pyに前構文が記述されているので、いい感じのところに自分の追加したい構文を実際に記述する

かなり曖昧な手順ですが、上記に関連するファイルを眺めるとだいたい分かると思われます。

ちなみに、僕が任意の命令を引数付きで呼び出せるraw命令を追加したときの_c_ast.cfgはこれで、c_parser.pyはこれになります。

使い方

from pycparser import CParser, c_parser, c_ast

parser = CParser(lex_optimize=False, yacc_optimize=False)
ast = parser.parse(code_string, filename="myfilename")
ast.show()

構文をカスタマイズした場合、lex_optimize, yacc_optimizeオプションをFalseにしないとパーサーテーブルが更新されずカスタマイズが反映されません。

今はparseメソッドでstringを入力にパースを行っていますが、parsefileメソッドを使えばファイルがそのままパースできます。

このプログラムを実行するとパースしたast木が極めて微妙に表示されます。何が微妙かって、なんかプリントされる要素がそれぞれの木のどの名前の要素なのかが全く表示されないんですよね。

このast木を紐解くにあたり、どのノードがどんな名前の子を持っているのか知りたくなると思いますが、それはすべて_c_ast.cfgに記述された通りになります。ので、このファイルを印刷したくなるくらいにらめっこすることになります。

僕はpycparserから吐き出されたast木をコンパイラに使うast木に転写するプログラムを書きました。

かなり苦痛感の強いプログラムでちょっと恥ずかしいですがこんな感じです。

特に、pycparserデフォルトの構文では何でもかんでもdeclというノードに紐付けられてそれがかなり厄介に感じました。もう少しいい方法があったかもしれません。もしくはpycparserのyaccを大きく書き換えてしまうというのも手かもしれません。


ひとまずはこんな感じです。またなにか思い出したり、わかったことがあれば追記しようと思います。