PowerPoint プレゼンテーション

計算機科学実験及演習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
ミニ補講用モニタ