この節では、yacc の高度な機能についていくつか説明します。
error と accept の構文解析アクションは、マクロ YYACCEPT
と YYERROR
を使用してアクションのなかでシミュレーションを行うことができます。YYACCEPT
マクロを使用すると、yyparse() は値 0 を返します。YYERROR
を使用すると、パーサーは現在の入力シンボルが構文エラーであるかのように動作します。つまり、yyerror() を呼び出して、エラー回復を実行します。
これらのメカニズムは、複数のエンドマーカーを持つパーサーのシミュレーションや、コンテキスト文法の検査に使用できます。
あるアクションは、左側に記述された規則のアクションによって返された値を参照できます。メカニズムは通常のアクションと同じで、$ の後に数字が 1 つ続きます。
sent : adj noun verb adj noun { look at the sentence ... } ; adj : THE { $$ = THE; } | YOUNG { $$ = YOUNG; } ... ; noun : DOG { $$ = DOG; } | CRONE { if ( $0 = = YOUNG ) { (void) printf( "what?¥n" ); } $$ = CRONE; } ; ...
この例では、その数字は 0 または 負の値にできます。ワード CRONE の後のアクションでは、シフトされた前のトークンが YOUNG かどうかを検査します。ただし、この検査は、入力内のシンボル noun の前のシンボルについて詳しい情報が得られている場合にのみ可能です。場合によっては、このメカニズムによって多くの問題が回避できます。特に、他の標準構造から結合をいくつか除外するときに役立ちます。
デフォルトでは、アクションと字句アナライザによって返される値は整数です。yacc は、構造体などの他の型の値もサポートできます。また、生成されるパーサーの型を厳密に検査するために、yacc は型を監視して、適切な共用体のメンバー名を挿入します。yacc の値スタックは、必要なさまざまな型の値の共用体として宣言されます。共用体を宣言したら、値を持った各トークンと非終端記号に共用体のメンバー名を関連付けます。$$ または $n 構成によって値が参照されると、yacc は、不要な変換が起きないように、適切な共用体の名前を自動的に挿入します。
これには 3 つのメカニズムが用意されています。1 つ目は、共用体を定義する方法です。他のサブルーチン (特に、字句アナライザ) は共用体のメンバー名を知る必要があるので、この定義はユーザーが行わなければなりません。2 つ目は、共用体のメンバー名をトークンと非終端記号に関連づける方法です。3 つ目は、yacc が容易に型を判定できないごく少数の値の型を表すメカニズムです。
共用体を宣言するには、以下の記述を宣言セクションに加えます。
%union { 共用体の本体 }
これにより、yacc の値スタックと外部変数 yylval および yyval は、この共用体と同じ型で宣言されます。-d オプションを付けて yacc を呼び出した場合には、共用体の宣言は y.tab.h ファイルに YYSTYPE としてコピーされます。
YYSTYPE を定義したら、共用体のメンバー名を各種の終端および非終端の名前に関連づける必要があります。以下の構文を使用して、共用体のメンバー名を指定します。
<name>
%token、%left、%right、%nonassoc のいずれかのキーワードの後に上記の指定が続いている場合は、共用体の名前は記述されているトークンに関連付けられます。
したがって、以下のように記述すると、これら 2 つのトークンによって返された値に対する参照には、すべて共用体のメンバー名 optype がタグとして付けられます。
%left <optype> '+' '-'
もう 1 つのキーワード %type は、共用体のメンバー名を非終端記号に関連づける際に使用します。たとえば、以下の規則を使用すれば、共用体メンバー nodetype を非終端記号 expr と stat に関連づけることができます。
%type <nodetype> expr stat
これらのメカニズムでは対応しきれないケースもいくつかあります。規則のなかにアクションがある場合には、そのアクションによって返される値には型がありません。同様に、左側のコンテキスト値 (たとえば、$0 など) を参照した場合は、yacc はその型を容易に知ることができなくなります。この場合には、最初の $ の直後の < と > の間に共用体のメンバー名を挿入することによって、型を強制的に参照できます。その例を以下に示します。
rule : aaa { $<intval>$ = 3; } bbb { fun( $<intval>2, $<other>0 ); } ;
この構文には特に説明することはありませんが、このような状況は頻繁に発生します。
任意値型のサポート機能は、実際に使用されるまでは起動されません。特に %type の使用によって、これらのメカニズムは有効になります。これらの機能を使用するときには、非常に厳密な検査が行われます。
たとえば、型が定義されていないものを $n または $$ を使用して参照すると、型の診断が行われます。これらの機能を起動しない場合は、yacc の値スタックは int
を保持するために使用されます。
この節では、yacc 仕様としての yacc 入力構文について説明します。コンテキスト依存性などについては考慮していません。yacc では LALR(1) 文法を使用することもできますが、yacc 入力仕様言語は、LR(2) 文法として指定されています。この文法では、規則内のアクションの直後に識別子があると、問題が発生します。
この識別子の後にコロンが続いている場合には、次の規則の開始を表しています。コロンが続いていない場合は、現在の規則の続きであり、その規則のなかに偶然アクションが埋め込まれていただけに過ぎません。字句アナライザは、識別子を認識した後にその先を読み取り、次のトークン (空白文字、復帰改行、コメントなどは読み飛ばします) がコロンかどうかを判定します。コロンの場合には、トークン C_IDENTIFIER を返します。
コロンでない場合には、IDENTIFIER を返します。リテラル (引用符で囲んだ文字列) も C_IDENTIFIER としてではなく、必ず IDENTIFIER として返されます。
/* yacc に対する入力の文法 */ /* 基本エントリ */ %token IDENTIFIER /* 識別子とリテラルの取り込み */ %token C_IDENTIFIER /* 識別子 (リテラル以外) */ /* 後に a : が続く */ %token NUMBER /* [0-9]+ */ /* 予約語: %type=>TYPE %left=>LEFT、等 */ %token LEFT RIGHT NONASSOC TOKEN PREC TYPE START UNION %token MARK /* %% 記号 */ %token LCURL /* %{ 記号 */ %token RCURL /* %) 記号 */ /* 自分自身を表す ASCII 文字リテラル*/ %token spec t %% spec : defs MARK rules tail ; tail : MARK { In this action,read in the rest of the file } | /* 空: 2 番目の MARK は省略可 */ ; defs : /* 空 */ | defs def ; def : START IDENTIFIER | UNION { Copy union definition to output } | LCURL { Copy C code to output file } RCURL | rword tag nlist ; rword : TOKEN | LEFT | RIGHT | NONASSOC | TYPE ; tag : /* 空: 共用体タグは省略可 */ | '<' IDENTIFIER '>' ; nlist : nmno | nlist nmno | nlist ',' nmno ; nmno : IDENTIFIER /* 注: リテラル % type では無効 */ | IDENTIFIER NUMBER /* 注: % type では無効 */ ; /* 規則セクション */ rules : C_IDENTIFIER rbody prec | rules rule ; rule : C_IDENTIFIER rbody prec | '|' rbody prec ; rbody : /* 空 */ | rbody IDENTIFIER | rbody act ; act : '{' { Copy action translate $$ etc. } '}' ; prec : /* 空 */ | PREC IDENTIFIER | PREC IDENTIFIER act | prec ';' ;