• 検索結果がありません。

18/12/06 情報工学実験 C コンパイラ (2018 年度 ) 担当 : 笹倉 佐藤 その 3 yacc の構造 定義部 %% 定義部の終了 規則部 %% 規則部の終了 ユーザ定義サブルーチン部 :C のプログラムを書く 形は lex と同じ 1

N/A
N/A
Protected

Academic year: 2021

シェア "18/12/06 情報工学実験 C コンパイラ (2018 年度 ) 担当 : 笹倉 佐藤 その 3 yacc の構造 定義部 %% 定義部の終了 規則部 %% 規則部の終了 ユーザ定義サブルーチン部 :C のプログラムを書く 形は lex と同じ 1"

Copied!
16
0
0

読み込み中.... (全文を見る)

全文

(1)

情報工学実験

C

コンパイラ

2018年度)

担当:笹倉・佐藤

2018.12.6 その3

yaccの構造

定義部

%%

定義部の終了

規則部

%%

規則部の終了

ユーザ定義サブルーチン部

Cのプログラムを書く

形は

lexと同じ

(2)

yacc

• 

yacc のキモは規則部

•  規則部には文法規則を書く

左辺

: 右辺

• 

yaccは入力されたプログラムを右辺から左辺

に「還元」していく

•  この規則にアクションが書かれていたら還元

するときにアクションも実行する.

•  アクションというのは基本的にはCプログラム.

yacc 注意点

• 

yacc は

字句解析をされた後のトークン

を入力

とするので必ず字句解析ルーチンが必要.単

独では使えない

•  字句解析ルーチンを使うにはyaccの中から

yylex()関数を呼び出す(実際にはyyparse()関

数を呼ぶとその中で自動的に

yylex()を呼んで

くれる)

•  字句解析ルーチンは自作することも可能だが

普通は

lexを使う

(3)

yacc 定義部

•  初期設定を行う

•  使用するトークン名を

%token

の後に続けて書

–  そうするとそれに対応したCのマクロ宣言が作成され  *.tab.h が自動的に作成される –  それをlexプログラムでもyaccプログラムでもincludeす ることでトークンの受け渡しが可能になる.

%{

%}

で囲まれた部分はそのまま *.tab.c にコ

ピーされる.必要なヘッダファイルの

includeなど

を行ったりする

yacc 規則部

•  文法規則を書く •  左辺 : 右辺; •  文法としては左辺が右辺に変換される(基本的に書き換え るだけ).ただしそれに加えて右辺にアクションが書ける. •  左辺は非終端記号 •  左辺と右辺の区切りは : •  各ルールの区切りは; •  lexと同様に | が使える •  右辺のアクションはその規則を使って還元が行われるとき に実行される.Cのプログラムも書ける •  最初の規則の左辺がスタートシンボルとみなされる

(4)

yacc ユーザ定義サブルーチン部

•  ここで書いたものはすべてC言語だと解釈さ

*.tab.cにそのままコピーされる

yyparse() を呼び出さないと字句解析が実行さ

れない

ので

yaccがmainとなるプログラムを書

く場合はこの中で必ず 

yyparse() を呼び出さ

なければならない

• 

yyparse()の中でyylex()が呼ばれている.

例:「

I am」だけを受理する(sample.y)

%{ #include <stdio.h> #include "sample.tab.h" extern int yylex(); extern int yyerror(); %} %token SUBJECT PRED SPACE %% statement : SUBJECT SPACE PRED { printf("OK!\n");} ; %% int main(void){ if (yyparse()) { fprintf(stderr, "Error ! Error ! Error !\n"); return 1; } }

(5)

例:「

I am」だけを受理する(sample.l)

%{ #include "sample.tab.h" /* lex for sample.y */ %} %% I {return SUBJECT;} am {return PRED;} [\t ]+ {return SPACE;} \n return 0; . return yytext[0]; %%

yacc のコンパイルの仕方

> flex ファイル名

> bison -d ファイル名

> gcc *.tab.c lex.yy.c –o 実行ファイル名 –lfl -ly

•  UNIXの標準は yacc だが,GNUの yacc相当であるbison を本

実験では使う.-d はヘッダファイルを出すためのオプション. ヘッダファイルに変更がなければつけなくてよい.

sample.y をコンパイルして実行してみよう

> flex sample.l

> bison –d sample.y

> gcc sample.tab.c lex.yy.c –o sample –lfl -ly

(6)

yacc が対応できない規則1

•  二つ以上のトークンを先読みしないといけな

いような文法

例:

phrase : cart_animal AND CART

| work_animal AND PLOW ;

cart_animal : HORSE | GOAT;

work_animal : HORSE | OX;

(注:大文字はトークン) この例では例えば HORSE AND CART の HORSE が cart_animal から導出されたHORSEなのかwork_animalから導出された HORSEなのかはHORSEの2トークン先のCARTを見ないと決定で きない

yacc が対応できない規則2

•  曖昧な文法

– 複数の構文木を作れるような文法を曖昧な文法

という

– 曖昧な文法に対して yacc はreduce/reduce

conflict や shift/reduce conflictのエラーを出す.

(7)

曖昧な文法の例

1

start : x

| y;

x : A;

y : A;

この時,

Aという入力に対して,これがx から導

出される

Aなのかyから導出されるAなのかはっ

きりしない.どちらも有りうる.

これが還元

/還元衝突 (reduce/reduce conflict)

曖昧な文法の例

2

start : x

| y R;

x : A R;

y : A;

ARという入力に対して,x から A R が導出され

るし,

yをAを考えてstartの規則に戻ってRとも考

えられる.どちらも有りうる.

これがシフト

/還元衝突 (shift/reduce conflict)

(8)

lex から yacc に値を渡す方法

• 

lex プログラムから yacc プログラムに値を渡すには

– lex プログラムのアクション部で return 値; とする.この 場合,型はデフォルトではint – lex プログラムで渡したい値をyylval変数に入れておく.こ の場合の型はデフォルトではint – yylvalの型は変更することができる(後述).

• 

yacc プログラムで値を受け取るには

–  規則部のシンボルの値として受け取れる. $$ : 左辺のシンボルの値 $1 : 右辺一番左のシンボルの値 $2 : 右辺二番目のシンボルの値 …

yylval の型

• 

yylval のデフォルトの型は int

int の値が欲しければ何もしなくてよい

• 

yylval はライブラリの中で宣言されているので,

これを使う

lexプログラムの宣言部にC言語と

して

extern int yylval;

を書いておけばよい.

(9)

yacc のアクションでできること

• 

$$ に値を代入することで,規則部の左辺の

値を任意に決められる

(特に明示しなければ 

$$ = $1 となる)

•  還元のタイミングで任意のCプログラムを実行

できる

– 例えばprintfで値を表示させたり

– 例えば抽象構文木を作ったり

加減算を行う計算機

(p-m.y)

%{ #include <stdio.h> #include "p-m.tab.h" extern int yylex(); extern int yyerror(); %} %token NUMBER %% statement : expression {printf("= %d\n", $1);}; expression : expression '+' NUMBER {$$ = $1 + $3; } | expression '-' NUMBER {$$ = $1 - $3; } | NUMBER {$$ = $1;}; %% int main(void) { if (yyparse()) { fprintf(stderr, "Error\n"); return 1; } return 0;

(10)

加減算を行う計算機

(p-m.l)

%{ #include "p-m.tab.h" extern int yylval; %} %% [0-9]+ {yylval = atoi(yytext); return NUMBER; } [\t ] ; /* ignore whitespace */ \n return 0; . return yytext[0]; %%

演習:四則演算

(arith.y)

• 

p-m.y を拡張して乗除算と括弧も扱えるように

しよう

•  乗除算は加減算より優先される(先に実行さ

れる)ことに注意

•  括弧があると括弧の中を先に計算することに

注意

•  最初に文法を考える

(11)

演習:文法

•  数字と四則演算とかっこからなる算術式の文

法を書いてみよ

<算術式> ::= <算術式> <演算子> <因子>        | <因子> <演算子> ::= + | - | * | / <因子> ::= <数> | (<算術式>)

よくない例

(BNF

表記

)

算術式において乗除算は加減算より優先されることがこの 文法では実現できていない

演習:文法

•  数字と四則演算とかっこからなる算術式の文

法を書いてみよ

<算術式> ::= <算術式> + <項>         | <算術式> - <項>         | <項> <項> ::= <項> * <因子> | <項> / <因子> | <因子> <因子> ::= <数> | (<算術式>)

正しい例

(BNF

表記

)

(12)

演習:四則演算

(arith.y)

•  文法の正しい例になるように p-m.y を変更し四則演

算と括弧が使えるようにしよう

(lexプログラムの内容はp-m.lと同じでよい. p-m.lを

arith.lにコピーし,p-m.tab.hをarith.tab.hに変更して使

う)

•  応用:除算の時に0で割っている場合にはエラーメッ

セージをだして計算を行わないようにする

–  ヒント:エラーが起こったことを知らせるには関数yyerror() を使うのがよい –  関数yyerror()はデフォルトでは引数で与えた文字列を stderrに表示する (独自に実装することも可能)

arith.y(穴埋め)

%{ #include <stdio.h> #include "arith.tab.h" extern int yylex(); extern int yyerror(); %} %token NUMBER %% statement : expression { printf("= %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()) { fprintf(stderr, "Error\n"); return 1; } return 0; }

(13)

演習:実数

(arithr.l, arithr.y)

•  これまで整数の四則演算を行っていたarith.yを実数

の四則演算を行うように変更しよう

• 

lexプログラムを実数をトークンとするように変更する

–  ヒント:実数を受理するように正規表現を変える 

• 

lexプログラムからyaccプログラムへ渡す値も実数にな

–  ヒント:lex プログラム yaccプログラムのどちらにも宣言部 でarithr.tab.hをincludeする前に #define YYSTYPE double を入れる(注:YYSTYPE はyylval の型) – arithr.tab.hを見て理解すること

変数を使えるようにする

• 

arithr.yを拡張して変数を使えるようにする

•  変数はアルファベット小文字一文字だけからなる

ものとする

•  変数の数はたかだか26なので,26個の要素をも

つ配列

vbltableに格納する

(注:これは問題を簡単にするためにこのようにするだけなので実 際にコンパイラを作成する時はこの方法ではなく個別にメモリを確 保して変数名を記憶するようにすること)

•  一行だけで計算が終わるのではなく数式を連続

で計算できるようにし,

$が入力されたら終了す

るようにする

(14)

%union

•  どの変数が使われるのかを知るためにはlex

プログラムから変数名を受け取らなければな

らない

•  数字が入力された時は実数を,変数が入力

された時には整数(配列の添え字に使う

)を受

け取るように複数種類の型の値を

yylvalに入

れて

lexプログラムからyaccプログラムへ値を

渡したい

•  そのための%union

%union宣言の使い方例

• 

yaccプログラムの宣言部にシンボルの型を定義する

%union {

double dval;

int vblno;

}

Cの共用体として実現される

• 

yaccプログラムの各シンボルにどの型を取るかを教え

てあげなければならない

–  トークンの場合 %token <dval> NUMBER %token <vblno> NAME –  非終端記号のシンボルの場合 %type <dval> expression

(15)

演習:変数も使える計算機

(arithrv.[ly])

•  arithr.[ly]を拡張する •  %union行をarithrv.yに加える •  変数の値を格納する配列vbltable[26]をarithrv.yで宣言し, arithrv.lではextern宣言する •  変数のトークンをNAME としてarithrv.lで読み込めるようにする •  arithrv.yに statement : NAME = expression | expression を付け加える(アクション部も各自考えて追加すること) •  NAMEをexpressionの中で使えるようにする •  statement を複数行読めるようにし,$が来たら終わるようにする •  各トークン,必要な非終端記号に型を指定すること

基本言語仕様

<プログラム> ::= <変数宣言部> <文集合> <変数宣言部> ::= <宣言文> <変数宣言部> | <宣言文> <宣言文> ::= define <識別子>; <文集合> ::=  <文> <文集合>| <文> <文> ::= <代入文> | <ループ文> | <条件分岐文> <代入文> ::= <識別子> = <算術式>; <算術式> ::= <算術式> <加減演算子> <項> | <項> <項> ::= <項> <乗除演算子> <因子> | <因子> <因子> ::= <変数> | (<算術式>) <加減演算子> ::= + | - <乗除演算子> ::= * | / <変数> ::= <識別子> | <数> <ループ文> ::= 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|

(16)

この言語で書けるプログラムの例

define a; define a1; define b; a = 2800; b = (a + 5) *3; if (b> 3000) { b = 3000; } a1 = 0; while(a1 < 2){ b = b / 2; a1 = a1 + 1; }

本日の作業内容

•  作業目標 1.  スライド・資料をみながら四則演算のできる計算機のプログ ラムを完成させる(資料の演習問題2) 2.  スライドをみながら四則演算のできる計算機が実数も扱える ようにする 3.  スライド・資料をみながら変数も使える計算機のプログラムを 完成させる(資料8節) 4.  基本言語仕様を構文解析するyacc,lexプログラムを作成する •  まずは正しいプログラムは何のエラーもなく通るようにする.コード生 成はまだしなくてよい.アクション部は何も書かなくてよい. •  エラーがあれば”error”と出力するようにする •  時間が来たら作業報告書を書いて印刷し,それを提出の こと.作業報告書には作業目標の何番まで終了したかを 書くこと.

参照

関連したドキュメント

スライド5頁では

テューリングは、数学者が紙と鉛筆を用いて計算を行う過程を極限まで抽象化することに よりテューリング機械の定義に到達した。

(ロ)

年限 授業時数又は総単位数 講義 演習 実習 実験 実技 1年 昼 930 単位時間. 1,330

部分品の所属に関する一般的規定(16 部の総説参照)によりその所属を決定する場合を除くほ か、この項には、84.07 項又は

から揚げ粉を付け油で揚げる 通則 1.. ③: 自動車用アルミホイール 第17部

・条例第 37 条・第 62 条において、軽微なものなど規則で定める変更については、届出が不要とされ、その具 体的な要件が規則に定められている(規則第

それゆえ︑規則制定手続を継続するためには︑委員会は︑今