Go to the first, previous, next, last section, table of contents.


構文解析器のC言語インターフェイス

Bison構文解析器の正体は、yyparseという名前のCの関数です。 ここでは、yyparseとほかに使う必要がある関数の間の インターフェイスの方法を示します。

構文解析器の内部では、多くの`yy'または`YY'で始まるCの識別子が 使われていることに注意してください。 本書で説明しているものを除いて、そのような識別子を 文法ファイルのアクションや追加のCプログラムの中で使うと、 問題が起きるでしょう。

構文解析器関数yyparse

構文解析を始めるには、関数yyparseを呼び出します。 この関数は、トークンを読み、アクションを実行し、最後には入力ファイルの 終わりに達するか回復不可能な構文エラーに達して、戻ります。 読み込みを打ち切ってyyparse関数から戻るような アクションを書くことも可能です。

構文解析が成功する、つまり入力ファイルの終わりに達すると、 yyparseからの戻り値が0になります。

構文解析が失敗する、つまり構文エラーが発生すると、 戻り値が1になります。

アクションの中で、次のマクロを使って、 yyparseからただちに戻れます。

YYACCEPT
成功の印である戻り値0をともなって、ただちに戻ります。
YYABORT
失敗の印である戻り値1をともなって、ただちに戻ります。

字句解析器関数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つの方法があります。

トークンの意味値

通常の再入可能でない構文解析器では、 トークンの意味値が広域変数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_linefirst_columnlast_linelast_columnの4つです。 この機能を使うと、構文解析器が著しく遅くなることに注意してください。

yyllocのデータ型は、YYLTYPEという名前を持っています。

再入可能構文解析器を呼び出す方法

純粋な、つまり再入可能な、構文解析器を生成するために、 %pure_parserをBison宣言すると、広域変数yylvalyyllocを使えなくなります (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の構造物、変数、 マクロを示します。

`$$'
現在の規則で作られるグループに対する意味値を保持する変数のように働きます。 See section アクション
`$n'
現在の規則のn番目の構成要素に対する意味値を保持する変数のように 働きます。See section アクション
`$<typealt>$'
$$に似ていますが、%union宣言で指定された共用体の中の typealtを選びます。 See section アクション中の値のデータ型
`$<typealt>n'
$nに似ていますが、%union宣言で指定された共用体の中の typealtを選びます。 See section アクション中の値のデータ型
`YYABORT;'
yyparseからただちに戻り、失敗を示します。 See section 構文解析器関数yyparse
`YYACCEPT;'
yyparseからただちに戻り、成功を示します。 See section 構文解析器関数yyparse
`YYBACKUP (token, value);'
トークンを逆シフトします。 1個の値を還元する規則の中で、先読みトークンがない場合にのみ、 このマクロが使えます。 このマクロは、トークン型がtokenで意味値がvalueのトークンを、 先読みトークンとして格納し、この規則で還元されるはずだった値を捨てます。 先読みトークンがすでにあるような、このマクロが無効な状況で このマクロを使うと、メッセージ`cannnot back up'をともなう 文法エラーが報告され、通常のエラー回復が行われます。 どちらの場合も、アクションの残りの部分は実行されません。
`YYEMPTY'
先読みトークンがない場合に、変数yycharに記憶されている値です。
`YYERROR;'
ただちに文法エラーを発生させます。この文は、構文解析器がエラーを検出したように エラー回復を始めますが、yyerrorを呼び出さず、 メッセージは表示されません。 もし、エラーメッセージを表示したければ、`YYERROR'文よりも先に、 明示的にyyerrorを呼び出してください。 See section エラーからの回復
`YYRECOVERING'
このマクロの値は、字句解析器が文法エラーからの回復中ならば1、 そうでなければ0です。 See section エラーからの回復
`yychar'
現在の先読みトークンを含んでいる変数です (再入可能構文解析器では、yyparseの局所変数です)。 先読みトークンがない場合には、この変数に YYEMPTYという値が入っています。 See section 先読みトークン
`yyclearin;'
現在の先読みトークンを捨てます。エラー規則の中で有用です。 See section エラーからの回復
`yyerrok;'
後に続く文法エラーに対して、エラーメッセージの生成を再開します。 これは、エラー規則で特に重要です。 See section エラーからの回復
`@n'
現在の規則の第n要素の、行番号と列番号を含む、 配列変数のように働きます。 次のようなメンバがあります。
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.