C++ 構文解析 構文解析器の状態保存と復元 2019.03.18 長谷川啓
例1 void f() { int (n); // 宣言 ... int (n+n); // 式 }
例1 int の導出 (n+n); int (n); int simple-type-specifier decl-specifier simple-type-specifier の次の字句が ‘(‘ であった場合に, 残りが init-declarator-list なのか ‘(‘ expression-list ‘)’ なのか不明 decl-specifier-seq
宣言を優先させる yacc/bison のレポートファイル State X1 R11 type_specifier: simple_type_specifier . R12 postfix_expression: simple_type_specifier . '(' expression_list ')' R13 | simple_type_specifier . '(' ')' '(' shift, and go to state Y1 '(' [reduce using rule R11 (type_specifier)] $default reduce using rule R11 (type_specifier) このままの動作だと simple-type-specifier の後の ‘(‘ で R11の規則が使われることはない.
宣言を優先させる (続き) 以下のコードを yyparse の適切な場所に入れる. if ( yystate == X1 && yychar == ‘(‘ ) { yyn = R11 + 1; goto yyreduce; } 勿論このままだと、函数スタイルのキャストは構文エラーを引き起こす。
構文解析器の状態を保存する さらに以下のように修正する if (yystate == X1 && yychar == ‘(‘ ) { if (!retry[X1] ) { // 1 回目なら save(yystate, ...); // 構文解析器の状態を保存 yyn = R11 + 1; // 宣言だとして構文解析をする goto yyreduce; }
構文解析器の状態を復元する 以下のコードを yyerror が呼び出される前の位置に挿入する if (構文解析器の状態が保存されている) { restore(&yystate, ...); // 構文解析器の状態を復元する ++retry[yystate]; // リトライカウンタをインクリメント goto yynewstate; }
1回目の構文エラーを起こすまでの字句も保存する int get_token() { int ret = ... ... if (構文解析器の状態が保存されている) { // retry するときのために字句をため込んでおく // 字句に属性を付加しているのならば、それも保存する } return ret;
例2 struct T { ... }; int a; ... T t1(); // 戻り値の型が T の函数の宣言 T t2(a); // 型 T の変数の定義. a で初期化.
函数の宣言は優先されているが... yacc/bison のレポートファイル State X2 R21 declarator: direct_declarator . R22 direct_declarator: direct_declarator . '(' parameter_declaration_clause ')' ... '(' shift, and go to state Y2 '(' [reduce using rule R21 (declarator)] $default reduce using rule R21 (declarator) このままの動作だと declarator の後の ‘(‘ で R21 の規則が使われることはない. つまり例2 の変数の定義は構文エラーになる
構文解析器の状態を保存する 以下のコード yyparse の適切な場所に追加する: if (yystate == X2 && yychar == ‘(‘ ) { if (!retry[X2] ) // 1 回目なら save(yystate, ...); // 構文解析器の状態を保存 else { yyn = R21 + 1; goto yyreduce; }
例3 double f(); void g(int a) { int(f())+a; }
int(f())+a; int の次の ‘(‘ まで読み込んだ時点で例1の X1 になる f の次の ‘(‘ まで読み込んだ時点で例2の X2 になる + を読み込んだ時点で X2 で保存した状態を捨てて X1 で保存した状態を復元するべき f を記号表に登録するのはマズいから
最後に保存した状態を捨てる 以下のコードを yyparse の適切な場所に追加する: if (yystate == X2) { switch (yychar ) { case ’(’: case ’[’: ... break; // 宣言として構文解析してエラーにならないもの default: if (最後に保存した状態はX2 のもの) { 最後に保存した状態を捨てる goto yyerrlab; }