gcc - yyparse (printf debug with dump-tree)
init は飛ばして(@@@) 字句解析もつまらんから飛ばして (@@@) 、構文解析を見ていこうと思う。(*lang_hooks.parse_file) (set_yydebug); と呼ばれているフックの先は c_common_parse_file のようだ。 pch_init なんてのがあってさすが 3.4 と思った。 c_parse_file ではすぐに yyparse に入ってしまう。
yyparse は c-parse.y にあるのだが、少しびっくりすることにこのファイルは c-parse.in から生成されている。 C と Objective-C のパーサを切り分けるためのようだ。まあつまるところ生成された c-parse.y を読めば良いと思う。さて、 yacc のソースは書く分には良いのですけど処理が追いにくいのがやっかいだ。とはいえとても重要な場所だと思うので、簡単なソースを書いてそれに対する挙動を逐一追っていって理解を深めようと思う。
とりあえずとても簡単な例から考える。
/* main.c */ int main() { return 0; }
これに対してどういう手順で木構造が生成されているかを調べる。逐一追う方法としては printf を埋め込んでやることにする。埋める場所のあたりを付けるために c-parse.in を読むと、 fndef は今回の場合、
declspecs_ts setspecs declarator { if (! start_function (current_declspecs, $3, all_prefix_attributes)) YYERROR1; } old_style_parm_decls save_location { DECL_SOURCE_LOCATION (current_function_decl) = $6; store_parm_decls (); } compstmt_or_error { finish_function (); POP_DECLSPEC_STACK; }
がマッチしそうだ。というわけで start_function 内に渡した引数をダンプするものと、 compstmt_or_error で関数の実装をダンプさせてやれば良いだろう。
普段から tree をダンプしたりすると gcc のビルド時に動く xgcc などでもダンプされて五月蝿いので、 -v オプションが付いている時のみダンプする。そのために c-opts.c の bool verbose の static をはずし、 c-decl.c の冒頭で、 extern bool verbose; を書き、 c-decl.c の start_function 内に、
if (verbose) { FILE* fp = fopen("log", "a"); fprintf(fp, "start_function = {\n"); fprintf(fp, " declspecs =\n"); dump_node(declspecs, 0, fp); fprintf(fp, " declarator =\n"); dump_node(declarator, 0, fp); fprintf(fp, " attributes =\n"); if (attributes) dump_node(attributes, 0, fp); fprintf(fp, "}\n"); fclose(fp); }
を埋め、 c-parse.in の冒頭に同じく extern bool verbose; を書き、 compstmt: の中に、
if (verbose) { FILE* fp = fopen("log", "a"); fprintf(fp, "compstmt =\n"); dump_node($1, 0, fp); fclose(fp); }
を埋める。これで関数宣言と関数の定義の節の tree がダンプされるはず。早速 tree-dump.c を読んでおいた効果があらわれるわけ。
で、 main.c をダンプすると、
start_function = { declspecs = @1 tree_list valu: @2 @2 identifier_node strg: int lngt: 3 declarator = @1 call_expr fn : @2 args: @3 @2 identifier_node strg: main lngt: 4 @3 tree_list attributes = } compstmt = @1 compound_stmt line: 1 next: @2 @2 scope_stmt line: 2 begn clnp next: @3 @3 return_stmt line: 2 expr: @4 next: @5 @4 modify_expr type: @6 op 0: @7 op 1: @8 @5 scope_stmt line: 3 end clnp @6 integer_type name: @9 size: @10 algn: 32 prec: 32 min : @11 max : @12 @7 result_decl type: @6 srcp: main.c:1 size: @10 algn: 32 @8 integer_cst type: @6 low : 0
この後もだらだらと続くのだが、これ以上先は srcp:
しかし dump-tree の出力はわかりやすい。きちんとそれらしいものができていることがわかる。後で気付いたことだが、このダンプコードを埋め込んだおかげで理解が大幅に容易になった。