言語理論とコンパイラ
第十回:
上向き構文解析の詳細
2015 年 6 月 12 日
http://www.sw.it.aoyama.ac.jp/2015/Compiler/lecture10.html
Martin J. Dürst
© 2005-15 Martin
J. Dürst 青山学院大学
今日の予定
- 宿題について
- 前回のまとめ
- 導出の順番
bison
内の仕組み
前回のまとめ
bison
と flex
でプログラムを作成すると段階が多くて、make
が大事
bison
の入力形式は flex
と非常に似ているが、差異も
bison
は属性文法で構文解析の結果を算出
- 属性は C 言語の部分の中で
$$
、$1
、$2
などで参照
- 演算子の優先度と結合規則が文法の書き換え規則の形で決定
優先度の取扱い
- 優先度ごとに非終端記号を使用
- 優先度の低い方 (外側) から文法を書く
- 優先度の低い方の右側に同じ優先度
または一つ高い優先度の非終端記号を使用
- 名前の選び方:
- 数学の用語 (term (項)、factor (因子) など)
- 名前の一部に演算子の種類や主な演算子
(shift_expression, mulExpression など)
結合方向の取扱い
- 左結合の場合:
- 書き換え規則の左側の非終端記号を演算子の左側
- 優先度が一つ高い非終端記号を演算子の右側
- 右結合の場合には左右逆
繰返しの取扱い
- 例: 文のリスト (
statementList
)
- 0以上の繰り返し: 右が空の書換規則
- 1以上の繰り返し: 右が 1つだけの要素 (例:
statement
) の書換規則
- 右がリストと要素の書換規則
- リストが左の場合、左再帰、上向き構文解析の場合、早めにまとめるのが可能
- リストが右の場合、右再帰
導出の順番: 最左導出と最右導出
最左導出の場合に、いつも最も左の非終端記号を置き換える
最右導出の場合に、いつも最も右の非終端記号を置き換える
簡単な文法の例:
E → E '+' T
T → integer
入力例: 5 + 7 + 3
解析方法の種類
- LL: 左から入力を読んで、最左導出
- LR: 左から入力を読んで、最右導出 (逆順)
- LL(1): LL で、先読みはトークン一つに限定
- LR(1): LR で、先読みはトークン一つに限定
- LALR: LR (1) の一種で、yacc、bison など幅広く使用
ラベルは文法にも使用: 「この文法は
LL(1)」(で解析可能)
bison
の仕組みとデバッグ
bison -v
で作った機械の明細のファイルを作成
(calc.y → calc.output)
#define YYDEBUG 1
でデバッグを有効に
.output
の読み方
- Grammar: 文法 (書換規則ごとに番号)
- Terminals: 終端記号 (番号は ASCII コード又は 256以上)
- Nonterminals: 非終端記号
- State: 状態毎に次の情報:
- 書換規則 (
.
は現在の位置)
- 終端記号 (又は $default (その他)):
その終端記号が次に来る場合の動作
- 非終端記号: reduce 後の遷移先
LALR 構文解析の原理
スタックを使って読んだトーケンや途中の非終端記号を蓄積
オートマトンを使ってできるだけ簡単な操作で次のステップを決定
(LA)LR 構文解析の三つのオペレーション
- shift:
トークンを一個読んで、そのトークンをスタックにステートと一緒に詰める
- reduce:
スタックの上部にあるトークンや非終端記号を書換規則で一つの非終端記号に変換
reduce 後には go to で状態遷移
- accept: 入力を受理、作業を終了
bison
の書換規則の文法
書換規則 → 非終端記号 ":
" 右辺列
";
"
右辺列 → 右辺 | 右辺 "|
" 右辺列
右辺 → 記号列 "{
" C言語 "}
"
記号列 → 記号 | 記号 記号列
記号 → 非終端記号 | 終端記号
flex
と bison
の使い方の概要
- bison でトークンの種類を記述:
%token NUM PLUS ASTERISK
...
- bison で属性値の型を定義
#define YYSTYPE int
- flex でそれぞれのトークン用のルールを定義
- bison で文法規則を定義
- bison で文法の属性の規則を定義
- コンパイルとテスト
(全項目と合わせて行った方がよい)
曖昧な文法
- 文法の例:
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
上向き構文解析の利点と問題点
- 利点:
- 左再起に対応可能
- 幅広い文法に対応可能
- 実装が自動化
- 問題点:
宿題
提出: 再来週の木曜日 (6 月 25日) 19 時 00 分、O 棟 529
号室の前
簡単な電卓を有理数の電卓に拡張、有理数の表現方法として、[
分子,
分母]
を追加
[] 内には割り算は許されないように文法を設計
出力は約分したもの (例: Result: -53/17
)
優先度と結合規則は文法で定義 (%left
,
%right
など使用禁止)
.lex
と .y
のファイルを印刷して提出 (等幅の書体、A4
両面印刷
(↓↑のではなく↓↓)、表紙なし、左上ホチキス止め、名前、フリガナ、学生番号を1面の右上のコメントに記述)
来週質問が可能なので、よく準備して質問できるようにすること!
宿題のヒント
- calc などからスタート、しかしファイル名を変更
(
makefile
内も)
YYSTYPE
は有理数が表現できるように変更
(.lex
と .y
両方)
.y
で追加のトークンを定義
.lex
で追加のトークンを認識
- 数値の読み込みでは浮動小数点数への対応が不必要
- shift/reduce conflict や reduce/reduce conflict の場合
.output
ファイルを見て検討
- 実際の入力で試してみる
- 文法を少しづつ拡張しながら念入りにテスト
- テストファイルを作成、文法を拡張するたびにテストを追加
- 関心の項目 (優先順位、結合規則)
を仕分けるテストを作成
- テストの結果を取っておいて、次のテストの結果と比較