lex プログラムおよび yacc プログラムの例
この項では、lex コマンドと yacc コマンドのプログラム例を示します。
これらのプログラム例は、一緒に、簡単な追加、減算、乗算、 および割り算の演算を行う卓上計算機プログラムを作成します。 この電卓プログラムは、変数 (それぞれ単一の小文字で指定されている) に値を割り当てて、その変数を計算で使用することができます。 lex プログラムと yacc プログラムの例の入ったファイルは、次のとおりです。
| ファイル | 内容 |
|---|---|
| calc.lex | 字句解析規則を定義する lex コマンド仕様ファイルを指定します。 |
| calc.yacc | 構文解析規則を定義する yacc コマンド文法ファイルを指定し、lex コマンドによって作成された yylex サブルーチンを、入力を準備するために呼び出します。 |
以下の記述では、calc.lex および calc.yacc プログラム例が、現行ディレクトリーに置かれていることを前提としています。
サンプル・プログラムのコンパイル
卓上計算機プログラム例を作成する手順は、次のとおりです。
- -d オプショナル・フラグ (C 言語ソース・コードに加えて、使用されるトークンを定義するファイルを作成するように、yacc コマンドに指示する) を使用して
yacc 文法ファイルを処理します。
yacc -d calc.yacc - ls コマンドを使用して、
以下のファイルが作成されたことを検証します。
- y.tab.c
- yacc コマンドがパーサーのために作成した C 言語のソース・ファイル
- y.tab.h
- パーサーが使用するトークンの定義ステートメントの入ったヘッダー・ファイル
- lex 仕様ファイルを処理します。
lex calc.lex - ls コマンドを使用して、
以下のファイルが作成されたことを検証します。
- lex.yy.c
- lex コマンドが字句解析プログラムのために作成した C 言語のソース・ファイル
- C 言語ソース・ファイルをコンパイルおよびリンクします。
cc y.tab.c lex.yy.c - ls コマンドを使用して、
以下のファイルが作成されたことを検証します。
- y.tab.o
- y.tab.c ソース・ファイルのオブジェクト・ファイル
- lex.yy.o
- lex.yy.c ソース・ファイルのオブジェクト・ファイル
- a.out
- 実行可能プログラム・ファイル
a.out ファイルからプログラムを直接実行するために、
次のように入力します。
$ a.outOR
次の例のようにさらに分かりやすい名前をプログラムに付けてファイルに移動し、
それを実行するために、次のように入力します。
$ mv a.out calculate
$ calculateどちらの場合も、ユーザーがプログラムを開始した後で、
カーソルが $ (コマンド・プロンプト) の下の行に移動します。 次に、計算機上で行うように、数字と演算子を入力します。 Enter キーを押すと、プログラムが演算の結果を表示します。 次のように変数に値を割り当てた後、カーソルは次の行に移動します。
m=4 <enter>
_後続の計算でその変数を使用すると、
そこには値が割り当てられています。
m+5 <enter>
9
_パーサーのソース・コード
次の例は calc.yacc ファイルの内容を示しています。 このファイルには、yacc 文法ファイルの 3 つのセクション (宣言、規則、およびプログラム) すべてにエントリーがあります。
%{
#include<stdio.h>
int regs[26];
int base;
%}
%start list
%union { int a; }
%token DIGIT LETTER
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS /*supplies precedence for unary minus */
%% /* beginning of rules section */
list: /*empty */
|
list stat '¥n'
|
list error '¥n'
{
yyerrok;
}
;
stat: expr
{
printf("%d¥n",$1);
}
|
LETTER '=' expr
{
regs[$1.a] = $3.a;
}
;
expr: '(' expr ')'
{
$$ = $2;
}
|
expr '*' expr
{
$$.a = $1.a * $3.a;
}
|
expr '/' expr
{
$$.a = $1.a / $3.a;
}
|
expr '%' expr
{
$$.a = $1.a % $3.a;
}
|
expr '+' expr
{
$$.a = $1.a + $3.a;
}
|
expr '-' expr
{
$$.a = $1.a - $3.a;
}
|
expr '&' expr
{
$$.a = $1.a & $3.a;
}
|
expr '|' expr
{
$$.a = $1.a | $3.a;
}
|
'-' expr %prec UMINUS
{
$$.a = -$2.a;
}
|
LETTER
{
$$.a = regs[$1.a];
}
|
number
;
number: DIGIT
{
$$ = $1;
base = ($1.a==0) ? 8 : 10;
} |
number DIGIT
{
$$.a = base * $1.a + $2.a;
}
;
%%
main()
{
return(yyparse());
}
yyerror(s)
char *s;
{
fprintf(stderr, "%s¥n",s);
}
yywrap()
{
return(1);
}このファイルには、次のセクションが入っています。
- 宣言セクション。 このセクションには、次のエントリーが入っています。
- 標準 I/O ヘッダー・ファイルを組み込む
- グローバル変数を定義する
- list 規則を処理の開始点として定義する
- パーサーで使用するトークンを定義する
- 演算子とその優先度を定義する
- 規則セクション。 規則セクションは、入力ストリームの構文解析の規則を定義します。
- %start - 入力データ全体が stat と一致しなければならないことを 指定します。
- %union - デフォルトでは、アクションと字句解析プログラムによって戻される値は整数です。 yacc は、構造体を含めて、ほかの型の値をサポートすることもできます。 さらに、生成されるパーサーに精密な型の検査が行われるように、yacc は型のトラックを保持し、 適切な共用体メンバー名を挿入します。 yacc 値のスタックは、望ましいさまざまな型の値の共用体であると 宣言されます。 ユーザーは共用体を宣言し、共用体メンバー名を各トークンおよび値のある非終端記号に関連付けます。 値が $$ または $n の構造で参照されると、yacc は 適切な共用体名を自動的に挿入するため、 不要な変換は起こりません。
- %type - %union 宣言のメンバーを活用し、 文法の各部分に関連付けられた値に個々の型を与えます。
- %toksn - lex ツール由来のトークンとその型をリストします。
- プログラム・セクション。 プログラム・セクションには、以下のサブルーチンが入っています。 これらのサブルーチンはこのファイルに組み込まれているので、
このファイルの処理時に yacc ライブラリーを使用する必要はありません。
サブルーチン 説明 main yyparse サブルーチンを呼び出してプログラムを開始する必須のメインプログラム。 yyerror(s) このエラー処理サブルーチンは、構文エラー・メッセージを印刷するだけです。 yywrap 入力の終わりが発生すると値 1 を戻す、wrap-up サブルーチン。
字句解析プログラムのソース・コード
このファイルには、y.tab.h ファイルの場合と同様、
標準入出力用のステートメントが入っています。 yacc コマンドで -d フラグを使用した場合、
yacc プログラムはそのファイルを
yacc 文法ファイル情報から生成します。 y.tab.h ファイルには、
パーサー・プログラムが使用するトークンの定義が入っています。
さらに、calc.lex ファイル
にはこれらのトークンを入力ストリームから生成するための規則が入っています。 次に示すのは、calc.lex ファイルの内容です。
%{
#include <stdio.h>
#include "y.tab.h"
int c;
%}
%%
" " ;
[a-z] {
c = yytext[0];
yylval.a = c - 'a';
return(LETTER);
}
[0-9] {
c = yytext[0];
yylval.a = c - '0';
return(DIGIT);
}
[^a-z0-9¥b] {
c = yytext[0];
return(c);
}
%%