渕野 昌 (Sakae' Fuchino)
解説% flex format3.lとして flex にかける.この結果 lex.yy.c という名前の C プログラムが出力される. このファイルを今度は
% cc -o format lex.yy.cと C コンパイラでコンパイルすると,format という名前の実行プログラムが出力される. このプログラムは標準入力から入力された C ソースファイルを整形して, その結果を標準出力に出力するようになっている. たとえば,
for(;;){ for(;;){ for(;;){ for(;;){ for(;;){ x++; } } } } }というべた書きの C ソースプログラムが,testing-indent.c という名前でセーブされているとして, これを testing-indented.c という名前の整形されたプログラムファイルに変換するには,
% cat testing-indent.c | ./format > testing-indented.cとして先程作成したツール format を呼びだす. この結生成される testing-indented.c は
for(;;){ for(;;){ for(;;){ for(;;){ for(;;){ x++; } } } } }と整形されたものになっている.
% bison scalc.yとして scalc.tab.c を生成する. 次に
% flex scalc.lとして lex.yy.c というファイルを生成する.このファイルは scalc.y の終り書いた
#include ".../lex.yy.c"によって scalc.tab.c から呼びだされる.ただし,ここで ... にはもともとの実験のホームページのディレクトリー名が書かれているので, ファイルをコピーして自分のところで実行ファイルを生成するためには, ... を各人の path 名に変更する必要がある. 終りに,
% cc scalc.tab.c -ll -o scalcとして scalc.tab.c をコンパイルすると,実行ファイル scalc が得られる.
scalc は算術表現を標準入力から得て結果を標準出力に出すようになっている. コンソールが標準入力の場合には, ".RET"か"RET"を空行でタイプすると,プログラムからぬけられるようになっている.
% ./scalc 50+1 The result is: 51.000000 34/23 The result is: 1.478261 ((20+45)/70+1.34)/34.4678 The result is: 0.065817 . %適当でない表現を入力するとエラーメッセージを出して止まる.
% ./scalc 50+1) parse error %もちろん入出力をリダイレクトして他のプログラムと組み合せて使うことも可能である:
% echo "(30.14+173)*3.1415927+25" | ./scalc The result is: 663.183141 %
scalc のソースプログラムを見てみることにする.scalc.l から, 字句解析の関数 yylex() が生成され,scalc.y から構文解析の関数 yyparse() が生成される.算術表現の計算と表示は,yyparse() の side effect の形で 実現されている.yylex() は yyparse() から subfunction として呼びだされる.
まず,scalc.l を見てみる.lex/flex のプログラムは,
C の宣言文 %% 字句解析の規則 %% ユーザー・コードという形をしている,字句解析の規則は,拡張された regular expression と, それにマッチした場合の動作の記述からなる. lex/flex で出力される C 言語のプログラムでは, C の宣言文の部分とユーザーコードの部分が,それぞれプログラムの頭と終りに付加され, 字句解析の規則に対応する yylex とそれに付随した subfunctions などが, これらの間に書きこまれる. 我々の例では,
%% "+" return (ADDOP); "-" return (SUBOP); "*" return (MULOP); "/" return (DIVOP); "(" return (LPAR); ")" return (RPAR); "." return (OWARI); [0-9]+\.[0-9]*|[0-9]+ { sscanf (yytext, "%lf", &yylval); return (NUMBER); } [ \t] ; ^\n return (OWARI); \n return (NL); . return (yytext[0]); %%となっていて,C の宣言文とユーザーコードの部分は空である. 字句解析の規則の記述は各行が,
正規表現 空白 C 言語による動作の記述となっている. "ADDOP", "SUBOP" etc. は scalc.y で定義される enumeration type の定数で,それぞれ 加法演算子,減法演算子 に対応する token である.定数にマッチした場合には, "NUMBER" という token それの attribute としての浮動点小数値が返されるが,この attribute は,scalc.y で定義される YYSTYPE という型の yylval という global variable に格納されて構文解析に渡される.
次に scalc.y のソースコードを見てみる.yacc/bison のソースコードも, lex/flex と似た,
%{ C の宣言文 %} Bison の宣言文 %% 文法規則 %% ユーザー・コードという構成になっている.
%{ #define YYSTYPE double #include <stdio.h> #include <math.h> %} %token NL %token NUMBER %token LPAR %token RPAR %token OWARI %left ADDOP SUBOP %left MULOP DIVOP %left NEG %% s : list ; list : /* empty */ | list expr NL { printf ("The result is: %lf\n", $2);} | list OWARI { return;} ; expr : expr ADDOP expr {$$ = $1 + $3;} | expr SUBOP expr {$$ = $1 - $3;} | expr MULOP expr {$$ = $1 * $3;} | expr DIVOP expr {$$ = $1 / $3;} | SUBOP expr %prec NEG {$$ = -$2;} | LPAR expr RPAR {$$ = $2;} | NUMBER {$$ = $1;} ; %% yyerror(s) char *s; { printf ("%s\n",s);} main() {yyparse();} #include "/var/home2/fuchino/public_html/experimentIII-1998/lex.yy.c"ここで,YYSTYPE は前に述べた,構文解析の各段階で受け渡される semantic value の型で,我々の例では,これは double 型の浮動点小数となっている. コンパイラや次の課題で考察するインタプレータでは, これは,構文木の型として定義されることになる. %token と %left, %right でトークンの宣言がされている. %left, %right はそれぞれ左結合則,右結合則を満たす演算記号の宣言である.
次の %% の後で,文法規則が定義されている. Backus Nauer Form に準ずる書きかたになっていることが分ると思う. { と } で囲まれた部分は,構文にマッチしたときの C であらわされる動作の規定で, たとえば,
expr : expr ADDOP expr {$$ = $1 + $3;} . . .となっていて,expr ADDOP expr にマッチしたときには,$$ であらわされる左辺の expr の semantic value を, $1 であらわされる右辺の最初の expr にマッチする表現の semantic value と, $2 であらわされている右辺の二つめに現れる expr にマッチする表現の semantic value の和とする,という動作の規定となっている.
二番目の %% の後には,scalc の main() 関数と,エラーが生じた場合に呼ばれる yyerror() の定義がされている.最後に flex によって生成された, lex.yy.c が読み込まれている. ここで簡易電卓プログラムのときと同様に,
#include ".../lex.yy.c"の行の path 名をあらかじめ各人のものに変更する必要があることに注意.
while(算術式){ 実行文(複数) }のみが用意されている. これの解釈は,算術式の評価の結果が正の実数となっている間 '{' と '}' の間の実行文の順次実行をくりかえす.while 文の入れ子は可能である.
example.x: printstr("** This is a test **\n\n"); x1 = 5; x0 = 1; while (0 < x1){ x1 = x1 - 1; x0 = x0 * 2; x2 = 5 - x1; while (0 < x2 ){ printstr("*"); x2 = x2 - 1; } x2 = x1 + 2; while (0 < x2 ){ printstr(" "); x2 = x2 - 1; } printvar(x0); printstr("\n"); }に,
% itpr example.xとして実行すると,
** This is a test ** * 2.000000 ** 4.000000 *** 8.000000 **** 16.000000 ***** 32.000000 %という結果が得られる.