第三章 词法分析
正则表达式
正则表达式(RE)是一种用来描述正则语言的更紧凑的表示方法。正则表达式可以由较小的正则表达式按照特定的规则递归地构建,每个正则表达式r定义(表示)一个语言,记为 L ( r ) L(r) L(r),这个语言也是根据r的子表达式所表示的语言递归定义的。
正则表达式的定义
-
ε \varepsilon ε是一个RE, L ( ε ) = { ε } L(\varepsilon)=\{\varepsilon\} L(ε)={ε}
-
如果 a ∈ ∑ a\in\sum a∈∑,则 a a a是一个RE, L ( a ) = { a } L(a)=\{a\} L(a)={a}
-
假设r和s都是RE,表示的语言分别是 L ( r ) L(r) L(r)和 L ( s ) L(s) L(s),则
- r ∣ s r|s r∣s是一个RE, L ( r ∣ s ) = L ( r ) ∪ L ( s ) L(r|s)=L(r)\cup L(s) L(r∣s)=L(r)∪L(s)
- r s rs rs是一个RE, L ( r s ) = L ( r ) L ( s ) L(rs)=L(r)L(s) L(rs)=L(r)L(s)
- r ∗ r^* r∗是一个RE, L ( r ∗ ) = ( L ( r ) ) ∗ L(r^*)=(L(r))^* L(r∗)=(L(r))∗
- ( r ) (r) (r)是一个RE, L ( ( r ) ) = L ( r ) L((r))=L(r) L((r))=L(r)
运算优先级:*、连接、|
例:C语言无符号整数的RE
- 十进制整数: ( 1 ∣ . . . ∣ 9 ) ( 0 ∣ . . . ∣ 9 ) ∗ ∣ 0 (1|...|9)(0|...|9)^*|0 (1∣...∣9)(0∣...∣9)∗∣0
- 八进制整数: 0 ( 0 ∣ 1 ∣ 2 ∣ . . . ∣ 7 ) ( 0 ∣ 1 ∣ . . . ∣ 7 ) ∗ 0(0|1|2|...|7)(0|1|...|7)^* 0(0∣1∣2∣...∣7)(0∣1∣...∣7)∗
- 十六进制整数: 0 x ( 0 ∣ 1 ∣ . . . ∣ F ) ( 0 ∣ . . . ∣ F ) ∗ 0x(0|1|...|F)(0|...|F)^* 0x(0∣1∣...∣F)(0∣...∣F)∗
正则语言
可以用RE定义的语言叫做正则语言(regular language)或正则集合(regular set)
RE的代数定律
定律 | 描述 |
---|---|
$r | s=s |
$r | (s |
r ( s t ) = ( r s ) t r(st)=(rs)t r(st)=(rs)t | 连接是可以结合的 |
$r(s | t)=rs |
ε r = r ε = r \varepsilon r=r\varepsilon=r εr=rε=r | ε \varepsilon ε是连接的单位元 |
$r^*=(r | \varepsilon)^*$ |
r ∗ ∗ = r ∗ r^{**}=r^* r∗∗=r∗ | *具有幂等性 |
正则文法与正则表达式等价
- 对任何正则文法G,存在定义同一语言的正则表达式r
- 对任何正则表达式r,存在定义同意语言的正则文法G
正则定义(Regular Definition)
正则定义是具有如下形式的定义序列:
d
1
→
r
1
d
2
→
r
2
.
.
.
d
n
→
r
n
d_1\to r_1\\ d_2\to r_2\\ ...\\ d_n\to r_n
d1→r1d2→r2...dn→rn
其中:每一个di都是一个新符号,它们都不在字母表
∑
\sum
∑中,而且各不相同。每个
r
i
r_i
ri是字母表
∑
∪
{
d
1
,
d
2
,
.
.
.
,
d
i
−
1
}
\sum\cup\{d_1,d_2,...,d_{i-1}\}
∑∪{d1,d2,...,di−1}上的正则表达式。
例1:C语言中标识符的正则定义
- d i g i t → 0 ∣ 1 ∣ 2 ∣ . . . ∣ 9 digit\to 0|1|2|...|9 digit→0∣1∣2∣...∣9
- l e t t e r _ → A ∣ B ∣ . . . ∣ Z ∣ a ∣ b ∣ . . . ∣ z ∣ _ letter\_\to A|B|...|Z|a|b|...|z|\_ letter_→A∣B∣...∣Z∣a∣b∣...∣z∣_
- i d → l e t t e r ( l e t t e r _ ∣ d i g i t ) ∗ id\to letter_(letter\_|digit)^* id→letter(letter_∣digit)∗
例2:整型或浮点型无符号数的正则定义
- d i g i t → 0 ∣ 1 ∣ 2 ∣ . . . ∣ 9 digit\to0|1|2|...|9 digit→0∣1∣2∣...∣9
- d i g i t s → d i g i t d i g i t ∗ digits\to digit\ digit^* digits→digit digit∗
- o p t i o n a l F r a c t i o n → . d i g i t ∣ ε optionalFraction\to.digit|\varepsilon optionalFraction→.digit∣ε
- o p t i o n a l E x p o n e n t → ( E ( + ∣ − ∣ ε ) d i g i t s ) ∣ ε optionalExponent\to(E(+|-|\varepsilon)digits)|\varepsilon optionalExponent→(E(+∣−∣ε)digits)∣ε
- n u m b e r → d i g i t s o p t i o n a l F r a c t i o n o p t i o n a l E x p o n e n t number\to digits\ optionalFraction\ optionalExponent number→digits optionalFraction optionalExponent
有穷自动机
有穷自动机(Finite Automata,FA)由两位神经物理学家于1948年首先提出,是对一类处理系统建立的数学模型;这类系统具有一系列离散的输入输出信息和有穷数目的内部状态(状态:概括了对过去输入信息处理的状况),系统只需要根据当前所处的状态和当前面临的输入信息就可以决定系统的后继行为。每当系统处理了当前的输入后,系统的内部状态也将发生改变。例如电梯控制装置,不需要记住先前全部的服务要求,只需要知道电梯当前所处状态以及还没有满足的所有服务请求。
FA模型
- 输入带(input tape):用来存放输入符号串
- 读头(head):从左向右逐个读取输入符号,不能修改、不能往返移动
- 有穷控制器(finite control):具有有穷个状态数,根据当前的状态和当前输入符号控制转入下一状态。
FA的表示
转换图(Transition Graph)
结点:FA的状态
- 初始状态(开始状态):只有一个,由start箭头指向
- 终止状态(接收状态):可以有多个,用双圈表示
带标记的有向边:如果对于输入a,存在一个从状态p到状态q的转换,就在p、q之间画一条有向边,并标记上a
例:
FA定义(接收)的语言
给定输入串x,如果存在一个对应于串x的从初始状态到某个终止状态的转换序列,则称串x被该FA接收。
由一个有穷自动机M接收的所有串构成的集合称为是该FA定义(或接收)的语言,记为 L ( M ) L(M) L(M),上图自动机定义的语言是所有以abb结尾的字母表{a,b}上的串的集合。
最长字串匹配原则(Longest String Matching Principle)
当输入串的多个前缀与一个或多个模式匹配时,总是选择最长的前缀进行匹配;在到达某个终态之后,只要输入带上还有符号,DFA(确定的有穷自动机)就继续前进,以便寻找尽可能长的匹配。
有穷自动机的分类
确定的FA(Deterministic finite automata,DFA)
M = ( S , Σ , δ , s 0 , F ) M=(S,\Sigma,\delta,s_0,F) M=(S,Σ,δ,s0,F)
S:有穷状态集
Σ \Sigma Σ:输入字母表,即输入符号集合。假设 ε \varepsilon ε不是 Σ \Sigma Σ中的元素
δ \delta δ:将 S × Σ S\times\Sigma S×Σ映射到S的转换函数。 ∀ s ∈ S , a ∈ Σ , δ ( s , a ) \forall s\in S,a\in\Sigma,\delta(s,a) ∀s∈S,a∈Σ,δ(s,a)表示从状态s出发,沿着标记为a的边所能到达的状态。
s 0 s_0 s0:开始状态(或初始状态), s 0 ∈ S s_0\in S s0∈S
F F F:接收状态(或终止状态)集合, F ⊆ S F\subseteq S F⊆S
DFA例
它的转换表为
状态/输入 | a | b |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 1 | 0 |
非确定的FA(Nondeterministic finite automata,NFA)
M = ( S , Σ , δ , s 0 , F ) M=(S,\Sigma,\delta,s_0,F) M=(S,Σ,δ,s0,F)
S:有穷状态集
Σ \Sigma Σ:输入字母表,即输入符号集合。假设 ε \varepsilon ε不是 Σ \Sigma Σ中的元素
δ \delta δ:将 S × Σ S\times\Sigma S×Σ映射到2S的转换函数。 ∀ s ∈ S , a ∈ Σ , δ ( s , a ) \forall s\in S,a\in\Sigma,\delta(s,a) ∀s∈S,a∈Σ,δ(s,a)表示从状态s出发,沿着标记为a的边所能到达的状态。
s 0 s_0 s0:开始状态(或初始状态), s 0 ∈ S s_0\in S s0∈S
F F F:接收状态(或终止状态)集合, F ⊆ S F\subseteq S F⊆S
NFA例:
转换表为
状态/输入 | a | b |
---|---|---|
0 | {0,1} | {0} |
1 | ∅ \emptyset ∅ | {2} |
2 | ∅ \emptyset ∅ | {3} |
3 | ∅ \emptyset ∅ | ∅ \emptyset ∅ |
DFA和NFA的等价性
对于任何非确定的有穷自动机N,存在定义同一语言的确定的有穷自动机D;对于任何确定的有穷自动机D,存在定义同一语言的非确定的有穷自动机N。上述DFA和NFA的例子就是同一种语言, r = ( a ∣ b ) ∗ a b b r=(a|b)^*abb r=(a∣b)∗abb,正则文法 ⇔ \Leftrightarrow ⇔正则表达式 ⇔ \Leftrightarrow ⇔FA
带有“ ε − \varepsilon- ε−边”的NFA
δ \delta δ:将 S × ( Σ ∪ { ε } ) S\times(\Sigma\cup\{\varepsilon\}) S×(Σ∪{ε})映射到2S的转换函数。 ∀ s ∈ S , a ∈ Σ ∪ { ε } , δ ( s , a ) \forall s\in S,a\in\Sigma\cup\{\varepsilon\},\delta(s,a) ∀s∈S,a∈Σ∪{ε},δ(s,a)表示从状态s出发,沿着标记为a的边所能到达的状态集合。
例:
语言为 r = 0 ∗ 1 ∗ 2 ∗ r=0^*1^*2^* r=0∗1∗2∗
同样与不带 ε \varepsilon ε-边的NFA具有等价性
DFA算法实现
输入:以文件结束符eof结尾的字符串x;DFA的开始状态 s 0 s_0 s0,接收状态集F,转换函数move
输出:如果D接收x,则回答yes,否则回答no
s = s0;
c = nextChar();//返回输入串x的下一个符号
while(c != eof){
s = move(s,c);//从状态s出发,沿着标记为c的边所能到达的状态
c = nextChar();
}
if(s在F中)
return yes;
else
return no;
从正则表达式到有穷自动机
-
r = r 1 r 2 r=r_1r_2 r=r1r2对应的NFA
-
r = r 1 ∣ r 2 r=r_1|r_2 r=r1∣r2对应的NFA
-
r = ( r 1 ) ∗ r=(r_1)^* r=(r1)∗对应的NFA
从NFA到DFA的转换
DFA的每个状态都是一个由NFA中的状态构成的集合,即NFA状态集合的一个子集,写出转换表。
例1:
NFA:
DFA:
r = a a ∗ b b ∗ c c ∗ r=aa^*bb^*cc^* r=aa∗bb∗cc∗
子集构造法
基本思想:构造得到的DFA的每个状态对应于NFA的一个状态集合
方法:为DFA构造一个转换表Dtran,使得Dtran“并行地”模拟NFA在遇到一个输入串时可能执行的所有动作。就是说D中的一个状态可能对应N中的多个状态,并且当N中某个状态遇到一个输入串执行的动作,在D中该状态存在的状态集合也执行这个动作。构造出这样一个表格,问题就迎刃而解了。具体步骤如下
ε − c l o s u r e ( s ) \varepsilon-closure(s) ε−closure(s):能够从NAF的状态s开始,只通过 ε \varepsilon ε转换到达的NAF状态集合
ε − c l o s u r e ( T ) \varepsilon-closure(T) ε−closure(T):能够从T中某个NFA状态s开始,只通过 ε \varepsilon ε转换到达的NFA状态集合,即 ∪ s ∈ T ε − c l o s u r e ( s ) \cup_{s\in T}\varepsilon-closure(s) ∪s∈Tε−closure(s)
m o v e ( T , a ) move(T,a) move(T,a):能够从T中某个状态s出发通过标号为a的转换到达的NFA状态的集合
一开始, ε − c l o s u r e ( s 0 ) \varepsilon-closure(s_0) ε−closure(s0)时Dstates中唯一状态,且未加标记
while(在Dstates中有一个未标记状态T){
给T加上标记;
for(每个输入符号){
U = U= U= ε − c l o s u r e ( m o v e ( T , a ) ) \varepsilon-closure(move(T,a)) ε−closure(move(T,a));//输入符号为a,并再执行几个 ε \varepsilon ε转换
if(U不在Dstates中)
将U加入到Dstates中,且不加标记;
Dtran[T,a]=U;
}
}
识别单词的DFA
识别标识符的DFA
识别无符号数的DFA
n u m b e r → d i g i t s o p t i o n a l F r a c t i o n o p t i o n a l E x p o n e n t number\to digits\ optionalFraction \ optionalExponent number→digits optionalFraction optionalExponent
n u m b e r → d i g i t d i g i t ∗ ( ( . d i g i t d i g i t ∗ ) ∣ ε ) ( ( E ( + ∣ − ∣ ε ) d i g i t d i g i t ∗ ) ∣ ε ) number\to digit\ digit^*\ ((.\ digit\ digit^*)|\varepsilon)((E(+|-|\varepsilon)digit \ digit^*)|\varepsilon) number→digit digit∗ ((. digit digit∗)∣ε)((E(+∣−∣ε)digit digit∗)∣ε)
识别各进制无符号整数的DFA
识别注释的DFA
识别Token的DFA
词法分析阶段的错误处理
可检测的错误:单词拼写错误、非法字符
错误处理:查找已扫描字符串中最后一个对应于某终态的字符,如果找到了,将该字符与前面的字符识别成一个单词。然后将输入指针退回到该字符,扫描器重新回到初始状态,继续识别下一个单词。
如果没找到,则确定出错,采用错误恢复策略:恐慌模式(最简单的错误回复策略),从剩余的输入中不断删除字符,直到词法分析器能够在剩余输入的开头发现一个正确的字符为止。