LexとYacc


準備

コンパイラの概要

コンパイラは、いくつかのフェーズからなる

今回扱うのは、この中の字句解析(単語の切分け)と構文解析(木構造の構築)である。

正規表現

記号列(文字列)の集合を定義するための方法の一つである。字句解析に使用される。

正規表現の定義

A を想定する文字の集合とする

  1. A の要素は、 A の上の正規表現である
  2. 空の記号列 ε は、 A の上の正規表現である
  3. x, yA の上の正規表現であるとき、
    1. x yxy の連接)
    2. x | yxy の選択)
    3. x *x の反復)
    A の上の正規表現である。

優先順位

反復、連接、選択の順に優先順位が高い。

つまり、ab*|c(a(b*))|c と解釈される。 これ以外の順に結合するには、(ab)*|cのように括弧を用いる。

省略記法

斜字体(例: x )は、任意の正規表現、azは文字

注意

正規表現は入れ子関係(例: 括弧の釣り合った文字列)は表現できない。(一方、BNFはできる。)

練習問題

  1. C言語の変数として使える文字列を表す正規表現を書け。
  2. C言語の整数リテラル・浮動小数点リテラルを表す正規表現を書け。
  3. メールアドレスとなる可能性のある文字列・URLとなる可能性のある文字列 を表す正規表現を書け。(完全でなくても良い。)
  4. 危険なパス(/から始まったり、..を含むパス)を 表す正規表現を書け。

LexとJFlex

BNF

Backus=Naur Form(バッカス=ナウア記法)のこと。 やはり、記号列(文字列)の集合を定義するための方法の一つである。 再帰的な定義が可能なため、正規表現より表現力が高い。 構文解析に使用される。

BNFの説明

BNFの例

expr expr + num
| expr - num
| num
num 0|1||9

1+2+3expr である」ことを示すためには、

exprexpr + numexpr + 3expr + num + 3expr + 2 + 3num + 2 + 31 + 2 + 3

という列を示せば良い。

解析木

導出列を木の形に表したものを解析木という。 書換え規則の適用順の違いという本質的でない違いを無視することができる。

最右導出

最も右側に出現する非終端記号を選択して、書き換えていく導出列のこと。 例えば、上の expr ⇒ … ⇒ 1 + 2 + 3 という導出列は 最右導出である。一方、次の導出列は最右導出ではない

exprexpr + numexpr + num + numnum + num + num1 + num + num1 + 2 + num1 + 2 + 3

この導出列は、最も左側に出現する非終端記号を選択して、書き換えているので、 最左導出という。

曖昧な文法

一つの記号列に対して 2つ以上の解析木が生成される可能性がある文法のこと。 例えば次のような BNF で表現される文法は曖昧である。

expr expr + expr
| expr - expr
| num
num 0|1||9
例えば、1+3-2という記号列に対して、 2つの解析木を対応させることができる。

優先順位と結合性

曖昧な文法は、利用価値がないというわけではない。 演算子の優先順位や結合性を指定して非曖昧にすることができる。

優先順位

例えば *,/+,-より優先順位が高い、などという。 このとき、1+2*31+(2*3)と解釈される。

結合性

例えば +, -左結合である、という風に使う。 つまり、(同じ優先順位の演算子の中で)左側に現れた演算子が優先であり、 3-2-13-(2-1) ではなく(3-2)-1 と解釈される。

左結合の他に、右側に現れた演算子を優先する右結合、 演算子が続けて現れると文法エラーと解釈する非結合がある。

上向き構文解析

解析木を下から上へ(枝から幹へ)という順で生成していく 構文解析方法のことをいう。 Yacc, Bison, Jaccなどが採用しているのは上向き構文解析である。

BisonとJacc

シフト (shift) と還元 (reduce)

Bisonなどで生成した構文解析プログラムの動作の途中で、 次の字句(単語)を読み込む動作をシフト、 解析木の一部を生成する動作を還元という。

衝突 (conflict)

Bisonなどに与えた文法が曖昧だと、シフト/還元衝突 (shift/reduce conflict)、あるいは、 還元/還元衝突 (reduce/reduce conflict) があると言って Bisonに怒られる。

適宜、演算子の優先順位や結合性を宣言して、曖昧さを解消する必要がある。

宣言してもシフト/還元衝突が解消しないときは、警告を出しつつ、 Bisonはシフトのほうを採用する。例えば、次のような BNF で起こり得る。

stmt  →  if ( expr ) stmt
 | if ( expr ) stmt else stmt
 |

また、還元/還元衝突に関しては、Bisonは先に書かれている生成規則を優先する。 例えば、次のような BNF で起こり得る。

E →  E ^ E _ E
 | E ^ E
 | E _ E
 | id

練習問題

  1. 四則演算に次の演算子を追加した簡易電卓プログラムを作成せよ。

  2. プログラミング言語 PL/0の文法に対する 構文解析プログラムを作成せよ。さらに C (または Java) へのトランスレーター を作成せよ

その他の構文解析法

手書きの構文解析プログラムに適した再帰下降(下向き)構文解析法 (プログラム例)、 四則演算の式くらいなら表を用いる単純な方法で構文解析できる演算子順位法 (プログラム例) などがある。

Haskell や Scala などの関数型言語では、 パーサーコンビネーター (parser combinator) という手法もよく用いられる。

リンク集


Koji Kagawa