本文介绍词法分析的基本概念与几个重要算法的思想。
概念与背景
“扫描字符,拼接成词”,这是对词法分析器主要功能的精简概括。
词法分析是编译的第一个阶段,从左到右逐个字符地对源程序进行扫描,产生一个个单词序列(标记,token),用于语法分析。事实上,词法分析器除了按规则识别单词、管理单词的属性(调用符号管理器)之外,还会过滤源程序中的无用部分(注释等)并处理错误。
我们通常用正则表达式(正规式)来说明单词的构成模式,是表示正规集的数学工具。正规集也就是有正规式可以确定的串的集合。
根据乔姆斯基的分类,文法可以分为 4 种,分别为 0 型文法(无限制文法)、1 型文法(上下文相关文法,Context Sensitive)、2 型文法(上下文无关文法,Context Free)和 4 型文法(正规文法)。
其中,上下文无关文法的产生式形如 ((V_N) ightarrow (V_N igcup V_T)^+)(示意用,非正式记法),而正规文法是在其基础上进一步限制,分为左线性和右线性两种。左线性即 ((V_N) Rightarrow (V_T) | (V_T)(V_N)),右线性即 ((V_N) ightarrow (V_T)|(V_N)(V_T))。
有穷自动机 (finite state automata) 是一个识别器,它对每个输入的字符做识别和判断,以确定其能到达的最终状态或状态集和路径,有穷自动机分为两类,即不确定的有穷自动机 NFA 和确定的有穷自动机 DFA。
DFA 和 NFA 的区别在于:DFA 没有对空串的转换操作;DFA 从一个状态,对一个特定的符号输入只能得到一个状态,而 NFA 可能得到一个状态集合。
接下来,我们讨论正则文法、正则表达式、NFA 之间的相互转换,以及如何从 NFA 构建 DFA 并化简。
将 NFA 转换为 DFA
有定理曰:如果语言 L 被一个 NFA 所接受,那么一定存在一些 DFA 也接受这一语言 L。因此,一定可以构造 NFA 对应的 DFA。
NFA 之 N 在于它接受一个特定的输入后,其所有可能的状态构成了一个集合,而非一个确定的状态。因此,在构造 NFA 对应的 DFA 时,我们考虑这个 NFA 可能到达的状态集合的所有可能性,并将每个状态集合映射为一个状态。
算法对 NFA 的子集进行搜索。初态为 (s_0) 的 (varepsilon) 闭包,将其加入候选队列。循环对候选队列中每个集合 (p),枚举字符集中的字符 (a),将状态集合中所有状态接受该字符后构成的新状态集合的 (varepsilon) 闭包 (u) 求出,如果 (u) 不是以及出现过的集合,则将其加入候选队列,并为 (u) 创建新的 DFA 节点。将 (p) 到 (u) 的 (a) 边在 DFA 图中连上。
DFA 化简
我们将 M 的状态集首先分按是否为终态分为两个子集。
每次考虑当前划分的每个子集,如果这个子集 (x) 的所有元素经过某一字母到达的所有状态不在划分的同一子集内(不属于同一个等价类),则将 (x) 按照这一情况继续划分以满足条件。
不断迭代直到收敛。根据划分,建立化简后的 DFA 即可。
正规式与 NFA 的转换
首先我们考虑如何从正规式建立 NFA。这个过程是直观的,只需要将正规式不断拆解即可。
建立 NFA 的过程,本质上是将若干已经构建的小 NFA 组装成大 NFA。
NFA 转换为正规式的过程则类似上述过程的逆过程。
首先我们需要为 NFA 添加超级源汇点(连接到所有初态、终态节点)。
我们不断从 NFA 图中寻找三种模式,然后它们简化。直到只剩下两个节点一条边时,读出边上的串即可。
正规文法和 NFA 的转换
最后我们简要讨论一下正规文法与 NFA 的转换和对应关系。
首先考虑如何从正规文法构建 NFA。我们主要关注状态转换函数(状态转换图)的产生。
- 对于 (A o aB),有 (delta(A,a)=B)
- 对于 (A o a),有 (delta(A,a)=T)
- 对于 (ain V_T),(delta(A,a)=emptyset)
反过来,对于任意 (a in Sigma, Bin S),若有 (delta(A,a)=B),则
- 若 (B otin F),则 (A o aB)
- 若 (B in F),则 (A o a|aB)
- 若 (s_0 in F),则 (s_0 o varepsilon)