Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
情報工学実験C コンパイラ
第2回説明資料
(2018年度) 担当:笹倉・佐藤
2018.12.13
コンパイラ作成実験
• 非常に難しい. • まず、コンパイラを実装すること自体が難しい. • コンパイラを指して「人工知能」と呼んだ時代も
あった.
• 難しさは、抽象的なアイデアを元に具体的な実装を行うことにある.
• クヌースはこれを「計算機科学的な考え方」と呼び、できる人の存在比率は 1/50だと述べている.
• つまりこれが、まさに計算機科学の難しさである.
抽象的なことを具体的に
• そもそもコンパイラ自体が抽象的なことを具体的に変換するもの 高級プログラミング言語 → アセンブラコード
• コンパイラの各フェーズを実装するのも抽象的なことを具体的にしないと実装できてない 例:入力のソースを抽象構文木に変換 – 抽象構文木のデータ構造はどうする? – どうやって抽象構文木を作る?
実験を実施するにあたって
• 手順を示して、その通りにしてもらうことは簡単. • しかし,そうではなくて,悩んで,理解して,腑に
落ちてほしい. • わからないことは相談してください.
– グループ内 – 教員 – TA
• 「答えを聞く」のではなくて、理解する手助けを求めてほしい.
大学で学ぶべきこととは
• わからないことはストレスだけど,耐えてほしい.
• 「新しいこと」は常に「わからないこと」 • わからないことがわかるようになる活動が非
常に重要 • 「わかる」とはどういうことか. • むしろ、わかることはやらなくてもよい
前回の演習問題(復習)
1. 四則演算のできる計算機のプログラム (括弧も使える)
2. 実数の扱える四則演算の計算機のプログラム (実数「も」というより実数「が」が正しかったです)
3. 変数も扱える四則演算の計算機のプログラム (変数と実数が扱える)
演習問題1で行うべきこと
• 構文木が演算の順序を反映するように規則を作る
• 具体的には – 同じ演算子が並んだら左側から実行する – 加減算より乗除算を優先して実行する – 括弧の中が外より先に実行される
算術式の規則
expression : expression ‘+’ mulexp | expression ‘-‐’ mulexp | mulexp ; mulexp : mulexp ‘*’ primary | mulexp ‘/’ primary | primary ; primary : ‘(‘ expression ‘)’ | NUMBER ;
演習問題1解答例(arith.y) %{ #include <stdio.h> #include ”arith.tab.h" extern int yylex(); extern int yyerror(); %} %token NUMBER %% statement : expression { prinY("= %d\n", $1);} ; expression : expression '+’ mulexp {$$ = $1 + $3;} | expression '-‐’ mulexp {$$ = $1 -‐ $3;} | mulexp {$$ = $1;} ; mulexp : mulexp ‘*’ primary {$$ = $1 * $3;}
| mulexp ‘/’ primary {if ($3 == 0) { yyerror(“divide by zero”); return 0;} else $$ = $1 / $3;} | primary {$$ = $1;} ; primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ; %% int main(void) { if(yyparse()) { fprinY(stderr, "Error\n"); return 1; } return 0; }
演習問題1解答例(arith.l) %{ #include ”arith.tab.h” %} %% [0-‐9]+ {yylval = atoi(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%
lex とyaccの連携(1)
• lex のアクションでreturn した値 → yacc ではトークンの種類として受け取る
%% [0-‐9]+ {yylval = atoi(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%
lex
%token NUMBER %% …(中略) … primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ; %%
yacc
lex とyaccの連携(2) • lex のアクションでreturn した値 → yacc で’’で囲まれたものは文字そのものとして受け取る
%% [0-‐9]+ {yylval = atoi(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%
lex
primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ; %%
yacc
lex とyaccの連携(3) • lex で yylval に入れた値 → yacc ではシンボル値として受け取れる シンボル値のデフォルトの型は int
%% [0-‐9]+ {yylval = atoi(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%
lex
primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ; %%
yacc
シンボル(例)
primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ;
シンボル
シンボル
シンボル シンボル シンボル
$$ は規則の左辺のシンボル値(この例ではprimaryのシンボル値) $1は規則の右辺の一つ目のシンボル値 $2は規則の右辺の二つ目のシンボル値 …
シンボル値(例)
primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ;
$$
$1
$1 $2 $3
演習問題2で行うべきこと
• 演習問題1のプログラムで整数を実数に変える 1. lex で実数を受け取れるようにする 2. yylval で実数値をyaccに渡す
実数の正規表現の例
[0-‐9]+ | [0-‐9]*\.[0-‐9]+
[0-‐9]+ は0から9までの数字の1個以上の繰り返し.整数表現に対応 例:14 [0-‐9]*\.[0-‐9]+ は0から9までの数字の0個以上の繰り返しの後 . が来て,その後0から9までの数字の1個以上の繰り返し. . はそのままではすべての文字にマッチしてしまうので\.にして . だけを表すようにしている. 例:1.34
yylval を実数に
• yylval の型はYYSTYPEで指定 • YYSTYPE はデフォルトではint • YYSTYPE を変えることで yylval とすべてのシン
ボル値の型が変わる #define YYSTYPE double
演習問題2解答例(arithr.y) %{ #include <stdio.h> #define YYSTYPE double #include ”arith.tab.h" extern int yylex(); extern int yyerror(); %} %token NUMBER %% statement : expression { prinY("= %g\n", $1);} ; expression : expression '+’ mulexp {$$ = $1 + $3;} | expression '-‐’ mulexp {$$ = $1 -‐ $3;} | mulexp {$$ = $1;} ; mulexp : mulexp ‘*’ primary {$$ = $1 * $3;}
| mulexp ‘/’ primary {if ($3 == 0) { yyerror(“divide by zero”); return 0;} else $$ = $1 / $3;} | primary {$$ = $1;} ; primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} ; %% int main(void) { if(yyparse()) { fprinY(stderr, "Error\n"); return 1; } return 0; }
演習問題2解答例(arithr.l) %{ #define YYSTYPE double #include ”arith.tab.h” %} %% [0-‐9]+ | [0-‐9]*\.[0-‐9]+ {yylval = atof(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%
演習問題3で行うべきこと
1. 演習問題2のプログラムで変数を使えるようにする
2. 一行だけで計算が終わるのではなく数式を連続で計算できるようにし,$が入力されたら終了するようにする
変数を使えるようにする
• arithr.yを拡張して変数を使えるようにする • 変数はアルファベット小文字一文字だけから
なるものとする • 変数の数はたかだか26なので,26個の要素
をもつ配列vbltableに格納する • 配列vbltableはyaccの宣言部で宣言する
%union
• どの変数が使われるのかを知るためにはlexプログラムから変数名を受け取らなければならない
• 数字が入力された時は実数を,変数が入力された時には整数(配列の添え字に使う)を受け取るように複数種類の型の値をyylvalに入れてlexプログラムからyaccプログラムへ値を渡したい
• そのための%union • %union を使うとYYSTYPEの型が共用体になる
共用体とは
• Cのデータ型の一つ • 一つの変数を場合によって異なる型で使える
ようにするための仕組み • あまり使われることはない
でもコンパイラで使うと便利 • ここではyylvalをdouble で使ったりintで使った
りするために利用する
Cの共用体とは
• 宣言の形は構造体と似ているが,構造体とは別物 例: union u_tag{ int ival; float fval; char *sval; } u;
• u には整数も実数も文字列へのポインタも入れることができる 整数を入れる例 u.ival = 3; 実数を入れる例 u.fval = 5.789 整数へのポインタを入れる例 u.sval = 文字列へのポインタ • 構造体とは違い,型が異なる場合別々の場所に値が格納されるのでは
なく,同じ場所に格納される.だから異なる型のうち一番格納場所をたくさん必要とするサイズに合わせて格納場所が確保される.
• 実際に使うときには,そのときどの型の値が入っているかをプログラマの側で管理しないといけない
• 詳しくはCの教科書を参照のこと
%union宣言の使い方例
• yaccプログラムの宣言部にシンボルの型を定義する %union { double dval; int vblno; } yaccを通すと*.tab.h, *.tab.c の中でCの共用体として実現される
• yaccプログラムの各シンボルにどの型を取るかを教えてあげなければならない – トークンの場合 %token <dval> NUMBER %token <vblno> NAME
– 非終端記号のシンボルの場合 %type <dval> expression
演習:変数も使える計算機(arithrv.[ly])
• arithr.[ly]を拡張する • %union行をarithrv.yに加える • 変数の値を格納する配列vbltable[26]をarithrv.yで宣言する • 変数のトークンをNAME としてarithrv.lで読み込めるようにする • 変数への代入を可能にするためにarithrv.yに
statement : NAME = expression | expression を付け加える
• NAMEをexpressionの中で使えるようにする • statement を複数行読めるようにし,$が来たら終わるようにする • 各トークン,必要な非終端記号に型を指定すること
演習問題3解答例(arithrv.y) %{ #include <stdio.h> #include ”arithrv.tab.h" extern int yylex(); extern int yyerror(); double vbltable[26]; %} %union{ double dval; int vblno; } %token <vblno> NAME %token <dval>NUMBER %type <dval> expression mulexp primary %%
statement_list : statement ‘\n’ | statement_list statement ‘\n’ ; statement : NAME ‘=‘ expression { vbltable[$1] = $3; prinY(“%c = %g\n”, $1 + ‘a’, $3);} | expression { prinY("= %g\n", $1);} ; (中略) primary : ‘(‘ expression ‘)’ {$$ = $2;} | NUMBER {$$ = $1;} | NAME {$$ = vbltable[$1];} ; %% (後略)
演習問題3解答例(arithrv.l) %{ #include ”arithrv.tab.h” %} %% [0-‐9]+ | [0-‐9]*\.[0-‐9]+ {yylval = atof(yytext); return NUMBER;} [\t ] ; /* ignore whitespace */ [a-‐z] {yylval.vblno = yytext[0] -‐ 'a'; return NAME; } "$" {return 0; /* end of input */} \n | . return yytext[0]; %%
注) lex がreturn 0; すると入力終了の意味になる.
演習問題4
• 基本言語仕様を構文解析するyacc,lexプログラムを作成する – まずは正しいプログラムは何のエラーもなく通る
ようにする.コード生成はまだしなくてよい.アクション部は何も書かなくてよい.
– エラーがあれば”error”と出力するようにする
基本言語仕様 <プログラム> ::= <変数宣言部> <文集合> <変数宣言部> ::= <宣言文> <変数宣言部> | <宣言文> <宣言文> ::= define <識別子>; | array <識別子> [<数>]; <文集合> ::= <文> <文集合>| <文> <文> ::= <代入文> | <ループ文> | <条件分岐文> <代入文> ::= <識別子> = <算術式>; <識別子> [ <数> ] = <算術式>; ß 追加 <算術式> ::= <算術式> <加減演算子> <項> | <項> <項> ::= <項> <乗除演算子> <因子> | <因子> <因子> ::= <変数> | (<算術式>) <加減演算子> ::= + | - <乗除演算子> ::= * | / <変数> ::= <識別子> | <数> | <識別子>[<数>] <ループ文> ::= while (<条件式>) { <文集合> } <条件分岐文> ::= if (<条件式>) { <文集合> } | if (<条件式>) { <文集合> } else { <文集合> } <条件式> ::= <算術式> <比較演算子> <算術式> <比較演算子> ::= == | '<' | '>' <識別子> ::= <英字> <英数字列> | <英字> <英数字列> ::= <英数字> <英数字列>| <英数字> <英数字> ::= <英字> | <数字> <数> ::= <数字> <数> | <数字> <英字> ::= a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|
T|U|V|W|X|Y|Z <数字> ::= 0|1|2|3|4|5|6|7|8|9
この言語で書けるプログラムの例 define a; define a1; define b; array c[3]; a = 2800; b = (a + 5) *3; c[0] = 0; c[1] = 1; c[2] = 2; if (b> 3000) { b = 3000; } a1 = c[0]; while(a1 < c[2]){ b = b / 2; a1 = a1 + c[1]; }
この言語でエラーとなるプログラムの例
int a; a = 0;
define a; array c[3]; a = 0; while (a < 3) { c[a] = a; }
例1 例2
int という型名は定義されていない 配列の添え字に変数を用いることは 定義されていない
演習問題4で行うべきこと
• 何をトークンとして扱うか – 終端記号はすべてトークン – 基本言語仕様のうち正規表現で書けるものは
トークンとしてlexで切り出す
基本言語仕様 <プログラム> ::= <変数宣言部> <文集合> <変数宣言部> ::= <宣言文> <変数宣言部> | <宣言文> <宣言文> ::= define <識別子>; | array <識別子> [<数>]; <文集合> ::= <文> <文集合>| <文> <文> ::= <代入文> | <ループ文> | <条件分岐文> <代入文> ::= <識別子> = <算術式>; <識別子> [ <数> ] = <算術式>; <算術式> ::= <算術式> <加減演算子> <項> | <項> <項> ::= <項> <乗除演算子> <因子> | <因子> <因子> ::= <変数> | (<算術式>) <加減演算子> ::= + | - <乗除演算子> ::= * | / <変数> ::= <識別子> | <数> | <識別子>[<数>] <ループ文> ::= while (<条件式>) { <文集合> } <条件分岐文> ::= if (<条件式>) { <文集合> } | if (<条件式>) { <文集合> } else { <文集合> } <条件式> ::= <算術式> <比較演算子> <算術式> <比較演算子> ::= == | '<' | '>' <識別子> ::= <英字> <英数字列> | <英字> <英数字列> ::= <英数字> <英数字列>| <英数字> <英数字> ::= <英字> | <数字> <数> ::= <数字> <数> | <数字> <英字> ::= a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|
T|U|V|W|X|Y|Z <数字> ::= 0|1|2|3|4|5|6|7|8|9
この線から下はlexで
基本言語仕様 <プログラム> ::= <変数宣言部> <文集合> <変数宣言部> ::= <宣言文> <変数宣言部> | <宣言文> <宣言文> ::= define <識別子>; | array <識別子> [<数>]; <文集合> ::= <文> <文集合>| <文> <文> ::= <代入文> | <ループ文> | <条件分岐文> <代入文> ::= <識別子> = <算術式>; <識別子> [ <数> ] = <算術式>; <算術式> ::= <算術式> <加減演算子> <項> | <項> <項> ::= <項> <乗除演算子> <因子> | <因子> <因子> ::= <変数> | (<算術式>) <加減演算子> ::= + | - <乗除演算子> ::= * | / <変数> ::= <識別子> | <数> | <識別子>[<数>] <ループ文> ::= while (<条件式>) { <文集合> } <条件分岐文> ::= if (<条件式>) { <文集合> } | if (<条件式>) { <文集合> } else { <文集合> } <条件式> ::= <算術式> <比較演算子> <算術式> <比較演算子> ::= == | '<' | '>' <識別子> ::= <英字> <英数字列> | <英字> <英数字列> ::= <英数字> <英数字列>| <英数字> <英数字> ::= <英字> | <数字> <数> ::= <数字> <数> | <数字> <英字> ::= a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|
T|U|V|W|X|Y|Z <数字> ::= 0|1|2|3|4|5|6|7|8|9
具体的には赤字がトークン あとはこの線より上の終端記号がトークン
トークン
• それぞれのトークンごとに種類を分ける 例:識別子,数,プラス記号,マイナス記号,define, array ….
• 今はまだyaccのアクションを書かないので型について考える必要はない
• lexもトークンの名前をreturnするだけでよい
非終端記号の名前 (本来なんでもいいのだが,時間がないので例を示す)
<プログラム> program <変数宣言部> declarations <宣言文> decl_statement <文集合> statements <文> statement <代入文> assignment_stmt <算術式> expression <項> term <因子> factor <加減演算子> add_op <乗除演算子> mul_op <変数> var <ループ文> loop_stmt <条件分岐文> cond_stmt <条件式> condition <比較演算子> cond_op
基本言語仕様にあるそれ以外の非終端記号はlexで処理してトークンになってしまうのでyaccファイルでの非終端記号としては扱わない
言語仕様を拡張してこれ以外の非終端記号を導入するときはその名前は自分たちで考えること
これらの名前をyaccファイルで使う
トークン名(普通は大文字を使う) (本来なんでもいいのだが,時間がないので例を示す.)
define DEFINE array ARRAY while WHILE if IF else ELSE ; SEMIC [ L_BRACKET ] R_BRACKET ( L_PARAN ) R_PARAN { L_BRACE } R_BRACE = ASSIGN + ADD - SUB * MUL / DIV == EQ < LT > GT <識別子> IDENT <数> NUMBER
言語仕様を拡張してこれ以外のトークンを導入するときはその名前は自分たちで考えること
これらがlexからのreturn値になる
lex ファイル(ひながた)
(定義部省略) %% "define" return DEFINE; "array" return ARRAY; …(中略)… 整数の正規表現 return NUMBER; 識別子の正規表現 return IDENT; 空白タブ改行 ; %%
• 省略されている定義部と規則部の部分を書くこと
• 赤字のところを正規表現に直すこと
yaccファイル(ひながた) …(略)… %token DEFINE ARRAY WHILE IF ELSE 以下トークンを全て%tokenで定義 %% program : declaraxons statements ; declaraxons : decl_statement declaraxons | decl_statement ; decl_statement : DEFINE IDENT SEMIC | ARRAY IDENT L_BRACKET NUMBER R_BRACKET SEMIC ; 以下規則をすべて定義 %% int main(void) { if (yyparse()){ fprinY(stderr, “Error\n”); return 1; } retrurn 0; }