在图6.1的词法分析器中,我们可以很清楚地看出I、N、R和O四个规则两两之间都不存在交集。我们可以尝试构造一个冲突的规则,并看一看词法分析器的DFA是什么样子的:
假设词法分析器包含以下规则:
A:”if”
B:[a-z]+
对A|B构造DFA,我们将会得到如下状态机:
图6.2
通过图6.2我们可以看出,这个状态图满足了上述的条件3:包含了状态A的结束状态的状态都包含了B的结束状态,因此A是B的子集。显然”if”是[a-z]+的一个子集。在处理这种有冲突的规则的时候,既可以报错,也可以根据指定的优先级进行挑选。
7、尾声
使用DFA的方法完成的可配置词法分析器的性能是相当好的。笔者前不久曾经做过 实验,首先使用本文提到的算法开发一个这样的词法分析器,然后在一份C++代码(这份代码经过多次复制而成件,一共有3.12M)中抽取所有数字、标识符 和注释,吞吐速度高达46万记号/秒(笔者的台式电脑配置是奔腾4的超线程2.99GHz处理器,1G内存),其中抽取出来的记号一共有22万个。在分析 的过程中,只有10%的时间花在了DFA上,90%的时间花在了处理结果的工作上。DFA本身造成的消耗是很小的。不过词法分析的性能在很大程度上跟 DFA的实现有很大关系。三个月前笔者也实现过一个同类的程序,但是吞吐速度仅有1.1万记号/秒。
一般来说,比较高性能的DFA的实现是一张二维的表。行代表字符,列代表DFA 的状态,单元格代表该状态经输入某个字符之后进行转移的目标状态。此外还有一张表用来记录哪些状态对应哪些规则的结束状态。笔者的词法分析器是基于 UTF-16编码的字符串,一张表一共有65535行显然是不现实的,因此还有另一张表把字符转换成字符类。字符类是这样定义的:假设现在已经存在了 65535行的一张大表,如果在某个字符区间所对应的子表内,任意一列的单元格的数据都一样的话,那么这个区间内的所有字符就可以被视为是等价的,这些字 符就属于同一个字符类。于是仅需要另外一张65535个单元的表用来把一个字符映射到字符类。这种做法可以大大的压缩DFA所需要的空间。在笔者的程序 里,识别字符类的算法被融入了DFA的构造算法中