Bison構文解析器の正体は、yyparse
という名前のCの関数です。
ここでは、yyparse
とほかに使う必要がある関数の間の
インターフェイスの方法を示します。
構文解析器の内部では、多くの`yy'または`YY'で始まるCの識別子が 使われていることに注意してください。 本書で説明しているものを除いて、そのような識別子を 文法ファイルのアクションや追加のCプログラムの中で使うと、 問題が起きるでしょう。
yyparse
構文解析を始めるには、関数yyparse
を呼び出します。
この関数は、トークンを読み、アクションを実行し、最後には入力ファイルの
終わりに達するか回復不可能な構文エラーに達して、戻ります。
読み込みを打ち切ってyyparse
関数から戻るような
アクションを書くことも可能です。
構文解析が成功する、つまり入力ファイルの終わりに達すると、
yyparse
からの戻り値が0になります。
構文解析が失敗する、つまり構文エラーが発生すると、 戻り値が1になります。
アクションの中で、次のマクロを使って、
yyparse
からただちに戻れます。
YYACCEPT
YYABORT
yylex
字句解析器(lexical analyzer)関数yylex
は、
入力からトークンを認識し、構文解析器に返します。
Bisonはこの関数を自動的に生成しないので、
yyparse
から呼び出されるようにyylex
を書く必要があります。
関数yylex
は"lexical scanner"と呼ばれることもあります。
単純なプログラムでは、よく文法ファイルの最後でyylex
を
定義します。yylex
が別のソースファイルの中で定義する場合は、
そこでトークン型マクロ定義を使えるように準備する必要があります。
そのためには、`-d'オプションを指定してBisonを実行してください。
すると、マクロ定義がヘッダファイル`name.tab.h'に
書き込まれ、それを必要とするソースファイルにインクルードできます。
See section Bisonの実行。
yylex
を呼び出す方法
yylex
が返す値は、見つかったトークンの型に対する番号で、
入力ファイルの終わりに達した場合には0を返します。
トークンが文法規則の中で名前で参照される場合、
構文解析器ファイルの中でのその名前は、
トークン型に対する適切な番号にCのマクロとして定義されます。
したがって、yylex
は型を示すためにその名前を使用できます。
See section 記号、終端と非終端。
文法規則の中でトークンが1文字リテラルとして参照される場合には、
その文字の文字符号がトークン型に対する番号でもあります。
そこで、yylex
は、単純に文字符号を返します。
しかし、戻り値0は入力ファイルの終わりを意味するので、
ヌル文字('\0'
)の文字符号を返してはいけません。
以下に例を示します。
yylex () { ... if (c == EOF) /* ファイルの終わりか調べる。 */ return 0; ... if (c == '+' || c == '-') return c; /* `+' に対するトークン型が '+' であると仮定する。 */ ... return INT; /* トークン型を返す。 */ ... }
このようなインターフェイスは、
lex
が生成した字句解析器を、
yylex
の定義を変えずに使えるように設計されています。
文法規則が文字列リテラルトークンを使っている場合には、
yylex
がそれに対するトークン型番号を使う、
2つの方法があります。
yylex
はその記号名を他のトークンの記号名と
同様に使えます。この場合、文法ファイルの中での文字列リテラルトークンの
利用は、yylex
にまったく影響しません。
yylex
は、yytname
表の中で、
複数文字トークンを見つけられます。
トークンに対する表の添え字は、そのトークン型の番号です。
複数文字トークンはyytname
の中にダブルクォート記号で囲まれて
記憶されます。
トークンに含まれる文字はエスケープされず、
表の中の文字列にそのまま書き込まれています。
トークンを構成する文字列がtoken_buffer
に記憶されていると仮定して、
yytname
からトークンを探し出すプログラムを示します。
for (i = 0; i < YYNTOKENS; i++) { if (yytname[i] != 0 && yytname[i][0] == '"' && strncmp (yytname[i] + 1, token_buffer, strlen (token_buffer)) && yytname[i][strlen (token_buffer) + 1] == '"' && yytname[i][strlen (token_buffer) + 2] == 0) break; }
yytname
表は、%token_table
宣言をした場合にのみ生成されます。
See section Bison宣言の要約。
通常の再入可能でない構文解析器では、
トークンの意味値が広域変数yylval
に代入される必要があります。
意味値に対してただ1つのデータ型を使っている場合には、
yylval
の型もそうです。
したがって、たとえば、宣言を省略して型がint
ならば、
次のように書けます。
... yylval = value; /* 値をBisonスタックに積む。 */ return INT; /* トークン型を返す。 */ ...
複数のデータ型を使っている場合には、
%union
宣言で作られた共用体がyylval
の型になります
(see section 値型の集合)。
そこで、トークンの値を代入するには、
共用体のメンバの名前を指定する必要があります。
%union
宣言の例を示します。
%union { int intval; double val; symrec *tptr; }
すると、yylex
の中のプログラムは次のようになります。
... yylval.intval = value; /* 値をBisonスタックに積む。 */ return INT; /* トークン型を返す。 */ ...
アクションの中で`@n'機能
(see section アクション中で使える特別な機能)を
使っている場合には、トークンとグループのテキスト中の位置を
見失わないように、yylex
の中で位置情報を提供する必要があります。
関数yyparse
は、ちょうど解析されたトークンのテキスト中の位置が、
広域変数yylloc
に記憶されていると仮定します。
そこで、yylex
は、yyloc
に正しいデータを記憶する必要があります。
変数yylloc
は構造体で、アクションの中で使われる場合にのみ、
メンバを初期化する必要があります。
メンバは、first_line
、first_column
、
last_line
、last_column
の4つです。
この機能を使うと、構文解析器が著しく遅くなることに注意してください。
yylloc
のデータ型は、YYLTYPE
という名前を持っています。
純粋な、つまり再入可能な、構文解析器を生成するために、
%pure_parser
をBison宣言すると、広域変数yylval
と
yylloc
を使えなくなります
(see section 純粋(再入可能)構文解析器)。
このような構文解析器では、2つの広域変数の代わりに、
yylex
への引数として渡されるポインタを使います。
yylex
を次のように宣言し、
これらのポインタを通して情報を受け渡しする必要があります。
yylex (lvalp, llocp) YYSTYPE *lvalp; YYLTYPE *llocp; { ... *lvalp = value; /* 値をBisonスタックに積む。 */ return INT; /* トークン型を返す。 */ ... }
文法ファイルがテキスト中の位置を参照するための`@'機能を
使っていない場合は、YYLTYPE
は定義されません。
この場合、第2引数を省略し、yylex
は
1個の引数をともなって呼び出されます。
再入可能な構文解析器を使っている場合、
再入可能な方法で構文解析器に追加の引数を渡す方法があります。
そのためには、マクロYYPARSE_PARAM
を変数名として定義します。
すると、関数yyparse
は、定義された名前で、型がvoid *
の
追加の引数を受け取ります。
yyparse
を呼び出すときに、オブジェクトの番地を
void *
型にキャストして渡します。
文法のアクションは、ポインタを適切な型へのポインタへキャストし、
逆参照して、オブジェクトの内容を参照できます。
例を示します。
%{ struct parser_control { int nastiness; int randomness; }; #define YYPARSE_PARAM parm %}
次のように構文解析器を呼び出します。
struct parser_control
{
int nastiness;
int randomness;
};
...
{
struct parser_control foo;
... /* foo
に正しいデータを記憶 */
value = yyparse ((void *) &foo);
...
}
文法アクションの中では、データを参照するために次のような式を使います。
((struct parser_control *) parm)->randomness
yylex
に追加の引数を渡したい場合には、
YYPARSE_PARAM
と同様に、マクロYYLEX_PARAM
を定義します。
例を示します。
%{ struct parser_control { int nastiness; int randomness; }; #define YYPARSE_PARAM parm #define YYLEX_PARAM parm %}
そして、yylex
が追加の引数、parm
の値を受け取るように、
yylex
を定義する必要があります
(型YYLTYPE
のどの引数が渡されるかに応じて、
引数の合計が2個または3個になります)。
引数を正しいオブジェクト型として宣言できます。すなわちvoid *
として
宣言し、上記の番地を参照できます。
YYPARSE_PARAM
を使わずに、`%pure_parser'を使って、
再入可能な構文解析器を生成することも可能です。
その場合、引数をつけずにyyparse
を呼び出すべきです。
yyerror
Bison構文解析器は、文法規則に適合しないトークンを読むたびに、
構文解析エラー(parse error)すなわち文法エラー(syntax error)を
検出します。文法中のアクションは、マクロYYERROR
を使って、
明示的にエラーを示せます
(see section アクション中で使える特別な機能)。
Bison構文解析器は、yyerror
という名前の関数を使って、
エラーを報告するようになっています。
事前に用意が必要な
この関数は、文法エラーが発生するたびに、1個の引数をともなって、
yyparse
から呼び出されます。
構文解析エラーに対して、引数の文字列は通常"parse error"
です。
Bison定義部(see section Bison宣言部)で、
マクロYYERROR_VERBOSE
を定義すると、
"parse error"
の代わりに、
エラーを詳細に報告する文字列が用意されます。
マクロYYERROR_VERBOSE
の定義はなんでもかまいません。
構文解析器は、もう1種類のエラーであるスタックオーバーフローを検出する
可能性があります。これは、入力がきわめて深い入れ子からなっていると
起こることがあります。Bison構文解析器は自動的にスタックの限界を大きく拡張し
ているので、スタックオーバーフローはめったに起きません。
しかし、もしスタックオーバーフローが起きれば、
"parser stack overflow"
という
文字列の引数をともなって、yyerror
が呼び出されます。
単純なプログラムでは、次の例のようにyyerror
を定義できます。
yyerror (s) char *s; { fprintf (stderr, "%s\n", s); }
yyerror
から戻った後、yyparse
は、
適切なエラー回復文法規則(see section エラーからの回復)があれば、
エラーからの回復を試みます。
もし、回復が不可能ならば、yyparse
は即座に1を返します。
変数yynerrs
には、それまでに出くわした文法エラーの数が記憶されています。
通常、この変数は広域変数です。
しかし、再入可能な構文解析器(see section 純粋(再入可能)構文解析器)
を生成した場合には、アクションからのみ参照可能な局所変数になります。
ここの表では、アクション中で有用な、Bisonの構造物、変数、 マクロを示します。
$$
に似ていますが、%union
宣言で指定された共用体の中の
typealtを選びます。
See section アクション中の値のデータ型。
$n
に似ていますが、%union
宣言で指定された共用体の中の
typealtを選びます。
See section アクション中の値のデータ型。
yyparse
からただちに戻り、失敗を示します。
See section 構文解析器関数yyparse
。
yyparse
からただちに戻り、成功を示します。
See section 構文解析器関数yyparse
。
yychar
に記憶されている値です。
yyerror
を呼び出さず、
メッセージは表示されません。
もし、エラーメッセージを表示したければ、`YYERROR'文よりも先に、
明示的にyyerror
を呼び出してください。
See section エラーからの回復。
yyparse
の局所変数です)。
先読みトークンがない場合には、この変数に
YYEMPTY
という値が入っています。
See section 先読みトークン。
struct { int first_line, last_line; int first_column, last_column; };たとえば、第3要素の開始行番号を知るには、 `@3.first_line'とします。 この構造体のメンバを有効な情報にするためには、
yylex
がそれぞれのトークンに対して、
情報を提供する必要があります。
一部分のメンバだけが必要ならば、
yylex
はそのメンバの値を設定するだけでかまいません。
(13)
この機能を使うと、字句解析器が著しく遅くなります。
Go to the first, previous, next, last section, table of contents.