; v& j& N W0 J* @/ S有了全盘的计划之后,我们必须先处理输入的脚本,才能够进行下一步的工作。字符串处理方面可以参照一下三篇文章:《构造可配置语法分析器》、《构造正则表达式引擎》以及《如何手写语法分析器》。作为补充,这里再说一说其他的办法。4 c+ M R( \& B( K1 l9 @
/ e, k& \5 K I/ A首先是词法分析器。我们仍然能够使用《构造可配置语法分析器》前半部分的方法人脑画出一张合适的DFA,这个时候我们可以手工来实现。用于词法分析器的DFA只有两种状态,一种是普通状态,另一种是终结状态。所以我们可以很机械地将DFA用C++写出来。! H3 p/ t/ c3 a2 W
0 Y0 X2 F/ ]/ w5 Q7 p我们要为状态编号。编号要连续,而且要从0开始,这样的话C++的编译器一般都会为switch-case的代码生成一张表,用于快速跳转。然后用下面的方法。 # h9 V7 e0 a# k; b7 B; a0 l / H! t. Y! m. o4 }1:将输入的指针Input复制出一个副本,叫Current;给出一个同类型的指针Last,将其赋值为NULL;使用一个变量Status来记录当前的状态。初始化状态,一般为了方便我们把初始状态编号成0。, J3 w7 t6 Q, h, }& H2 g2 V8 z
o' ^* c0 @% o2:做一个死循环不断的计算新Status。对于某个Status我们总是能够知道输入什么字符跳转到什么新的Status上去。不同的人写出来的DFA可能会有所区别。我们首先判断当前的Status是不是终结状态,如果是的话将Current赋值给Last,然后继续往下走。我们从Current指针拿出一个字符,然后计算新Status。如果Current不满足要求那么结束循环,如果Current满足要求那么改变Status并让Current指向新的位置。 # P* t6 [4 \7 ?+ G0 q9 ]8 Z + D; P. u) S: i" U: [; B0 [3:因为字符串总是有限的,所以这个循环总是会结束。结束了之后,我们检查Last。如果Last仍然是NULL,那么代表输入的字符串是有问题的。如果不是,那么我们所需要的一个记号就从Input开始到Last结束了。如果记号的类型有需要保留的话,那么我们只需要添加一个新的代表类型的变量,在每一次修改Last的时候修改这个保存类型的变量就行了。因为一个终结状态只能代表一种类型的结束(反过来不成立,一种类型可能有好几个终结状态)。 3 _ _1 t( a( G- D' Y# W 5 w7 |6 q$ T. e' w然后是语法分析。一般来说,使用《如何手写语法分析器》中描述的方法实现一个语法分析器的话是很容易的,但是一个主要问题就是如果一门语言很复杂,特别是操作符特别多的话,这些函数写起来会很乱,因此每一个文法产生式的处理函数的命名和注释就变得相当重要了。为了简化这件事情,我们还有另一种专门用来处理操作符的方法,而且是高度可配置的。为了简化,我仅给出二元操作符和前缀操作符的处理方法。后缀操作符不常见,需要的话自己想办法吧,在上一篇文章中的语法定义中并没有出现后缀操作符。! u( r7 f% K, x+ p4 Z, X7 l3 ?' Y2 C