赞
踩
词法分析器读取有字符串组成的输入流,并产生包含单词的输出流,每个单词都标记了其语法范畴(syntactic category)或类型,等效于英文单词的词类。为了完成这种聚集和分类操作,词法分析器会应用一组描述输入程序设计语言的词法结构(也称微语法,microsyntax)的规则。程序设计语言的微语法规定了如何将字符组合为单词,以及反过来如何分开混合在一起的各个单词。
考虑识别关键字new的问题,首先检查第一个字符是否为n,再次检查是否后接e,最后e是否后接w。在每一步,未能匹配到适当的字符都将导致不能识别单词,编译器此时会输出一个错误信息并返回失败。整个对new关键字的识别可用转移图(transition diagram)来表示。
该转移图表示了一个识别器。每个圆圈都表示计算中的一个抽象状态(status)。为了方便起见,每个状态都标记起来。初始状态或起始状态为 S 0 S_0 S0,我们始终将起始状态标记为 S 0 S_0 S0。状态 S 3 S_3 S3是接受状态,仅当输入为new时,识别器才会到达 S 3 S_3 S3。接受状态以双层圆圈绘制,如上图所示。箭头表示根据输入字符的不同,所发生的状态之间的转移。如果识别器始于状态 S 0 S_0 S0,然后读取了字符n、e和w,那么最终会转移到状态 S 3 S_3 S3。
使用同样的方法为while和not构建识别器,将产生的识别器合并后的转移图,如下:
转移图还可以看做是形式化的数学对象,成为有限自动机,它定义了识别器的规格。形式上,有限自动机(FA)是一个五元组 ( S , Σ , δ , S 0 , S A ) ( S,\Sigma,\delta,S_0,S_A) (S,Σ,δ,S0,SA),其中各分量的含义如下:
我们可以将识别new/not/while的FA形式如下:
S = S 0 , S 1 , S 2 , S 3 , S 4 , S 5 , S 6 , S 7 , S 8 , S 9 , S 10 , S e S = {S_0, S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_8, S_9, S_{10}, S_e} S=S0,S1,S2,S3,S4,S5,S6,S7,S8,S9,S10,Se
Σ = e , h , i , l , n , o , t , w \Sigma = {e, h, i, l, n, o, t, w} Σ=e,h,i,l,n,o,t,w
δ
=
{
\delta = \{
δ={
S
0
(
n
)
→
S
1
,
S_0(n) \rightarrow S_1,
S0(n)→S1,
S
0
(
w
)
→
S
6
,
S_0(w) \rightarrow S_6,
S0(w)→S6,
S
1
(
e
)
→
S
2
,
S_1(e) \rightarrow S_2,
S1(e)→S2,
S
1
(
o
)
→
S
4
,
S_1(o) \rightarrow S_4,
S1(o)→S4,
S
2
(
w
)
→
S
3
,
S_2(w) \rightarrow S_3,
S2(w)→S3,
S
4
(
t
)
→
S
5
,
S_4(t) \rightarrow S_5,
S4(t)→S5,
S
6
(
h
)
→
S
7
,
S_6(h) \rightarrow S_7,
S6(h)→S7,
S
7
(
i
)
→
S
8
,
S_7(i) \rightarrow S_8,
S7(i)→S8,
S
8
(
l
)
→
S
9
,
S_8(l) \rightarrow S_9,
S8(l)→S9,
S
9
(
e
)
→
S
10
S_9(e) \rightarrow S_{10}
S9(e)→S10
}
\}
}
S 0 = S 0 S_0=S_0 S0=S0
S A = { S 3 , S 5 , S 10 } S_A=\{ S_3, S_5, S_{10} \} SA={S3,S5,S10}
对于状态 s i s_i si和输入字符 c c c的所有其他组合,我们定义 δ ( s i , c ) = S e \delta(s_i, c)=S_e δ(si,c)=Se, S e S_e Se是指定的错误状态。这种五元组与转移图是等价的,给出一种表示,我们可以轻易地重建另一种表示。转移图就是描绘了对应FA的一副图画。
并且仅当字符串从 S 0 S_0 S0开始时,FA接受字符串Str,该字符串中的字符序列可以使FA经历一系列状态转移,在处理整个字符串后FA到达某个接受状态。
更形式化地讲,如果字符串Str有字符 c 1 , c 2 , c 3 , . . . , c n c_1, c_2, c_3, ..., c_n c1,c2,c3,...,cn组成,那么FA ( S , Σ , δ , S 0 , S A ) (S, \Sigma, \delta, S_0, S_A) (S,Σ,δ,S0,SA)接受Str的充分必要条件是:
δ ( . . . δ ( δ ( δ ( S 0 , c 1 ) , c 2 ) , c 3 ) . . . , c n ) ∈ S A \delta( ... \delta(\delta(\delta(S_0, c_1), c_2), c_3)..., c_n) \in S_A δ(...δ(δ(δ(S0,c1),c2),c3)...,cn)∈SA
手工构建的 F A FA FA都不包括 ϵ \epsilon ϵ(空字符串),但一些正则表达式(Regular Expression,RE)确实用到了 ϵ \epsilon ϵ。在 F A FA FA中 ϵ \epsilon ϵ发挥什么作用?我们可以使用针对 ϵ \epsilon ϵ输入的转移来合并 F A FA FA,并组成用于更复杂RE的FA。例如,假定我们有用于m和n的两个RE的 F A FA FA,分别称为 F A m FA_m FAm和 F A n FA_n FAn。如下图所示:
在 F A m FA_m FAm的接受状态添加一个针对输入的 ϵ \epsilon ϵ的转移,转移到 F A n FA_n FAn的初始状态,把各个状态重新编号,然后使用 F A n FA_n FAn的接受状态作为最新 F A FA FA的接受状态,这样构建用于处理 m n mn mn的 F A FA FA。如下图所示:
随着 ϵ \epsilon ϵ转移的引入,“接受”的定义必须稍微改变一下,以允许输入字符串中任何两个字符串之间出现一次或多次 ϵ \epsilon ϵ转移。例如,在状态 s 1 s_1 s1,该 F A FA FA会采用转移 s 1 ( ϵ ) → s 2 s_1(\epsilon) \rightarrow s_2 s1(ϵ)→s2,而不会消耗任何字符。这是一个较小的变更,但看起来颇为合乎直觉。目测显示我们可以合并 s 1 s_1 s1和 s 2 s_2 s2,以消除 ϵ \epsilon ϵ转移。如下图:
利用 ϵ \epsilon ϵ转移合并两个 F A FA FA,可能会使我们关于 F A FA FA工作方式的模型复杂化。考虑用于语言 a ∗ a^* a∗和 a b ab ab的 F A FA FA。
我们可以用 ϵ \epsilon ϵ转移合并它们,形成一个处理 a ∗ a b a^*ab a∗ab的 F A FA FA。
实际上, ϵ \epsilon ϵ转移的引入,使得在状态 s 0 s_0 s0遇到输入字符 a a a时, F A FA FA可以选择两种不同的转移。它可以采用转移 s 0 ( a ) → s 0 s_0(a) \rightarrow s_0 s0(a)→s0,或采用两个转移: s 0 ( ϵ ) → s 1 s_0(\epsilon) \rightarrow s_1 s0(ϵ)→s1和 s 1 ( a ) → s 2 s_1(a) \rightarrow s_2 s1(a)→s2。哪种转移是正确的?考虑字符串 a a b aab aab和 a b ab ab。这个 F A FA FA应该可以接受这两个字符串。
对于
a
a
b
aab
aab,它应该采用的转移是:
s
0
(
a
)
→
s
0
,
s
0
(
ϵ
)
→
s
1
,
s
1
(
a
)
→
s
2
,
s
2
(
b
)
→
s
3
s_0(a) \rightarrow s_0,s_0(\epsilon) \rightarrow s_1,s_1(a) \rightarrow s_2,s_2(b) \rightarrow s_3
s0(a)→s0,s0(ϵ)→s1,s1(a)→s2,s2(b)→s3
对于
a
b
ab
ab,应该采用的转移是:
s
0
(
ϵ
)
→
s
1
,
s
1
(
a
)
→
s
2
,
s
2
(
b
)
→
s
3
,
s_0(\epsilon) \rightarrow s_1,s_1(a) \rightarrow s_2,s_2(b) \rightarrow s_3,
s0(ϵ)→s1,s1(a)→s2,s2(b)→s3,
正如这两个字符串所说明的,从状态
s
0
s_0
s0采取的正确转移,取决于
a
a
a之后的字符。在每一步,
F
A
FA
FA都会检查当前字符。
F
A
FA
FA的状态隐含了左上下文(left context),即它已经处理过了的那些字符。因为
F
A
FA
FA必须在检查下一个字符之前进行转移,所以诸如
s
0
s_0
s0这样的状态,背离了我们对顺序算法行为的观念。如果一个
F
A
FA
FA包含了
s
0
s_0
s0这样的状态,即对单个输入字符有多种可能的转移,则称为非确定性有限自动机(Nondeterministic Finite Automaton,NFA
。相反如果
F
A
FA
FA中每个状态对任一输入字符都具有唯一可能的转移,则称为确定性有限自动机(Deterministic Finite Automaton,DFA)
。
Note:
为了更加清楚NFA的语义,我们需要一组规则来描述其行为。在历史上,针对NFA的行为,已经给出了两个不同的模型。
Note:
在上述任一模型中, N F A ( S , Σ , δ , s 0 , S A ) NFA(S, \Sigma, \delta, s_0, S_A) NFA(S,Σ,δ,s0,SA)接受一个输入字符串 x 1 , x 2 , x 3 . . . x k x_1, x_2, x_3...x_k x1,x2,x3...xk的充分必要条件是:至少存在一条穿越转移图的路径,起始于状态 s 0 s_0 s0,结束于某个接受状态 s k ∈ S A s_k \in S_A sk∈SA,且从头至尾沿该路径前进时,其上各个转移边的标签能够与输入字符串匹配(忽略标签为 ϵ \epsilon ϵ的边)。换言之,第 i i i条边的标签必须是 x i x_i xi。这种定义与NFA行为的两种模型都是一致的。
NFA和DFA在表达力上是等价的。任何DFA都是某个NFA的一个特例。因而,NFA至少像DFA一样强大。任何NFA都可以通过一个DFA模拟,通过子集构造法可以确立的。
与NFA等价的DFA包含的状态可能是NFA的指数倍。虽然DFA中状态的集合 S D F A S_{DFA} SDFA可能比较庞大,但它是有限的。此外这个DFA对每个输入符号任然只进行一次状态转移。因而,模拟NFA的DFA,其运行实践任然与输入字符串的长度成正比。在DFA上模拟NFA可能有潜在的空间问题,而不是时间问题。
因为NFA和DFA是等价的,我们可以为 a ∗ a b a^*ab a∗ab构建一个DFA,如下图:
这依赖于下述事实: a ∗ a b a*ab a∗ab与 a a ∗ b aa*b aa∗b所定义的单词集合是相同的。
《Engineering a Compiler》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。