词法分析器原理简介

词法分析器读取有字符串组成的输入流,并产生包含单词的输出流,每个单词都标记了其语法范畴(syntactic category)或类型,等效于英文单词的词类。为了完成这种聚集和分类操作,词法分析器会应用一组描述输入程序设计语言的词法结构(也称微语法,microsyntax)的规则。程序设计语言的微语法规定了如何将字符组合为单词,以及反过来如何分开混合在一起的各个单词。

如何识别单词的

考虑识别关键字new的问题,首先检查第一个字符是否为n,再次检查是否后接e,最后e是否后接w。在每一步,未能匹配到适当的字符都将导致不能识别单词,编译器此时会输出一个错误信息并返回失败。整个对new关键字的识别可用转移图(transition diagram)来表示。

词法分析程序java语言代码 词法分析程序原理_接受状态

该转移图表示了一个识别器。每个圆圈都表示计算中的一个抽象状态(status)。为了方便起见,每个状态都标记起来。初始状态或起始状态为词法分析程序java语言代码 词法分析程序原理_接受状态_02,我们始终将起始状态标记为词法分析程序java语言代码 词法分析程序原理_接受状态_02。状态词法分析程序java语言代码 词法分析程序原理_接受状态_04是接受状态,仅当输入为new时,识别器才会到达词法分析程序java语言代码 词法分析程序原理_接受状态_04。接受状态以双层圆圈绘制,如上图所示。箭头表示根据输入字符的不同,所发生的状态之间的转移。如果识别器始于状态词法分析程序java语言代码 词法分析程序原理_接受状态_02,然后读取了字符n、e和w,那么最终会转移到状态词法分析程序java语言代码 词法分析程序原理_接受状态_04

使用同样的方法为while和not构建识别器,将产生的识别器合并后的转移图,如下:

词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_08

识别器的形式化

转移图还可以看做是形式化的数学对象,成为有限自动机,它定义了识别器的规格。形式上,有限自动机(FA)是一个五元组词法分析程序java语言代码 词法分析程序原理_算法_09,其中各分量的含义如下:

  • 词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_10是识别器中的有限状态集,以及一个错误状态词法分析程序java语言代码 词法分析程序原理_接受状态_11
  • 词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_12是识别器使用的有限字母表。通常,词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_12是转移图中边的标签的合集。
  • 词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_14是识别器的转移函数。它将每个状态词法分析程序java语言代码 词法分析程序原理_算法_15和每个字符词法分析程序java语言代码 词法分析程序原理_词法分析器_16的组合词法分析程序java语言代码 词法分析程序原理_词法分析器_17映射到下一个状态。在状态词法分析程序java语言代码 词法分析程序原理_词法分析器_18遇到输入字符词法分析程序java语言代码 词法分析程序原理_编译器_19,FA将采用转移词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_14转移函数。
  • 词法分析程序java语言代码 词法分析程序原理_词法分析器_21是指定的起始状态。
  • 词法分析程序java语言代码 词法分析程序原理_接受状态_22是接受状态的集合,词法分析程序java语言代码 词法分析程序原理_接受状态_23词法分析程序java语言代码 词法分析程序原理_接受状态_22中的每个状态都在转移图中表示为双层圈。

我们可以将识别new/not/while的FA形式如下:

词法分析程序java语言代码 词法分析程序原理_算法_25

词法分析程序java语言代码 词法分析程序原理_算法_26

词法分析程序java语言代码 词法分析程序原理_词法分析器_27
词法分析程序java语言代码 词法分析程序原理_算法_28
词法分析程序java语言代码 词法分析程序原理_接受状态_29
词法分析程序java语言代码 词法分析程序原理_编译器_30
词法分析程序java语言代码 词法分析程序原理_编译器_31
词法分析程序java语言代码 词法分析程序原理_编译器_32
词法分析程序java语言代码 词法分析程序原理_算法_33
词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_34
词法分析程序java语言代码 词法分析程序原理_编译器_35
词法分析程序java语言代码 词法分析程序原理_编译器_36
词法分析程序java语言代码 词法分析程序原理_接受状态_37
词法分析程序java语言代码 词法分析程序原理_词法分析器_38

词法分析程序java语言代码 词法分析程序原理_接受状态_39

词法分析程序java语言代码 词法分析程序原理_词法分析器_40

对于状态词法分析程序java语言代码 词法分析程序原理_编译器_41和输入字符词法分析程序java语言代码 词法分析程序原理_算法_42的所有其他组合,我们定义词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_43词法分析程序java语言代码 词法分析程序原理_词法分析器_44是指定的错误状态。这种五元组与转移图是等价的,给出一种表示,我们可以轻易地重建另一种表示。转移图就是描绘了对应FA的一副图画。

并且仅当字符串从词法分析程序java语言代码 词法分析程序原理_接受状态_02开始时,FA接受字符串Str,该字符串中的字符序列可以使FA经历一系列状态转移,在处理整个字符串后FA到达某个接受状态。

更形式化地讲,如果字符串Str有字符词法分析程序java语言代码 词法分析程序原理_算法_46组成,那么FA 词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_47接受Str的充分必要条件是:

词法分析程序java语言代码 词法分析程序原理_词法分析器_48

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

手工构建的词法分析程序java语言代码 词法分析程序原理_算法_49都不包括词法分析程序java语言代码 词法分析程序原理_词法分析器_50(空字符串),但一些正则表达式(Regular Expression,RE)确实用到了词法分析程序java语言代码 词法分析程序原理_词法分析器_50。在词法分析程序java语言代码 词法分析程序原理_算法_49词法分析程序java语言代码 词法分析程序原理_词法分析器_50发挥什么作用?我们可以使用针对词法分析程序java语言代码 词法分析程序原理_词法分析器_50输入的转移来合并词法分析程序java语言代码 词法分析程序原理_算法_49,并组成用于更复杂RE的FA。例如,假定我们有用于m和n的两个RE的词法分析程序java语言代码 词法分析程序原理_算法_49,分别称为词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_57词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_58。如下图所示:

词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_59

词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_57的接受状态添加一个针对输入的词法分析程序java语言代码 词法分析程序原理_词法分析器_50的转移,转移到词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_58的初始状态,把各个状态重新编号,然后使用词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_58的接受状态作为最新词法分析程序java语言代码 词法分析程序原理_算法_49的接受状态,这样构建用于处理词法分析程序java语言代码 词法分析程序原理_词法分析器_65词法分析程序java语言代码 词法分析程序原理_算法_49。如下图所示:

词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_67

随着词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移的引入,“接受”的定义必须稍微改变一下,以允许输入字符串中任何两个字符串之间出现一次或多次词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移。例如,在状态词法分析程序java语言代码 词法分析程序原理_算法_70,该词法分析程序java语言代码 词法分析程序原理_算法_49会采用转移词法分析程序java语言代码 词法分析程序原理_算法_72,而不会消耗任何字符。这是一个较小的变更,但看起来颇为合乎直觉。目测显示我们可以合并词法分析程序java语言代码 词法分析程序原理_算法_70词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_74,以消除词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移。如下图:

词法分析程序java语言代码 词法分析程序原理_算法_76

利用词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移合并两个词法分析程序java语言代码 词法分析程序原理_算法_49,可能会使我们关于词法分析程序java语言代码 词法分析程序原理_算法_49工作方式的模型复杂化。考虑用于语言词法分析程序java语言代码 词法分析程序原理_算法_80词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_81词法分析程序java语言代码 词法分析程序原理_算法_49

词法分析程序java语言代码 词法分析程序原理_接受状态_83

我们可以用词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移合并它们,形成一个处理词法分析程序java语言代码 词法分析程序原理_词法分析器_85词法分析程序java语言代码 词法分析程序原理_算法_49

词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_87

实际上,词法分析程序java语言代码 词法分析程序原理_词法分析器_50转移的引入,使得在状态词法分析程序java语言代码 词法分析程序原理_编译器_89遇到输入字符词法分析程序java语言代码 词法分析程序原理_算法_90时,词法分析程序java语言代码 词法分析程序原理_算法_49可以选择两种不同的转移。它可以采用转移词法分析程序java语言代码 词法分析程序原理_词法分析器_92,或采用两个转移:词法分析程序java语言代码 词法分析程序原理_算法_93词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_94。哪种转移是正确的?考虑字符串词法分析程序java语言代码 词法分析程序原理_接受状态_95词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_81。这个词法分析程序java语言代码 词法分析程序原理_算法_49应该可以接受这两个字符串。

对于词法分析程序java语言代码 词法分析程序原理_接受状态_95,它应该采用的转移是:
词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_99
对于词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_81,应该采用的转移是:
词法分析程序java语言代码 词法分析程序原理_算法_101
正如这两个字符串所说明的,从状态词法分析程序java语言代码 词法分析程序原理_编译器_89采取的正确转移,取决于词法分析程序java语言代码 词法分析程序原理_算法_90之后的字符。在每一步,词法分析程序java语言代码 词法分析程序原理_算法_49都会检查当前字符。词法分析程序java语言代码 词法分析程序原理_算法_49的状态隐含了左上下文(left context),即它已经处理过了的那些字符。因为词法分析程序java语言代码 词法分析程序原理_算法_49必须在检查下一个字符之前进行转移,所以诸如词法分析程序java语言代码 词法分析程序原理_编译器_89这样的状态,背离了我们对顺序算法行为的观念。如果一个词法分析程序java语言代码 词法分析程序原理_算法_49包含了词法分析程序java语言代码 词法分析程序原理_编译器_89这样的状态,即对单个输入字符有多种可能的转移,则称为非确定性有限自动机(Nondeterministic Finite Automaton,NFA。相反如果词法分析程序java语言代码 词法分析程序原理_算法_49中每个状态对任一输入字符都具有唯一可能的转移,则称为确定性有限自动机(Deterministic Finite Automaton,DFA)

Note:

  1. NFA,允许在空串输入词法分析程序java语言代码 词法分析程序原理_算法_111上进行转移的词法分析程序java语言代码 词法分析程序原理_接受状态_112,其状态对同一个字符输入可能存在多种转移。
  2. DFA,转移函数在单值的词法分析程序java语言代码 词法分析程序原理_接受状态_112称为词法分析程序java语言代码 词法分析程序原理_编译器_114词法分析程序java语言代码 词法分析程序原理_编译器_114不允许词法分析程序java语言代码 词法分析程序原理_算法_111转移。

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

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

Note:

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

在上述任一模型中,词法分析程序java语言代码 词法分析程序原理_编译器_117接受一个输入字符串词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_118的充分必要条件是:至少存在一条穿越转移图的路径,起始于状态词法分析程序java语言代码 词法分析程序原理_编译器_89,结束于某个接受状态词法分析程序java语言代码 词法分析程序原理_接受状态_120,且从头至尾沿该路径前进时,其上各个转移边的标签能够与输入字符串匹配(忽略标签为词法分析程序java语言代码 词法分析程序原理_词法分析器_50的边)。换言之,第词法分析程序java语言代码 词法分析程序原理_词法分析器_122条边的标签必须是词法分析程序java语言代码 词法分析程序原理_词法分析程序java语言代码_123。这种定义与NFA行为的两种模型都是一致的。

NFA和DFA的等价性

NFA和DFA在表达力上是等价的。任何DFA都是某个NFA的一个特例。因而,NFA至少像DFA一样强大。任何NFA都可以通过一个DFA模拟,通过子集构造法可以确立的。

与NFA等价的DFA包含的状态可能是NFA的指数倍。虽然DFA中状态的集合词法分析程序java语言代码 词法分析程序原理_编译器_124可能比较庞大,但它是有限的。此外这个DFA对每个输入符号任然只进行一次状态转移。因而,模拟NFA的DFA,其运行实践任然与输入字符串的长度成正比。在DFA上模拟NFA可能有潜在的空间问题,而不是时间问题。

因为NFA和DFA是等价的,我们可以为词法分析程序java语言代码 词法分析程序原理_词法分析器_85构建一个DFA,如下图:

词法分析程序java语言代码 词法分析程序原理_编译器_126

这依赖于下述事实:词法分析程序java语言代码 词法分析程序原理_算法_127词法分析程序java语言代码 词法分析程序原理_算法_128所定义的单词集合是相同的。

参考

《Engineering a Compiler》