言語理論とコンパイラ
第九回:
上向き構文解析の原理
2013 年 6 月 14 日
http://www.sw.it.aoyama.ac.jp/2013/Compiler/lecture9.html
Martin J. Dürst
© 2005-13 Martin
J. Dürst 青山学院大学
今日の予定
前回のまとめ
- 下向き構文解析とバックトラック
- 再帰的下向き構文解析
- 概要: 非終端記号ごとの関数
- 左再起への対応
- 下向き構文解析の限界: 左再起、先読み
前回の文法の発展
- 出発点 (parser.c):
- E → T '+' T | T
T → number
- 三つ以上の被演算子への対応 (parserA.c):
- E → T '+' E | T
T → number
- 引き算の追加 (parserB.c):
- E → T '+' E | T '-' E | T
T → number
- 右結合から左結合への変更 (parserC.c):
- E → E '+' T | E '-' T | T
T → number
- 左再帰への対応 (parserD.c):
- E → T ME
ME → '+' T ME | '-' T ME | ε
T → number
更なる文法の発展
- EBNF の利用:
書き換え規則の中の繰り返しを関数内の繰り返しと置き換える
- 掛算への対応
- 優先度への対応
下向き構文解析と上向き構文解析
- 下向き構文解析 (top-down parsing):
- 解析木を上から (初期記号から) 作る
- 上向き構文解析 (bottom-up parsing):
- 解析木を下から (終端記号から) 作る
- 途中に複数の (小さな) 解析木がある
下向き構文解析と上向き構文解析
|
下向き構文解析 |
上向き構文解析 |
一般的な方法 |
バックトラック |
動的プログラミング (CYK アルゴリズム) |
広く使われている方法 |
再帰的下向き構文解析 |
LR 法 |
導出の順番: 最左導出と最右導出
簡単な例:
E → E '+' T
T → integer
最左導出の場合に、いつも最も左の非終端記号を置き換える
最右導出の場合に、いつも最も右の非終端記号を置き換える
文法の種類の呼び方
- LL: 左から入力を読んで、最左導出
- LR: 左から入力を読んで、最右導出 (逆順)
- LL(1): LL で、先読みはトークン一つに限定
- LR(1): LR で、先読みはトークン一つに限定
- LALR: LR (1) の一種で、yacc、bison など幅広く使用
LALR 構文解析の原理
スタックを使って読んだトーケンや途中の非終端記号を蓄積
オートマトンを使ってできるだけ簡単な操作で次のステップを決定
(LA)LR 構文解析の三つのオペレーション
- shift:
トークンを一個読んで、そのトークンをスタックにステートと一緒に詰める
- reduce:
スタックの上部にあるトークンや非終端記号を文法規則を使って一つの非終端記号に変換
reduce 後には go to を使って状態を移動することがある
- accept: 入力を受理、作業を終了
Shift と Reduce の具体例
上向き構文解析の利点と問題点
曖昧な文法
- 文法の例:
E → E '-' E | integer
- 具体例:
5 - 3 - 7
は (5-3) - 7
か 5
- (3-7)
か
- 文法が両方の解釈を許す
- (形式) 言語の定義として問題ない
- ある入力に対して複数の解析木を許す文法は曖昧な文法
(ambiguous grammar) と言う
- 一部の文法の場合、変換によって曖昧性が取り除かれる
- 変換のアルゴリズムや変換が可能かどうかを決めるアルゴリズムは存在しない
- 曖昧な文法は言語の構文だけの定義には使えるが、プログラム言語やデータ形式には向いてない
- 文法の曖昧性とプッシュダウンオートマトンの非決定性は同じものではない
(例: 左右対称の文字列)
文法の曖昧性の除去
問題の例:
E → E '-' E | integer
入力 4 - 5 - 7
に対して複数の解析木が作成可能
解析木によって計算結果が違う
解決方法: 文法の書き換え (左結合の場合)
E → E '-' integer | integer
bison の概要
- yacc: yet another compiler compiler
- Unix とともに普及
- compiler compiler: コンパイラを作るコンパイラ
- yet another: もう一つ
(当時compiler compiler が流行、名前に困った)
(以後、YA.. は広く使われるようになった)
- bison: yacc の gnu 版
演習例: 簡単な電卓
スタートのためのファイル: makefile,
calc.y, calc.lex
bison のマニュアル
bison の仕組みとデバッグ
bison -v
で作った機械の明細のファイルを作成
(例: calc.output)
#define YYDEBUG 1
でデバッグを ON
bison の開発のコツ
- テストの入力ファイルを用意
- テストの正解ファイルを用意
- 自動的にプログラムを実行、
diff
コマンドで比較
diff
から出力がなかったらテスト成功
- テストファースト:
テストを追加して、失敗を確認してから実装
calc 用テストファイル: test.in, test.check
属性文法
(attribute(d) grammar(s))
- 単純な構文解析は「受理」の有無のみ
- 終端記号、被終端記号に「属性」を付ける
- 文法規則ごとに、その属性の割り出しを追加情報で定義
- 属性を解析木の下から上へと計算するのが普通が、
両方向に計算する仕組みも存在
- 例:
E0 → E1 '-' E3
S(E0
) = S(E1
) -
S(E3
)
bison の場合: $$ = $1 - $3
- 典型的な属性:
木の高さ、入力の長さ、式の評価値、部分解析木・構文木、生成されたコード
flex と bison の使い方の概要
- bison でトークンの種類を記述:
%token NUM PLUS ASTERISK
...
- bison で属性値の型の定義
#define YYSTYPE int
- flex でそれぞれのトークン用のルールの定義
- bison で文法規則の定義
- bison で文法の属性の規則の定義
- コンパイルとテスト
(全項目と合わせて行った方がよい)
make の活用
flex
, bison
, gcc
などを忘れず使うのが難しい
make
コマンドは makefile
の指定に従い、必要最小限の処理を実行
- 必要に応じて、make コマンドを cygwin で追加
- make ファイルの書き方 (→はタブの意味;
タブでないとだめ!):
target: input1 input2
input3 ...
→target 作成命令
- make だけ打つと makefile 内の最初の target が作られる