文法と言語
ー字句解析とオートマトンlexー
和田俊和
資料保存場所
http://vrl.sys.wakayama-u.ac.jp/~twada/syspro/
前回の復習1
• 言語とは,あるルールに従う記号列の無限集合である.
• 文法を与えることで言語が定義できる.
• 文法にはタイプ0からタイプ3までのレベルがあり,
– プログラム言語としてはタイプ2「文脈自由文法」
– プログラム中の字句の表現にはタイプ3「正規文法」
が用いられる.
• 文脈自由文法(Context Free Grammar:CFG)は,前後の記号に関
係なく「非終端記号1つ」を「非終端記号と終端記号から成る記号
列」に置き換えるという生成規則 A→t のみを持つ文法
• 出発記号に対して生成規則の要素を何度か適用して終端記号列を
得ることを「導出」と呼ぶ.
• 終端記号列と文法が与えられたとき,生成規則がどのように適用さ
れたのか,つまり,導出の過程を求めることを「構文解析」と呼び,
その結果,導出木が得られる.
• 導出木からそれを簡略化した「構文木(演算子木)」が求められ,そ
れを利用した式の計算などが可能である.
前回の復習2
• 正規文法は,「非終端記号1つ」を「終端記号」もしくは「終端記号
非終端記号」に置き換えるという生成規則 A→a or B→bB のみ
を持つ文法
• 正規文法で表現できるのは,「整数」「実数」「識別子(変数名)」
「キーワード」など,比較的単純な記号の並びである.
• 正規文法によって規定される言語は,正規表現によって表現するこ
とができる.
• 正規表現から,それに唯一に対応付けられる「非決定性有限状態
オートマトン(NFA)」が機械的に対応付けられる.
• その,機械的に求められたNFAは,計算機で実行可能な「決定性
有限状態オートマトン(DFA)」に変換することができ,さらに状態数
の最適化などが行われ,字句解析に用いられる.
オートマトンの利用例:字句解析
• 文字の連続的な並びに対して,字句の種類
ごとに分類しつつ,単一の字句の切り出し(セ
グメンテーション)も行う.
例
• while ( a14z < 100.4 ) a14z ...
予約語 左括弧 変数 比較演算子 実数 右括弧 変数名
• これは構文解析の前に行われる処理である.
前回の演習課題
(1| 2 | ...| 9)(0 |1| ...| 9) ( | .(0 |1| ...| 9) )
*
*
•最初の数字は1~9までの数字であり,
•引き続き0~9までの数字が0回以上繰り返される.
•これで終わりの場合もあるが,
•“.”が来て0~9までの数字が0回以上繰り返され
る場合もある.
正規表現からオートマトンへの変換規則
•
•a

i
a
i

•r|s
•
i
rs
i
f


f
N (r )

N (s)
N (r )
f


N (s)
f

•
r
*
i

N (r )



f
(1| 2|...|9)(0|1|...|9) ( |.(0|1|...|9) )
オートマトンへの変換

*
0
i









1
2
3
4
5
6
7
8
9





















1
2
3
4
5
6
7
8
9










*

0

.













1
2
3
4
5
6
7
8
9













f
オートマトンへの変換(簡単化)
a

a
x
x
a
a
b
x
b
x
c

c

0
1
c
c
i
2
3
4
5
6
7
8
9









1
2
3
4
5
6
7
8
9












0
1
2
3
4
5
6
7
8
9
.













f
NFAからDFAへ
• NFA(非決定性有限状態オートマトン)がDFA(決定性有限
状態オートマトン)と異なる点.
– 「ε遷移」がある.
– 同じ入力に対して複数の状態に遷移する「非決定性遷移」がある.
↓
これらを無くせば,DFAになる.
• 以下 “while”, “with”, “warp” の3つの文字列を受理する3
つのオートマトンを合成し,1つのNFAを作った後,これをDF
Aに変換する問題を例題として説明する.
複数のオートマトンの結合
• while with wrapという記号列を受理するオートマト
ンから (while|with|wrap)という正規表現に対応す
るオートマトンを生成すると,「ε遷移」を含むオート
マトンができる.
i
i
i
w
h
i
l
e
f
w
i
t
h
f
w
r
a
ε
p
i
ε
ε
f
w
w
w
h
i
l
e
ε
i
t
h
ε
r
a
p
ε
f
ε閉包(ε-closure)
• ある状態から無入力で遷移し得る状態の集合のこ
と.
• これら状態の集合を新たな状態として定義し直す
ことにより, ε遷移を含まないオートマトンができる.
ε
i
ε
ε
a w
b w
c
w
h
i
l
h
e
w ia
d ε
i
t
h
e
r
a
p
f
ε
ε
f
i
w ib
w ic
i
i
l
e
df
t
h
ef
r
a
p
ff
非決定的遷移の除去
• 同様に,ある状態に於いて,1つの入力記号が与
えられたときに遷移し得る状態集合をまとめること
により,非決定的遷移も消すことができる.
h
w
i
a
w b
w c
i
i
l
e
f
t
h
f
r
a
p
f
h
i w abc i
i
• これでDFAが得られたことになる.
e
f
t
h
f
a
r
l
p
f
このNFAを決定性に直す

i
1
a

0
1
b
c
d



e
.
0
1
f

g
h

Non-deterministic Finite Automaton
0
i
1
0
.
abef
1
fgif
fif
1
bdef
Deterministic Finite Automaton
i

f

bcef


fhif
終了状態はどこにある?
0
i
1
bcef
0
.
abef
1
fgif
fif
1
bdef
fhif
Deterministic Finite Automaton
•元の終了状態 f を含む状態を,終了状態とする.→終了状態からの状態遷移は
定義上おかしい.
•ε遷移で,終了状態に遷移させる.→DFAなのでε遷移はあってはいけない.
•各状態で受理しない記号を受け取った時点で終了状態に遷移する.→図として煩
雑なので,ここでは明示しない.
実装は大変!
lex(flex)を使えばオートマトンも簡単に作れる.
• lex は,正規文法を与えて,それを解析するオート
マトンを生成する,いわば「字句解析用オートマトン
生成プログラム」である.
• 正規文法だけでなく,文字列を受理した際に行う処
理をC言語で書くことができる.
{文法, C言語}
記号列
lex(flex)
字句解析器
区切られ,分類された字句
flex プログラムと使い方
入力ファイ %{
ルの構造 任意のC プログラム。定数やC のマクロの宣言をここにかく。
%}
下の定義で使うlex のマクロの定義
%%
正規表現による入力パターンの定義
正規表現パターン アクション という形で書く
%%
任意のC プログラム
test.l
使い方
%{
#include <stdio.h>
%}
%%
a(b|c)* { printf("OK\n"); } /*このパターンを入力したらOK を出力する*/
. {printf("NG\n");} /*.は以上以外のパターンの場合のアクションを指定 */
%%
lex test.l
gcc lex.yy.lex –ll
. /a.out
プログラム例
%{
#include <stdio.h>
%}
%%
a(b|c)* { printf("OK\n"); } /*このパターンを入力したらOK を出力する*/
. {printf("NG\n");} /*.は以上以外のパターンの場合のアクションを指定 */
%%
実行例
./a.out
abcbabcbabcbbabbc
OK
OK
OK
OK
flex プログラムの例2
%{
/* You can put any C code here. */
#define END
0
/* end of input */
#define BADTOKEN
1
/* invalid token */
#define NUMBER
2
/* decimal number */
#define HEXNUMBER
3
/* hex number */
#define RETURN
4
#define PLUS
5
#define MINUS
6
%}
%%
[0-9]+
return NUMBER;
0[Xx][0-9A-Fa-f]+
return HEXNUMBER;
“+”
return PLUS;
“-”
return MINUS;
“\n”
return RETURN;
[ \t]+
;
/* ignore */
.
return BADTOKEN;
%%
#include <stdio.h>
#include <stdlib.h>
/* for printf() */
/* for atoi() */
main()
{
extern char* yytext;
int token;
while((token = yylex()) != END){
printf("token no.%d: ", token);
switch(token){
case NUMBER :
printf("%d\n", atoi(yytext));
break;
case RETURN :
printf("return\n");
break;
default :
puts(yytext);
break;
}
}
}
正規表現と実行例
[0-9]+
0[Xx][0-9A-Fa-f]+
“+”
“-”
“\n”
[ \t]+
.
return NUMBER;
return HEXNUMBER;
return PLUS;
return MINUS;
return RETURN;
;
/* ignore */
return BADTOKEN;
-bash-3.1$ ./a.out
19+0x12+a
←入力
token no.2: 19
token no.5: +
token no.3: 0x12
token no.5: +
token no.1: a
token no.4: return
本日の演習課題
(what | where | when | why | who | how)
という正規表現を受理するオートマトンを,
• 個々の文字列を受理するオートマトンをε遷移
で束ねたNFA
• 決定性に変換したDFA
に分けて,それぞれ示しなさい.
ダウンロード

class-2