言語理論とコンパイラ
第四回:
正規表現と字句解析
2010 年 4 月 30 日
http://www.sw.it.aoyama.ac.jp/2010/Compiler/lecture4.html
Martin J. Dürst
© 2005-10 Martin
J. Dürst 青山学院大学
今日の内容
- DFA の簡単化
- 線形文法と有限オートマトン
- 正規表現
- 字句解析の概要
今週の展望
- 有限オートマトン (finite state automaton, FSA):
決定性有限オートマトン (deterministic finite automaton, DFA)
と非決定性有限オートマトン (non-deterministic finite
automaton, NFA)
- 正規文法 (regular grammar): 左線形文法 (left linear grammar) と
右線形文法 (right linear grammar)
- 正規表現 (regular expression)
これらは全て同じ力を持って、正規言語を定義・受理する
これらは字句解析に使われる
- 正規表現で字句解析の指定
- FSA は字句解析の実装
正規表現の例
計算機実習 I の演習問題: ある文章中に
&
, "
, '
,
<
, >
を見つけて、それぞれを
&
, "
, '
, <
,
>
に変換せよ。
Ruby で書くと次のようになる:
gsub /"/, '"'
gsub /'/, "'"
gsub /</, '<'
gsub />/, '>'
gsub /&/, '&'
正規表現の形式定義
アルファベットΣ 上の正規表現と表す言語
優先度 |
正規表現 |
条件 |
言語 |
備考 |
|
ε, a |
a ∈ Σ |
{ε} 又は {a} |
|
低い |
r|s |
r, s が正規表現 |
L(r|s) = L(r) ∪
L(s) |
集合和 |
低め |
rs |
r, s が正規表現 |
L(rs) =
L(r)L(s) |
連結 |
高め |
r* |
r が正規表現 |
L(r*) = (L(r))* |
閉含 |
高い |
(r) |
r が正規表現 |
L((r)) = L(r) |
|
L(r) は r
によって表されている言語。優先度は下の方が強い。
正規表現の意義
- 正規表現が定義する言語は文法でも書ける
- 文要は複数の書き換え規則なので描きにくい、分かりにくい
- 正規表現は一つなので、書きやすい、直観的で分かりやすい
正規表現自体の文法
- 正規表現自体も一つの言語
(全ての正規表現の集合)
- 正規言語ではなく、文脈自由言語
- 文法: R → ε, R →a, R →b,..., R
→R|R, R →RR,
R →R*, R →(R)
優先度に要注意
- abc* と (abc)*
- a|b|c* と (a|b|c)*
- ab|c と a(b|c)
正規表現の例
- ある一個の語だけ受理できる:
word
- ある記号の数が奇数 (
(aa)*a
)、偶数
((aa)*
)、3で割れば余りが 2
(aa(aaa)*
)、等
- 語の先頭に決まった記号列がある:
abc(a|b|c)*
- 語の終わりに決まった記号列がある:
(a|b|c)*abc
- 語の真ん中に・どこかに決まった記号列がある:
(a|b|c)*abc(a|b|c)*
正規表現から NFA へ (1)
正規表現に対応する NFA
は正規表現の部分表現から再帰的に作られる。
ε と a に対応する NFA
は初期状態一つと受理状態一つとそれを結ぶ ε 又は a
と書かれた矢印。
r|s の NFA は r の NFA と s の NFA から次のようにつくる:
正規表現から NFA へ (2)
rs の NFA は r の受理状態と s の初期状態を ε で結んで、r
の初期状態は rs の初期状態、s の受理状態は
rsの受理状態。
r* の NFA は次のようにつくる:
有限オートマトンから正規表現へ
変換は可能が、複雑
変換の原理:
- 状態 A から状態 B
へ直接遷移できる正規表現を全ての状態の組み合わせのために作る。
- 一個の状態だけを選んで、その状態の経由を含める正規表現を作る。
- 2.
のステップを繰り返して、経由できる状態を増やす。
- 途中で正規表現がどんどん複雑になるので、できる限り簡単化する
正規表現の応用
- コンパクトで簡単に色々なパターンが表現可能
- 理論が実装に結びつく
- 多くのプログラム言語で組み込まれている (Ruby,
Javascript, Perl, Python,...)
- 他の言語でライブラリとして使用可能 (Java, C#, C,...)
- エディタでもよくつかわれる
- 理論的な正規表現と実用化された正規表現は様々な面で異なる
実用化された正規表現: 記述上の違い
正規表現の便利な追加機能
(括弧内は相当の理論的な正規表現)
.
: 文字一個 (a|b|c|
...)
- r
+
: 一個以上の r
(rr*
)
- r
?
: r の有無 (r|ε,
その代わり ε は使わない)
- r
{
m,
n}
:
m 個以上 n 個以下の r
(r...rr?
...r?
)
[acdfh]
: 複数の文字から選択
(a|c|d|f|h
)
[b-f]
: b から f の字 (b|c|d|e|f
)
\*
等: \
はエスケープに使われる
実用化された正規表現: 使い方の違い
- 語全体のではなくその一部をマッチ
^
と $
で語 (行)
の先頭と最後をマッチ
- 括弧に相当する部分語を変数に代入可能
- マッチした部分の置き換え
- 変数の正規表現内の再利用
実用化された正規表現の注意点
- 能力は DFA/NFA/正規言語を超えている
- 実装がバックトラッキングを使用
- 一部の正規表現が一部の入力で非常に遅くなる
今週のまとめ
- 正規表現、線形・正規文法、有限オートマトンは皆同じ表現・受理能力を持つ。
- DFA による効率良い受理プログラムが作れる。
- 正規表現のコンパクトな定義方法がある。
- これらは字句解析に有効に使える。
- しかし、これらに表現できない言語がある。状態の有限な数によって、例えば一般の括弧の対応する言語は判定できない。
演習問題
(提出不要)
- 次の正規表現を NFA に変換し、NFA から DFA を作る:
(a|c*)a|b
- 1. の言語を定義する右線形文法を作る。
- Σ = {0, 1} の 0 が偶数 (1 は何個でもよい) 語を受理する
DFA を作る。
- (発展問題) 3. の言語の正規表現を作る。(ヒント:
変換するより正規表現を新たに作る方がいい)
次回の予定と準備
予定:
flex の演習
準備:
flex, bison, gcc の動作確認済みノート PC を持参