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


这里需要注意的是 , 常量表k中不止可以存字符串 , 还可以存数值类型的值 , 要通过表示数值类型 , 同样可以用类似字符串的方式去表示 。那在什么情况下 , 数值类型要通过VK的方式展现呢?答案是生成指令的时候 , 比如生成指令 , 这个指令要从常量表k中 , load一个值到栈顶 , 我们首先要将数值存入常量表 , 然后再去索引它 , 用 e来表示 , 就是e->k = VK, e-> =  , 后面会详细讨论 。对于的情况 , 我将在下一章进行说明 , 而对于的情况(也就是’{}‘) , expr会怎么处理呢?我们结合图12来进行说明:
图12
当我们的expr函数 , 检测到当前的token为’{‘时 , 会生成一个指令<0 0 0 > , 这里的含义是 , 在寄存器R(0)中创建一个table , 同时它的和都为0 。此时 , 的值如下所示:
e->k = VNONRELOCe->u.info = 0
类型表示 , 它的值在lua stack中 , e->则说明了它的值在栈中所在的位置 。在图12的例子中 , 它在R(0) 。我们知道 , 指令是iABC模式(指令的模式可以到.c里查找) , 在生成指令后 , e->的值 , 就是A域的值 , 因为指令操作的目标在R(A) , 所以e所代表的值 , 就是刚刚被处理过的R(A)值 , 在本例中(它就是指明刚生成的table在栈中的位置) , 不止是这样 , 其他指令也是这样 。由于花括号内没有其他定义 , 因此expr会直接跳过’}‘ , 调用获取下一个token 。
接下来 , 要分析的则是expr函数如何处理类型为的token , 其实是由函数来处理的 , 而又是的组成部分 , 但是又是的组成部分 , 所以我们可以视为在expr内进行处理 。现在我们来看一下这里的处理流程是怎样的 。首先在开始讨论 , expr如何处理之前 , 我们需要明确几个概念 , 首先是我们的代码中 , 函数与、结构的关系 , 现在我们通过图13的例子来进行分析:
图13
如上图所示 , 我们现在有一个test.lua的脚本 , 里面定义了两个local变量和一个函数 , 那么这个test.lua本身里面所有的代码就是一个chunk , 这个chunk实际上也是一个类型 , 我们称之为top-level。test.lua脚本里 , 定义了一个foo函数 , 我们在编译的过程中 , chunk因为也是一个 , 因此会有一个结构和它对应 , 这个结构主要用来存储编译结果的 , 另外foo函数 , 因为也是一个函数 , 因此它也有一个结构和它对应 , 并且它的prev指针 , 指向了chunk的结构 。每个函数都有一个列表 , 其中第一个必定是_ENV , 它默认指向全局表 , 如果我们没有对_ENV进行任何赋值操作 , 那么它就是全局表的象征 。每个函数在运行时 , 都有独立的虚拟栈 , local变量会被放入栈中 。这里的图 , 只是我用来方便讲解的图 , 实际上的布局并非真的完全如此 。
图13帮助我们梳理了很多概念 , 我们现在可以根据图13来做一些逻辑分析了 , 同样是expr函数 , 当我们输入不同的token的时候 , 它会生成什么样的变量呢?现在来罗列一下这些在编译过程中的情况: