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


foo.bar["call"][a]
首先 , 上面这个表达式 , 会交给函数来处理 , 而处理第一个token的 , 则是 , 此时 , 会将foo直接装载到结构之中 , 得到图79的结果:
图79
此时 , 我们当前的token是’.‘ , 因此进入了通过点来访问table域的逻辑流程之中 , 对应代码片段7中的第500行代码 , 此时需要做两件事情 , 第一件是将e中的信息转化为指令 , 第二件是 , 获取’.‘之后的那个 , 并装载到新的结构之中 , 于是得到图80的结果
图80
接下来 , 我们需要整合e和k , 并且获取下一个token , 得到图81所示的结果:
图81
现在 , 我们当前的token是’[‘ , 此时进入到方括号的table域访问流程之中 , 到这里 , 先要把e中的信息转化为虚拟机指令 , 于是得到图82的结果:
图82
此时我们获得了一个指令 , B为0表示操作对象位于R(0) , C为257 , 表示key值为Kst(257-256)=Kst(1) , 也就是bar字符串 , A值为1 , 表示访问结果存储到R(1)中 , 这里需要注意一下的变化 。关于转化为指令的流程 , 本篇前面的章节已经详细论述过 , 读者如果还有疑惑 , 可以翻回去查阅 , 这里不再赘述 。接下来 , lua 会调用函数 , 获取下一个token , 并且将字符串”call”的信息 , 装载到新的结构之中 , 于是得到图83的结果:
图83
接下来 , lua 会跳过下一个token ‘]‘ , 直接获得其后面的token ‘[‘ , 并且整合e和k , 得到图84的结果:
图84
现在当前的token是’[‘ , 还是进入方括号访问table域的流程之中 , 此时 , 先将e中的信息 , 转化为虚拟机指令 , 然后通过获取下一个token a , 并且将a装载到新的结构之中 , 于是得到图85的结果:
图85
接下来 , 就是调用函数 , 获得下一个token ‘]‘ , 并且将key中的信息转化为虚拟机指令 , 于是得到图86的结果:
图86
接下来就是要整合e和key了 , 得到图87的结果:
图87
到目前为止 , 对上面例子的处理就结束了 , 至于怎么将当前的exp转化为虚拟机指令 , 读者可以参阅【expr构造和编译】那一节的相关内容 , 这里不再赘述 , 由读者自行去推导 。
3、函数调用
是处理函数调用编译流程的 , 这里有两种模式 , 一种是通过’.‘访问 , 还有一种是通过’:‘访问 , 我们前面已经花了巨量的篇幅论述expr相关的编译流程 , 这里不打算继续通过这种方式来 , 而是通过工具来生成我们的虚拟机指令 , 由读者自己推导编译流程 , 关于的使用 , 读者可以阅读Part5的【分析工具】一节 。现在我们来看两个例子 , 第一个是:
foo(a, b, c)
其生成的虚拟机指令如下所示:
1 lua.dumpfile_name = ../test.lua.out+upvals+1+name [_ENV]+proto+code+1 [iABC:OP_GETTABUPA:0B:0C:256 ; R(A) := UpValue[B][RK(C)]]||+2 [iABC:OP_GETTABUPA:1B:0C:257 ; R(A) := UpValue[B][RK(C)]]||+3 [iABC:OP_GETTABUPA:2B:0C:258 ; R(A) := UpValue[B][RK(C)]]||+4 [iABC:OP_GETTABUPA:3B:0C:259 ; R(A) := UpValue[B][RK(C)]]||+5 [iABC:OP_CALLA:0B:4C:1; R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))]||+6 [iABC:OP_RETURNA:0B:1; return R(A), ... ,R(A+B-2)(see note)]|+maxstacksize [4]|+numparams [0]|+k+1 [foo]|| +2 [a]|| +3 [b]|| +4 [c]|+source [@../test.lua]|+linedefine [0]|+type_name [Proto]|+lineinfo+1 [1]||+2 [1]||+3 [1]||+4 [1]||+5 [1]||+6 [1]|+p|+lastlinedefine [0]|+locvars|+is_vararg [1]|+upvalues+1+instack [1]|+idx [0]|+name [_ENV]+type_name [LClosure]