計算機科学実験及演習3 (ソフトウェア) 大本 義正 馬谷 誠二 1. 概要 実験内容 Tiny Cコンパイラの作成 yacc (bison) と lex (flex) を用いて作成 ターゲット言語はIAのアセンブリ言語 一人一つ作成 上位互換であれば言語を拡張してもよい 教科書・資料と異なる設計をしてもよい この実験の目的・意義 プログラミングスキルの向上 これまで学んだプログラミング技術の応用 ある程度大きいプログラムの作成 • • • • 字句解析 (lexファイル):50行 構文解析・構文木生成(yaccファイル):400行 意味解析・識別子操作:200行 コード生成:550行 コンパイラ作成を通してプログラムに対する 理解を深める yacc/lexをマスターすることは重要でない • 解らないことはTA・教員にどんどん訊く Tiny C C言語のサブセット 型はint型のみ 制御構造は最低限のもの(if, while)だけ int fact(int n) { if (n == 1) return 1; else return n * fact(n-1); } • 追加してもかまわない(for, do-while, ...) Tiny Cプログラムのコンパイル ソースプログラム ソースファイル(*.tc) アセンブリ プログラム アセンブリファイル(*.asm) オブジェクト プログラム オブジェクトファイル(*.o) 実行形式 プログラム TinyCコンパイラ アセンブラ (nasm) リンカ (ld, gcc) 他の オブジェクトファイル ライブラリファイル コンパイル例 fact.tc main.c (このファイルはgccでコンパイル) int fact(int n) { if (n == 1) return 1; else return n * fact(n-1); } #include <stdio.h> main() { printf("%d\n", fact(10)); } gccと同一の stack layout / calling convention コンパイル例(続き) % tcc fact.tc ; fact.asm を生成 % nasm –f elf fact.asm ; fact.o を生成 % gcc –o fact main.c fact.o % ./fact ソースファイル(*.c) 3628800 ソースプログラム アセンブリファイル(*.asm) アセンブリ プログラム オブジェクトファイル(*.o) オブジェクト プログラム TinyCコンパイラ (tcc) アセンブラ (nasm) リンカ (ld, gcc) 実行形式 プログラム 他のオブジェクトファイル ライブラリファイル Tiny Cコンパイラの出力 (テキストファイル) fact.asm: GLOBAL fact fact push ebp mov ebp, esp cmp dword [8+ebp], 1 jne L2 mov eax, 1 jmp L1 L2 mov eax, [8+ebp] sub eax, 1 push eax call fact add esp, 4 imul eax, [8+ebp] L1 pop ebp ret 実験の進め方 実験資料の課題1~8を順に解く 課題1, 2はレポートに書く必要なし 課題5は選択自由 課題7, 8が本実験の主要部分 • 費やす時間もこの部分が大きい 資料のコンパイラは教科書に沿った設計 • 教科書もよく読むこと 各課題の解答となるソースは残しておく • 上書きすると元に戻せない 成績評価 レポート3回 中間レポート2回 (課題3〜6) 最終レポート (課題7, 8) メールでPDFを提出 • 課題毎にソースを残し,パス名をレポートに明記 • プログラムの解説 • 感想・要望 報告会 サンプルプログラムのコンパイルと実行 成績 コンパイラの完成度 レポートの出来(分かりやすい説明) その他の注意 出席を重視する 積極的にTA・教員に訊く 予習をしっかりと 予め資料を読み, 理解し, 方針を立てておく 演習時間は「実装・デバッグの時間」 webページ 止むを得ない場合はできるだけ事前に連絡する http://ecs.kuis.kyoto-u.ac.jp/isle/le3b/ 教員・TA宛メールアドレス [email protected] 2. 字句解析部の作成 lex (flex) 字句解析部 (Cのプログラム)を作成 lexへの入力:lexファイル • 字句構造を正規表現で定義 lexの出力:Cファイル • 関数 yylex() を定義 • 生成される字句解析プログラムの本体 • 入力文字列から字句要素を切り出す “a = b+c;” → “ a”, “=”, “b”, “+”, “c”, “;” • 字句要素はトークンとして構文解析部に渡される lexファイルの例 Cのコード.字句解析プログラムの 先頭にそのまま挿入される %option noyywrap %{ #include "calc.tab.h" %} digit [0-9] %% 数字を表すマクロ {digit}+ { digitの定義 yylval = atoi(yytext); cf. #define return INTEGER; } "-"|"+"|\n return *yytext; [ \t] ; /* スペース,タブは無視 */ . yyerror("Error: invalid character"); %% 定義 セクション ルール セクション Cコード セクション ルールセクションの基本構造 pattern1 action1 pattern2 action2 … pattern: 字句構造の正規表現 action: 字句要素を切り出したときに実行されるコード • Cで記述 • 普通はトークンをyaccに渡すコードを記述 • 字句要素:切り出した文字列 • トークン:字句要素を構文解析に適した形に変換したデータ lexはルールから関数yylex()を生成 入力からpatternに一致する最長の文字列を探す 見つかれば,対応するactionを実行 ルールセクション例 yaccにおけるトークン • トークンの種類:識別子,整数定数… yylex()の返値 • トークンの値:識別子の名前,整数値… 大域変数yylvalの値 トークンの値 切り出した字句要素 数字からなる1文字以上の文字列 [0-9]+ { yylval = atoi(yytext); return INTEGER; } “-”|“+”|”\n” return *yytext; 字句解析の中断 [ \t] ; /* スペース,タブは無視 */ & - or + or 改行 トークンの種類を返す . yyerror("Error: invalid character"); lexが出力するCファイルの内容 #include "calc.tab.h" … int yylex() { … yylval = atoi(yytext); return INTEGER; … } /* Cコードセクションのコード */ … 3. 構文解析部の作成 yacc (bison) 構文解析部(Cプログラム)を生成 yaccへの入力:yaccファイル • BNFに似た記法で生成規則を記述 yaccの出力:Cファイルとヘッダファイル • 関数yyparse() の定義 • 生成される構文解析プログラムの本体 • yylexを用いてトークンを取得し,構文をチェック • 抽象構文木を生成 yaccファイルの例 %token INTEGER %% program: /* empty */ | program expr '\n' ; expr: INTEGER | expr '+' INTEGER | expr '-' INTEGER ; %% int yyerror(char *s) { … } main() { yyparse(); } 定義 セクション { printf("%d\n", $2); } { $$ = $1; } { $$ = $1 + $3; } { $$ = $1 - $3; } ルール セクション Cコード セクション 定義セクション %token INTEGER 終端記号 INTEGER の定義 自動的に適当な整数値が割り振られる • yaccが出力するヘッダファイルで定義 • ASCIIコードは避けられる ASCII文字はそのまま終端記号になる ’a’ ’b’ ’=’ ’<’ ’\n’ などなど 定義セクション(続き) %{ … %} Cコードを記述可能 • yaccの出力ファイルの先頭にそのまま貼られる %union yylval,終端記号,非終端記号の型として複数の型 を許す • %union宣言しなければ,int型 型を指定する方法 • yylval: yylval.(%unionのメンバ名) • 非終端記号: %type <(メンバ名)> (記号) • 終端記号: %token <(メンバ名)> (記号) yaccファイルの例 %token INTEGER %% program: /* empty */ | program expr '\n' ; expr: INTEGER | expr '+' INTEGER | expr '-' INTEGER ; %% int yyerror(char *s) { … } main() { yyparse(); } { printf("%d\n", $2); } { $$ = $1; } { $$ = $1 + $3; } { $$ = $1 - $3; } ルール セクション ルールセクション nonterminal: rule1 action1 | rule2 action2 … ; nonterminal: BNFの左辺に相当 rule: BNFの右辺に相当,構文規則を規定 action: nonterminalを還元するときに実行されるCコード • 構文木生成コード • インタプリタのコード など yaccはルールから関数 yyparse() を生成 LALR(1)構文解析 トークンを一つ得るために yylex() を一回呼出す • yylex()の返値 (=トークンの種類) • yylvalの値 (=トークンの値) ルールセクション:rule トークンの種類 = 終端記号 ε program: 終端記号 /* empty */ | program expr '\n' { printf("%d\n", $2); } ; expr: INTEGER { $$ = $1; } | expr '+' INTEGER { $$ = $1 + $3; } | expr '-' INTEGER { $$ = $1 - $3; } 終端記号 ; 非終端記号 lexファイル %{ #include "calc.tab.h" %} %% {digit}+ { yylval = atoi(yytext); return INTEGER; } "-"|"+"|\n return *yytext; yaccファイル %token INTEGER %% program: /* empty */ | program expr '\n' ; expr: INTEGER | expr '+' INTEGER | expr '-' INTEGER ; { printf("%d\n", $2); } { $$ = $1; } { $$ = $1 + $3; } { $$ = $1 - $3; } ルールセクション:action 終端記号・非終端記号は値を持つ • 終端記号の値 = トークンの値 • $n (n=1,2,3…) でアクセス可能 program: /* empty */ | program expr '\n' 還元された ; exprの値 expr: INTEGER | expr '+' INTEGER | expr '-' INTEGER ; { printf("%d\n", $2); } INTEGERの値 = yylvalの値 { $$ = $1; } { $$ = $1 + $3; } { $$ = $1 - $3; } exprの値 値スタックと$$,$n yaccが持つ値スタックの要素へのアクセス expr: INTEGER { $$ = $1; } | expr '+' INTEGER { $$ = $1 + $3; } | expr '-' INTEGER { $$ = $1 - $3; } ; 入力:7 - 3 トークンの値 yylval: ・7 - 3 INTEGER ・- 3 expr ・– 3 expr '–' ・3 expr '–' INTEGER ・ expr ・ 還元 37 シフト 還元 シフト シフト トークンの種類 yylex()の返値: INTEGER '-' yylvalの値 をpush pop して push 3 $3 7('-') $2 74 $1 値スタック yaccが出力するCファイル #define INTEGER 257 … yyparse() { … } int yyerror(char *s) { … } main() { yyparse(); } Cコードセクションのコードは別ファイルに記 述しても構わない 4. 補足事項 makeのススメ 実行ファイル作成手順 1. 2. 3. bisonにより *.tab.c と *.tab.h を生成 flexにより lex.yy.c gccにより*.tab.c, lex.yy.c 他必要なソースをコン パイル・リンク yaccファイルを変更したら,flexを実行しなければならない hoge.tab.c yaccファイル (hoge.y) bison lexファイル (hoge.l) hoge.tab.h flex lex.yy.c Makefileの例 タブ文字 YACC=bison YFLAGS= -d LEX=flex LFLAGS= OBJS = bar.tab.o lex.yy.o all: a.out bar.tab.c: bar.y $(YACC) $(YFLAGS) bar.y lex.yy.c: foo.l bar.tab.h $(LEX) $(LFLAGS) foo.l a.out: $(OBJS) $(CC) –o $@ $(OBJS) OBJS のリストの並び順によっては以下も必要 bar.tab.h: bar.y $(YACC) $(YFLAGS) bar.y 構文木 抽象構文木 Tiny Cプログラムの別の表現 優先順位,結合性,elseの結びつきが容易に 読み取れる • 元のTiny Cプログラムの場合,(複雑な)生成規 則と(複雑な)構文解析が必要 + + a * b + (c – d) + e - * a e b c d 構文木の型 木のノードの型 nd typedef union nd { struct { int op; } n; struct tp tp; struct tk tk; struct c c; } ノードの種類を示す (tp, tk, cに共通のメンバ) タプル=子要素を持つノード 識別子ノード 整数定数ノード tree型 = ndへのポインタ型 (nd *) 構造体のメモリ割当て #include <stdlib.h> typedef struct st { … } *st_p; p struct st st_p p; /* ポインタ変数のメモリ割り当て */ p->??? = … /* 不可 */ st_p p = (st_p)malloc(sizeof(*p)); p->??? = … /* OK */ foo() { st_p p = (st_p)malloc(sizeof(*p)); /* free()するまで有効 */ … /* ただし,pはfoo()内でのみ有効 */ } 共用体 struct st1 { … }; struct st2 { … }; union un { struct st1 s1; struct st2 s2;}; union un *u = (union un *) malloc(sizeof(union un)); u->st1.??? = …; sizeof(union un):s1 または s2 を格納するの に十分なサイズ u は st2 として再利用可 共用体(続き) struct st1 *s = (struct st1 *) malloc(sizeof(struct st1)); union un *u = (union un *)s; u->st1.??? = …; sizeof(struct st1) ≧ sizeof(struct st2) ? u は struct st2 型として再利用できるとは 限らない どちらの方法が望ましいか? 用途・要求による メモリの使用量 vs. メモリの管理効率 •malloc の実装法 yaccファイルでの%union指定 %union { int i; char *str; tree n; } トークンIdentifierの値は • 型treeの宣言を持つヘッダファイルを用意してyaccファイルの先 文字列(char *)型 頭でインクルード %token <str> Identifier %token <i> Constant %token IF ELSE WHILE… yylval.i = atoi(yytext); yylval.str = strdup(yytext); %type <n> program … 非終端記号programの値は tree型 構文木の表示 括弧で括った表示 木構造の形を忠実に反映した表示が可能 a + b + c • (+ (+ a b) c) if (e1) if (e2) s1 else s2 = if (e1) { if (e2) s1 else s2 } • (if e1 (if e2 s1 s2)) 構文木の表示(続き) preorderの木の走査 葉でないノードの表示 • ‘(’を表示 • 節の値を表示 • 子の値を再帰的に表示 • ‘)’を表示 + + - * a b c 課題6(構文木の表示) e 表示の仕様は自由 • インデントなどはしなくても構わない d 構文木の表示(続き) 構文の要素毎に表示ルーチンを用意 print_statement … 文の表示 print_expression … 式の表示 などなど 演算子の結合の優先順位 構文解析時に構文木に反映しているはずなの で,表示側で考慮する必要はない opt の扱い 1 a: bopt c d (b c d または c d にマッチ) ↓ a: b_opt c d ; b_opt: /* empty */ { $$ = NULL; } |b ; yaccのエラー「empty rule for typed nonterminal, and no action」が出る場合 /* empty */ に対するアクションを明示的に書く opt の扱い 2 a: bopt c d ↓ a: c d | b c d ; a: bopt c d x: a |y ↓ a_aux: c d ; x: a_aux | b a_aux | y ; opt の扱い まとめ どの方法が優れているかは一概には言え ない(状況によって異なる) 可読性・記述性 • アクションの可読性・記述性 • conflictの問題 conflictが生じたら他の方法を試す • bisonの-vオプションにより生成される*.outputフ ァイルにより, conflictの原因を探る 5. Misc ミニ補講 課題が難しくて困っている プログラミングスキル不足を感じている 人達を対象にTAが実施 週に1回, 1時間程度 対象人数: 10名前後 (x 2 or x 3?) 詳細は後日改めてお知らせします C言語以外で演習するには 演習資料の読み替えは自己責任 C++ Scheme, Common Lisp flex, bisonがそのまま使えます 課題6の結果を渡せばOK Java JFlex, Cupというツールがあります • サンプル入力を用意してあるので参考に JRuby, Jython, Scala, Clojure, ... その他: Ruby, OCaml, Haskell, … グループ分け 座席 実装言語の選択 入口 TA ミニ補講用モニタ
© Copyright 2024 ExpyDoc