词法分析器原理简介

词法分析器原理简介

词法分析器读取有字符串组成的输入流,并产生包含单词的输出流,每个单词都标记了其语法范畴(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ΣδS0SA),其中各分量的含义如下:

  • S S S是识别器中的有限状态集,以及一个错误状态 S e S_e Se
  • Σ \Sigma Σ是识别器使用的有限字母表。通常, Σ \Sigma Σ是转移图中边的标签的合集。
  • δ ( s i , c ) \delta(s_i, c) δ(si,c)是识别器的转移函数。它将每个状态 s i ∈ S s_i \in S siS和每个字符 c ∈ Σ c \in \Sigma cΣ的组合 ( s i , c ) (s_i, c) (si,c)映射到下一个状态。在状态 s i s_i si遇到输入字符 c c c,FA将采用转移 δ ( s i , c ) \delta(s_i, c) δ(si,c)转移函数。
  • S 0 ∈ S S_0 \in S S0S是指定的起始状态。
  • S A S_A SA是接受状态的集合, S A ⊆ S S_A \subseteq S SAS S A S_A 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

确定性有限自动机(Deterministic Finite Automaton,DFA)和非确定性有限自动机(Nondeterministic Finite Automaton,NFA)

手工构建的 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 aab 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)s0s0(ϵ)s1s1(a)s2s2(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(ϵ)s1s1(a)s2s2(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:

  1. NFA,允许在空串输入 ϵ \epsilon ϵ上进行转移的 F A FA FA,其状态对同一个字符输入可能存在多种转移。
  2. DFA,转移函数在单值的 F A FA FA称为 D F A DFA DFA D F A DFA DFA不允许 ϵ \epsilon ϵ转移。

为了更加清楚NFA的语义,我们需要一组规则来描述其行为。在历史上,针对NFA的行为,已经给出了两个不同的模型。

  1. 每次NFA必须进程非确定性选择时,如果有使得输入字符串转向接受状态的转移存在,则采用这样的转移。使用“全知”NFA的这种模型颇有吸引力,因为它(表面上)维护了DFA那种定义明确的接受机制。本质上,NFA在每个状态都需要猜测正确的转移。
  2. 每次NFA必须进行非确定性选择时,NFA都克隆自身,以追踪每个可能的转移。因而,对于一个给定的输入字符,NFA实际上是处于一个特定的状态集合,其中每个状态都是NFA的某个可能来处理。在这种模型中,NFA并发地追踪所有转移路径。在任一时刻,存在NFA克隆副本活动状态的那些集合称为NFA的配置。当NFA到达一个配置,此时NFA已经耗尽输入字符串,且配置中的一个或多个克隆副本处于某个接受状态,则NFA接受该输入字符串。

Note:

  1. NFA的配置:NFA上并发活动状态的集合。

在上述任一模型中, 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 skSA,且从头至尾沿该路径前进时,其上各个转移边的标签能够与输入字符串匹配(忽略标签为 ϵ \epsilon ϵ的边)。换言之,第 i i i条边的标签必须是 x i x_i xi。这种定义与NFA行为的两种模型都是一致的。

NFA和DFA的等价性

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 aab构建一个DFA,如下图:

这依赖于下述事实: a ∗ a b a*ab aab a a ∗ b aa*b aab所定义的单词集合是相同的。

参考

《Engineering a Compiler》

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值