概述

一个语言中最小的语法单位是符号(单词),如标识符、常数、界限符等。词法分析的任务就是将源程序代码转换为单词符号序列。词法规则一般用3型文法或正规表达式描述。

其产生的集合是语言基本字符集\sum​上的字符串的子集,又称为正规集

词法分析的理论基础是有限自动机理论,它与文法、正规表达式在描述语言方面是一一对应的,因而自动机也是词法分析中的重点。

在了解下面内容前,假定你知道文法与正规表达式的关系,并知道关于文法的基础概念,如果不了解则可以先浏览 文法基础 一文。

正规表达式(RE, Regular Expression)

对于字母表\sum​,其 正规表达式\正规式(RE, Regular Expression) 可以定义如下:

设A是非空的有限字母表,A={aii=1,2,,n}A= \{a_i | i = 1, 2, \dots ,n \}则:

  1. ε,,ai(i=1,2,,n)\varepsilon, \empty, a_i(i=1,2, \dots, n)均为RE。
  2. α,β\alpha, \beta是RE,则αβ,αβ,α,β\alpha | \beta, \alpha \cdot \beta, \alpha^*, \beta^*​均为RE。
  3. RE只能通过有限次的使用上述两条规则产生

上述的“|”表示”或“;“·”表示”连接“,书写时可省略;”*“表示”闭包“;这三个符号优先级自高到低为"*"、"·"、"|"。

而RE所能组成的语言即正规集若两个RE所能组成的正规集相同,则两者等价,如:b(ab)=(ba)bb (ab)^* = (ba)^* b(ab)=(ab)(a|b)^* = (a^* b^*)^*​

从上例可知,正规式也遵循代数性质,设U、V、W均为RE,其性质如下:

  • UV=VUU|V = V|U​
  • (UV)W=U(VW)(UV)W = U(VW)​
  • (UV)W=U(VW)(U|V)|W = U|(V|W)
  • εU=Uε=U\varepsilon U = U \varepsilon = U​
  • U(VW)=UVUWU(V|W) = UV|UW​
  • V=(V+ε)V^* = (V^+ | \varepsilon)​
  • (UV)W=UWVW(U|V)W = UW | VW​
  • V=VV^{**} = V^*​

一些例子

  1. ={a,b}\sum = \{ a, b\},其正规表达式和对应的正规集如下表:
正规表达式 正规集
ababab(或写作a \cdot b) 字符串ab构成的集合
$a b$
aa^* 由0或多个a构成的字符串集合
$(a b)^*$
$a(a b)^*$
$(a b)^* aab$
  1. A={aii=1,2,,n}A = \{ a_i | i = 1, 2, \dots, n \},则:

    ε,a1,a1a2,a1a5a7,a5(a3a2),\varepsilon, a_1, a_1 a_2, a_1 | a_5 a_7, a_5(a_3|a_2),\dots \dots​等都是A的正规表达式。

  2. V={0,1}V = \{0, 1\},则:

    ε,0,1,0011,(01)10,((01)1)\varepsilon, 0, 1, 0011, (01)^*10, ((01)^* | 1)^*\dots\dots等都是V的正规表达式,也就是说,所有二进制字符串(包括空串)都是V上的正规表达式。

有限自动机(Finite Automata)

有限自动机是一种识别装置的抽象概念,它是具有离散输入输出系统的数学模型。其系统内具有有限数目的状态,系统状态就概括了过去输入处理的信息。只需根据当前所处的状态和面临的输入字符就可以决定系统的后继行为。例如电梯控制装置就是有限自动机,乘客只需选择索要到达的楼层作为输入,而电梯所处的楼层和方向作为状态,就能决定电梯的行为,而无需记住以前的行为。

确定有限自动机(DFA, Deterministic Finite Automata)

确定有限自动机定义为五元组:M=(S,,f,s0,Z)M = (S, \sum, f, s_0, Z)​,它们的定义如下:

  • SS 是有限集,其中每个元素称为一个状态。

  • \sum 是有穷字母表,其种每个元素称为一个输入字符。

  • ff​S×SS \times \sum \to S​ 上的单值部分映射。

    f(A,a)=Af (A,a) = A'​ 表示当前状态为AA​、输入为aa​时,将转换到状态AA'​,又称AA'​AA​的后继状态。其中,A,ASA, A' \in S​aa \in \sum​

  • s0s_0唯一的初始状态,s0Ss_0 \in S

  • ZZ 是终止状态集,ZSZ \subseteq S。但ZZ可以为空集,表示该DFA不接受任何东西。

状态转换图、状态转换矩阵

一个DFA可以用两种方式来表示:状态转换图和状态转换矩阵。

状态转换矩阵主要表示f(A,a)f(A,a)的值,通常约定第一行的状态为初始态而终态则需特别说明,具体的表示方式见下方例子,这样的矩阵对计算机来说是友好的,但对人来说则不那么直观。因此又有便于人理解的状态转换图。

状态转换图也简称为转换图,是有向图,DFA中每个状态对对应转换图中的一个结点,DFA中的每个映射对应图中的一条有向弧

假定一个DFA(M)含有m个状态和n个输入字符,那么转换图就包含m个状态结点,每个结点最多有n条弧射出。整个图只有一个用\rightarrow射入的表示的初始状态结点用双圆圈表示的终止状态结点可以有若干个(也可能一个没有)。若存在一条从初始状态结点到任一终止状态结点的路径,且路径上所有的弧标识符连接成一个字符串等于ω\omega ,则称ω\omega​可由该DFA识别。

若DFA的初始状态结点=终止状态结点,则仅有ε\varepsilon可被该DFA识别。DFA所能识别的的语言为:L(M)={ωωM}L(M) = \{ \omega | \omega是M的初态到终态的路径上的弧组成的字符串 \}

一些例子

  1. 有DFA M=({s0,s1,s2,s3},{a,b},f,s0,s3)M = (\{s_0, s_1, s_2, s_3\}, \{a,b\}, f, s_0, s_3)​,其中ff​为:

    • f(s0,a)=s1f(s_0, a) = s_1​
    • f(s0,b)=s2f(s_0, b) = s_2​
    • f(s1,a)=s3f(s_1, a) = s_3
    • f(s1,b)=s2f(s_1, b) = s_2
    • f(s2,a)=s1f(s_2, a) = s_1​
    • f(s2,b)=s3f(s_2, b) = s_3​
    • f(s3,a)=s3f(s_3, a) = s_3

    求该DFA对应的状态转换图及状态转换矩阵。

    状态转换矩阵如下:

    a b
    s0s_0 s1s_1 s2s_2
    s1s_1 s3s_3 s2s_2
    s2s_2 s1s_1 s3s_3
    s3s_3 s3s_3

    其状态转换图如下:

    编译原理_词法分析基础

  2. 构造一个DFA,它接受字母表{a,b,c}\{a,b,c\}上以a或b开始的字符串,或以c开始但所含的a不多于一个的字符串。

    首先根据题意画出其状态转换图如下:

    编译原理_词法分析基础

    由该图就可以简单的写出该DFA M

    M=({s0,s1,s2,s3},{a,b,c},f,s0,{s1,s2,s3})M = (\{s_0, s_1, s_2, s_3\}, \{a, b, c\}, f, s_0, \{s_1, s_2, s_3\})​,其中ff​等于:

    • f(s0,a)=s1f(s_0, a) = s_1
    • f(s0,b)=s1f(s_0, b) = s_1​
    • f(s0,c)=s2f(s0, c) = s_2​
    • f(s1,a)=s1f(s_1, a) = s_1​
    • f(s1,b)=s1f(s_1, b) = s_1
    • f(s1,c)=s1f(s_1, c) = s_1​
    • f(s2,a)=s3f(s_2, a) = s_3​
    • f(s2,b)=s2f(s_2, b) = s_2​
    • f(s2,c)=s2f(s_2, c) = s_2​
    • f(s3,b)=s3f(s_3, b) = s_3​
    • f(s3,c)=s3f(s_3, c) = s_3

不确定有限自动机(NFA, Nondeterministic Finite Automata)

NFA也是由五元组M=(S,,f,s0,Z)M = (S, \sum, f, s_0, Z)定义,但与DFA不同的是

  1. ffS×2SS \times \sum \to 2^S​ 上的映射,对于S中给定的状态及输入符号,返回一个状态的集合即当前状态和后继状态不唯一
  2. 有向弧上的标记可以是ε\varepsilon​

一个典型的NFA如下图:

编译原理_词法分析基础

NFA同样有状态转换矩阵,不同的是其对应的是一组状态集。上图对应的状态转换矩阵如下:

A B
s0s_0 {s0,s1}\{s_0, s_1\} {s0}\{s_0\}
s1s_1 {s2}\{s_2\}
s2s_2 {s3}\{s_3\}
s3s_3

NFA实际上是DFA的特例,每个NFA M都存在DFA N,且L(M)=L(N)L(M) = L(N),即存在和NFA等价的DFA。

NFA转换为DFA

上面提到,每个NFA都存在等价的DFA,也就是说,任一NFA都可以被转换为DFA,它由一个构造算法证明。

由NFA M=(S,,f,s0,Z)M = (S, \sum, f, s_0, Z) 构造一个等价的DFA M=(T,,f,I0,Z)M' = (T, \sum, f', I_0, Z')算法如下:

  1. I0=s0I_0 = s_0
  2. 若状态集TT​中有状态Ii={s0,s1,,sj}I_i = \{s_0, s_1, \dots, s_j \}​skS,0kjs_k \in S, 0 \leq k \leq j​。并且MM​中有f({s0,s1,,sj},a)=k=0jf(sk,a)={s0,s1,,su}=Iuf(\{s_0, s_1, \dots, s_j \}, a) = \bigcup^j_{k=0}f(s_k, a) = \{s_0, s_1, \dots, s_u\} = I_u​,若IuI_u​不在TT​中,则将IuI_u​加入TT​
  3. 重复上述步骤,直至TT​中没有新的状态加入。
  4. Z={IITIZ}Z' = \{I | I \in T 且I \bigcap Z \neq \empty \}​

这个过程总能在有限时间内完成,因为M的状态幂集是有限的。NFA转换为DFA又称作 子集化\确定化

一个例子

光看算法可能不太直观,因此实际操作一下更容易理解。

有NFA M=({q0,q1},{a,b},f,{q0},{q1})M = (\{q_0, q_1\}, \{a, b\}, f, \{q_0\}, \{q_1\})ff的映射见下映射矩阵:

a b
q0q_0 q0q_0 q1q_1
q1q_1 q0,q1q_0, q_1 q0q_0

这里可以发现当状态处于q1q_1时读入a,其后继状态不唯一。

其确定化的过程如下(T,I含义见上方算法):

T中初态I0=q0I_0 = {q_0}​,当读入字符a, b时,按NFA映射函数有:

  • f(I0,a)={q0}f (I_0, a) = \{q_0\}​
  • f(I0,b)={q1}f(I_0, b) = \{q_1\}​

其中发现新状态集{q1}\{q_1\},将其记为I1I_1并加入到T中。

此时考察I1I_1​,当读入字符a, b时,按NFA映射函数有:

  • f(I1,a)={q0,q1}f(I_1, a) = \{q_0, q_1\}
  • f(I1,b)={q0}f(I_1, b) = \{q_0\}

其中发现新状态集{q0,q1}\{q_0, q_1 \}​,将其记为I2I_2​并加入T中。

此时考察I2I_2,当读入字符a, b时,按NFA映射函数有:

  • f(I2,a)={q0,q1}f(I_2, a) = \{q_0, q_1\}
  • f(I2,b)={q0,q1}f(I_2, b) = \{q_0, q_1\}

其中没有新的状态集,因此循环部分结束。如果将过程画为状态表则更为直观:

a b
I0={q0}I_0 = \{q_0\} I0={q0}I_0 = \{q_0\} I1={q1}I_1 = \{q_1\}
I1={q1}I_1 = \{q_1\} I2={q0,q1}I_2 = \{q_0, q_1\} I0={q0}I_0 = \{q_0\}
I2={q0,q1}I_2 = \{q_0, q_1\} I2={q0,q1}I_2 = \{q_0, q_1\} I2={q0,q1}I_2 = \{q_0, q_1\}

可以发现,将InI_n看做单独的整体即为确定化的状态转换表。最后一步就是找出ZZ',按照定义Z={IITIZ}Z' = \{I | I \in T 且I \bigcap Z \neq \empty \},其计算如下:

  • I0TI0(Z={q1})I_0 \in T 且 I_0 \bigcap (Z = \{q_1\}) \neq \empty,后一条件不满足
  • I1TI1(Z={q1})I_1 \in T 且 I_1 \bigcap ( Z = \{q_1\}) \neq \empty​,条件满足。
  • I2TI2(Z={q1})I_2 \in T 且 I_2 \bigcap ( Z = \{q_1\}) \neq \empty​,条件满足。

最终Z={I1,I2}Z' = \{I_1, I_2\}​,最终DFA N=({I0,I1,I2},{a,b},f,I0,{I1,I2})N = (\{I_0, I_1, I_2\}, \{a, b\}, f', I_0, \{I_1, I_2\})​,画成状态转换图如下:

编译原理_词法分析基础

要理解算法的关键在于,它将状态以集为单位,当遇到新状态集时,结果状态集为各自NFA映射的并集

确定有限自动机(DFA)的化简

通常,对NFA确定化后产生的DFA状态数都会增加,而且可能出现一些状态之间是等价的,这时就需要化简,对确定有限自动机的化简又称作最小化。化简采用最小化算法,也称作划分法,它的原则是将DFA M中状态划分为不相交的子集,在每个子集内部其状态都等价,而在不同子集间状态均不等价(可区分的)。最后从每个子集中选一状态最为代表,消去等价状态。

其定义:设DFA M中有两个状态 s,ts, t​,若(s,ω)(s1,ε),(t,ω)(t1,ε)(s, \omega) \vdash^* (s_1, \varepsilon), (t, \omega) \vdash^*(t_1, \varepsilon)​,且s1,t1s_1, t_1​均属于终态,ωVT\omega \in V^*_T​,则称s,t是等价的,否则则是可区分的。

这里要解释一个新符号,状态前进至下一状态,我们称作自动机作了1步动作,其中"n,+,\vdash^n, \vdash^+, \vdash^*​"分别代表自动机作了n步、1步及以上、0步及以上的灯座。

简单直白即,对DFA中的任两状态s,ts, t​,若从其中一个状态触发接受字符串ω\omega​,而另一状态出发不能接受字符串ω\omega​,或从s,ts, t​出发均接受字符串ω\omega​到达的是不同的状态,则称它们是可区分的,否则就是不可区分。对任俩不可区分的状态都可以合并为等价状态

按这个原则算法步骤如下:

  1. 把状态集S分为终态集和非终态集Π0={I01,I02}\Pi_0 = \{I^1_0, I^2_0\},其中I01I^1_0属于非终态集,另一个则是终态集。终态集可接受ε\varepsilon而后者不行,因此是可以区分的。
  2. 假定经过k次划分后,含有m个子集,记为:Πk={Ik0,Ik1,,Ikm}\Pi_k = \{I^0_k, I^1_k, \dots, I^m_k\},这些子集到现在为止仍可区分,然后继续划分,设取任一子集Iki={s1,s2,,st}I_k^i = \{s_1, s_2, \dots, s_t\}若存在一个输入字符a,使得f(Iki,a)f(I_k^i, a)不全包含在现Πk\Pi_k的某个子集,则说明IkiI^i_k中仍存在不等价状态,应该还可被划分。如:f(s1,a)=t1,f(s2,a)=t2f(s_1,a) = t_1, f(s_2, a) = t_2t1,t2t1, t2分别属于Πk\Pi_k的不同子集。对所有的子集IkmI^m_k读入字符a做一遍才完成此次划分。
  3. 重复上述步骤,直至子集数不再增加位置。也就是不可再划分了。
  4. 对每个子集任取一状态作为代表,若该子集包含原初态,则此状态就是最小化后的初态;若此子集包含原有的终态,则此状态就是最小化后的终态。

一个例子

设有一DFA状态转换图如图:

编译原理_词法分析基础

按步骤将其划分为非终态集与终态集:Π0={{s0,s1,s2},{s3,s4,s5,s6}}\Pi_0 = \{ \{s_0, s_1, s_2\}, \{s_3, s_4, s_5, s_6\} \}

来看看如何划分Π0\Pi_0

  • 先解决I01={s0,s1,s2}I^1_0 = \{s_0, s_1, s_2\}集划分,有f({s0,s1,s2},a)={s1,s3}f( \{s_0, s_1, s_2\}, a) = \{s_1, s_3\},不在现有Π\Pi中,其中s0,s2s_0, s_2均经由a抵达状态s1s_1,而s1s_1仅能从a抵达状态s3s_3。因此分为{s0,s2},{s1}\{s_0, s_2\}, \{s_1\}两个子集。
  • 然后看I02={s3,s4,s5,s6}I^2_0 = \{s_3, s_4, s_5, s_6\},有f({s3,s4,s5,s6},a)={s3,s4,s5,s6},f({s3,s4,s5,s6},b)={s3,s4,s5,s6}f(\{s_3, s_4, s_5, s_6\}, a) = \{s_3, s_4, s_5, s_6\}, f(\{s_3, s_4, s_5, s_6\}, b) = \{s_3, s_4, s_5, s_6\},因此它们无法再被划分。这里要注意的,对于终态集,是存在接受ε\varepsilon的情况的,这也是为什么f(s3,a)=s4f( s_3, a) = s_4,因为这里可以假定接受了一个ε\varepsilon的情况。

这轮完成后,现结果为:Π1={{s0,s2},{s1},{s3,s4,s5,s6}}\Pi_1 = \{ \{s_0, s_2 \}, \{s_1\} ,\{s_3, s_4, s_5, s_6\} \}

然后继续划分Π1\Pi_1

  • 看仍存在I11={s0,s2}I^1_1 = \{s_0, s_2\},有f({s0,s2},a)={s1}f(\{s_0, s_2\}, a) = \{s_1\},存在于Π1\Pi_1中;但f({s0,s2},b)={s2,s5}f(\{s_0, s_2\}, b) = \{s_2, s_5\}出现了新子集,说明可划分,因为只有两个状态了,因此最终分为{s0},{s2}\{s_0\}, \{s_2\}

这轮完成后,现结果为:Π2={{s0},{s2},{s1},{s3,s4,s5,s6}}\Pi_2 = \{ \{s_0\}, \{s_2 \}, \{s_1\} ,\{s_3, s_4, s_5, s_6\} \}

这样一来,就全部分完了,发现仅有一个状态集存在多个状态,只需取其一,因为它们是等价的,设取s3={s3,s4,s5,s6}s_3 = \{s_3, s_4, s_5, s_6 \},最后使指向其他等价状态的弧指向s3s_3​即可。最终简化的DFA如下图:

编译原理_词法分析基础

正规式与有限自动机的转换

正规式与文法均是用来描述语言的,而自动机是用来识别的,它们之间自然也存在关系。事实上,它们是可以互相转换的,可以用以下定理证明:

  1. \sum 上的NFA M所能识别的语言L(M)L(M)可以用\sum上的正规表达式(RE)表示。
  2. 对于\sum上的任一RE α\alpha ,都存在一个DFA M使得L(M)=L(α)L(M) = L(\alpha)

下面分别了解它们之间是如何转换的,同时也能知道定理是可实践的。

有限自动机转换为正规式

令状态图中每一条弧都拓广,这样一来,每条弧就可以用一个正规式表达,有如下3条非常简单的转换规则:

编译原理_词法分析基础

其中,前两条都很好理解,第三条好像也很容易理解,但证明一下:

  1. L(s1)=L(s0)a+L(s1)bL(s_1) = L(s_0)a + L(s_1)b
  2. L(s0)=εL(s_0) = \varepsilon
  3. L(s1)=βL(s_1) = \beta,代入后得β=a+βb\beta = a+\beta b,左递归的等价式即β=ab\beta = ab^*

转换过程比较简单,其实就是将一个FA逐渐利用转换规则以减少状态(结点),使最终仅剩一对(一起点,一终点)。也有说用虚设x在初态之前,y在所有终态之后的。但实际原理是一样的。

看个例子

编译原理_词法分析基础

消除的过程清晰的描述在图片中,由最后步骤的状态图可得出该FA接受的正规式为$(ac | b)(dc)^* $。

正规式转换为有限自动机

本质上,这是FA转为RE的逆过程,它同样用一样的3条规则,只不过不一样的是,需要分裂出新的结点,详细的规则见下图:

编译原理_词法分析基础

主要不同的就在于RE转FA需要体现出新分裂的结点,只需按规则将每根弧上标记是\sum​上的字符或ε\varepsilon​为止,最后将该NFA(因为含有ε\varepsilon​弧)转为DFA并化简即可。

一个例子

编译原理_词法分析基础

可以看到最终就成功生成了NFA,但需要进一步确定化(存在ε\varepsilon弧)。

词法分析器构造

上面的内容和前一篇 文法基础 简要的介绍了语法、正规式及有限自动机,在有了这些基础理论和工具之后,就可以构造出编译所需要的词法分析模块。其一般步骤如下:

  1. 用RE描述语言的构成规则。
  2. 为每一RE构造NFA,用以识别RE所表示的正规集。
  3. 对构造出的NFA转为DFA,并简化。
  4. 用该DFA来实现词法分析。

以后有时间继续深入来看看具体都是怎么实现的。

相关文章: