上 构建Lua解释器Part7:构建完整的语法分析器(12)


接下来要论述的第三种情况 , 则是expr前面是’~‘的情况 , 这种操作符 , 其实和’-‘几乎一样 , 只是操作的对象应当是int型 。读者可以结合前面的例子再次分析一次 , 只是这里要生成的指令是 , 而不是 。
我们要讨论的第四种情况是 , expr前面是’not’的情况 。在lua中 , 除了nil和false是表示false以外 , 其他的都表示为true 。第四种情况的表现形式如下所示:
not exp
我在前面已经详细讨论过了 , exp如何通过expr函数转化为的流程 , 现在要看的 , 则是如何将not操作和这个整合起来 。我们则通过图21来展示这个流程:
图21
上图展示了为常量时 , 与not操作符整合的结果 , 其实就是修正原来的结构为或者为VTRUE 。对于not后面的exp为变量的情况 , 处理流程和’-‘一样 , 只是最终生成的指令从到 。
在我们的虚拟机指令之中(主要是iABC模式的指令) , B域和C域的值要两种情况来看待 , 一种是小于256的情况 , 还有一种则是大于等于256的情况 。为什么要这样处理呢?实际上这是lua对指令的一种优化 , 我们先复习一下lua虚拟机的指令编码格式:
+--------+---------+---------+--------+--------+|iABC|B:9|C:9|A:8|Opcode:6|+--------+---------+---------+--------+--------+|iABx|Bx:18(unsigned) |A:8|Opcode:6|+--------+---------+---------+--------+--------+|iAsBx |sBx:18(signed)|A:8|Opcode:6|+--------+---------+---------+--------+--------+
当B域或者C域的值大于等于256的时候 , 说明他要去常量表k中取参数 , 即Kst(B-256)或者Kst(C-256) 。如果小于256 , 则需要去lua栈上取参数 。在一个函数调用的过程中 , 一个函数的lua stack的最大尺寸是255 , 它是由指令的A域决定的 , 因为它只有8个bit位 。我们一个指令要从栈中访问数据 , 也是受这个条件限制 , 因为要取的数据 , 必然是前面执行的指令对栈进行操作的结果 。如果没有大于或等于256 , 就去常量表找参数的规则 , 那么我们首先就得用指令 , 将其先读入栈中 , 这样本来一条指令就能完成的事情 , 就可能需要两条甚至是三条 。接下来还有一个问题 , 就是 , 是不是所有在常量表K中的常量 , 都能够被非的指令直接获取呢?答案是不是 , 因为B域和C域都是9个bit为 , 最大值为512 , 因此 , 它能够直接从常量表k中找参数的范围就是0~255(一共512-256=256个) 。
3、双目运算操作
现在我们进入到双目运算的讨论之中了 , 这里 , 我会把它分为几个部分:算术运算、逻辑运算和比较运算 , 最后会在此基础上讨论一下递归的情况 。
我们先来看一下算术运算的部分 , 这里主要分为三种情况看待 , 分别是:两个常量操作、一个常量与一个变量操作和两个变量的操作 。现在先来看一下两个常量相加的例子:
1+1
我们进入到expr函数以后 , 就会开始处理上面这个exp了 , 先来回顾一下expr函数的定义:
static void expr(FuncState* fs, expdesc* e);
首先最左边的1 , 会通过函数来识别 , 获得图22的结果 , 此时调用函数 , 获得下一个token:
图22
此时 , 当前token为操作符’+‘ , 所有的双目运算操作 , 在这个阶段 , 都要进入“中序”处理 , 在expr函数中 , 是通过调用函数来实现的 , 在算术运算中 , 首先判断操作符左边的exp , 是否是数值类型 , 如果是则什么都不做 , 如果不是 , 则生成将exp入栈的指令 。在本例中 , 由于1是数值类型 , 因此不作任何处理 。这里需要注意的是 , 目前的结构变量e , 就上上面expr函数定义里的参数e 。此时 , 我们需要调用函数 , 获得下一个token , 而这个新的token , 需要新的结构来存储 , 我们姑且称之为e2 , 经过处理得到如图23所示的结果: