自作コンパイラの部屋 > yacc入門 > 何を書いたら良いか

何を書いたら良いか

 yaccを利用する上では、最低限以下を記述する必要があります。

  1. YACC文法ファイル:拡張子が .yであるファイル
  2. 字句解析関数:yylex()
  3. エラー表示関数:yyerror()
  4. パーサの呼び出し:yyparse()関数の呼び出し

 もちろん、先の電卓プログラムにも上記が含まれています。以下、順に説明することにしましょう。

1.YACC文法ファイル
 電卓プログラムでは、parser.yが該当します。ここに構文規則を記述します。  YACC文法ファイルは、以下のように3つの部分からなります。見てのとおり、%%だけからなる行が、各部分の区切りになります。

YACC宣言部

%%
文法規則部

%%
追加のCプログラム

 さらに、YACC宣言部には、%{ ... %}で囲むことにより、C言語の宣言を置くことが出来ます。YACC文法ファイルの詳しい説明は後回しにして、ここではもう一度parser.yの内容を確認してみます。

parser.y
%{
#include <stdio.h>	/* C宣言部 */
#include <stdlib.h>	/* 通常は#includeが並ぶ */
#include <string.h>
#include "calc.h"
%}
%token	NUM		/* YACC宣言部 */

%left	'-' '+' '*' '/'

%%			/* 以下が文法規則部 */
input:	/* empty*/
	| input line
	;

line:	'\n'
	| exp '\n'	{ printf("%d\n", $1); }
	;

exp:	NUM
	| exp '+' exp	{ $$ = $1 + $3; }
	| exp '-' exp	{ $$ = $1 - $3; }
	| exp '*' exp	{ $$ = $1 * $3; }
	| exp '/' exp	{ $$ = $1 / $3; }
	;

%%			/* 追加のCプログラムは空 */

2.字句解析関数:yylex()
 字句解析用の関数は、yylex()という名前で作成します。これはyaccのお約束です。yylex()のプロトタイプは、

int yylex(void);
です。見てのとおりyylex()は、ファイルなどから記号や数字、名前などの一塊(トークン)を読んで、トークンの種類を数値により返します。
 一般的に、一文字の記号('+'など)は、その文字のアスキーコードを返すのが普通です。二文字以上の記号や、数値、名前(識別子)などは、YACC宣言部で定義した記号定数を返します。これも、内部的には整数値として定義されています。
 以下は電卓プログラムの字句解析関数(再掲)です。まだ説明していない内容もありますが、ここでは「だいたいどんなものか?」を説明することが目的なので、細かいところはあまり気にしないで下さい。

lex.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef UNIX
#include "parser.tab.h"
#else
#include "parser.h"
#endif

int yylex()	/* 字句解析ルーチンはyylexという関数にする */
{
	int c = getchar();
	if (isdigit(c)) {	/* 一文字が数字だったら */
		char buff[128];	/* atoi用のバッファ */
		char *p = buff;
		do {
			*p++ = c;	/* その一文字を記憶し */
			c = getchar();	/* 次の文字を読む */
		} while (isdigit(c));	/* まだ数字だったら繰り返し */
		ungetc(c, stdin);		/* 読み過ぎた文字を返しておく */
		*p = '\0';		/* 終端 */
		yylval = atoi(buff);	/* yylval変数に値を覚える */
		return NUM;		/* NUMを返す */
	}				/* 数字でない場合は */
	return c;			/* 文字そのものを返す */
}

3.エラー表示関数:yyerror()
 yaccユーザはエラー表示用の関数を用意することになっています。これはyyerrorという名前で作成します。これもyaccの約束です。yyerrorのプロトタイプは、

int yyerror(const char *s);
です。但し、yyerror()の戻り値は無視されるので、0か何かを返しておけばよいでしょう(yyerrorの戻り型はどうもはっきりしませんが、少なくともkmyaccでは、intを仮定しているようです)。
 ここで、電卓プログラムで使ったyyerror()を再掲します。非常に簡単ですが、yyerror()は大体こういう風に書くと覚えておいてください。現実のコンパイラ(特に商用コンパイラなど)では、エラーの発生位置や詳しい内容を表示する必要があると思いますが、yaccを使うと、エラーの内容を詳しく表示するのは意外に難しいのです。

int yyerror(const char *s)		/* エラー表示 */
{
	printf("ERROR: %s\n", s);
	return 0;
}

4.パーサの呼び出し:yyparse()関数の呼び出し
 yaccにより生成されるパーサ(構文解析プログラム)は、yyparse()という関数になります。このプロトタイプは以下の通りです。

int yypparse(void);
 yyparse()の戻り値は、正常の場合(コンパイルエラーがなかった場合)は0、そうでなければ1が返ります。それぞれ、YYACCEPT、YYABORTというマクロが定義されていますので、これらを利用したほうが見やすいでしょう。
 電卓プログラムの場合、yyparse()の呼び出しは、以下のようにmain()関数の中から行っています。非常にシンプルです。

int main()	/* main:yaccの呼び出し */
{
	return yyparse();
}

次:字句解析

目次
Last update: '2000年06月06日