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


图7
通过Parse Tree , 我们既能解构代码的语法结构 , 也能确定其编译过程中的执行顺序 , 我们前面也介绍过 , 一般的多趟编译器(multi pass ) , 编译器前端要生成中间表示(IR) , 然后经过优化器优化 , 再交给编译器后端去生成对应平台的机器码 。一般意义来说 , Parse Tree虽然也可以作为IR使用 , 但是它包含太多不必要的信息 , 也不利于我们去生成三址代码形式的IR(three- code , 该种形式相比于树形结构更容易转换成机器指令) , 因此另一种形式的树 , 更易于我们去生成three- code , 它就是抽象语法树–Tree , 简称AST[6] 。AST在维基百科中的解释是:
In, antree (AST), or justtree, is a treeof theofcodein a. Each node of the treeain thecode.
简单的来说AST是源码的抽象树形结构 , 它的每个节点都是源码中的某个构造 。与Parse Tree不同 , Parse Tree会事无巨细地将源码的语法结构 , 完整地展露出来 , 一个标点符号都不会放过 , 而AST则不同 , 它会省略掉一些无关紧要的信息 , 比如一些符号’(‘、’)‘、’,‘、’;‘、’{‘、’}‘等等 , 它只关注执行什么指令 , 以及什么指令先执行 , 什么指令后执行 。比如上面例子的AST则如图8所示:
图8
上图的含义是执行赋值操作 , 将1赋值给a , 如果我们的AST执行后序遍历的方式 , 那么它的逻辑执行顺序就非常清晰了 , 比如a=1+2*3 , 得到如下的结果:
图9
lua的编译器是典型的单趟编译(one pass ) , 没有显示地生成任何形式的IR , 包括Parse Tree或者是AST , 它所有的编译逻辑都是在里执行 , 直接生成虚拟机指令 , 所以本系列 , 不会对AST着色过的的笔墨 , 后续将会结合一些实例 , 适当结合Parse Tree进行讲解 。
的项目目录结构
本节我们来回顾一下的目录结构 , 并对不同的模块进行说明:
+ 3rd/#引用的第三方库均放置在这里+ bin/#编译生成的二进制文件放置在这里~ clib/#外部要在c层使用Lua的C API , 那么只能调用clib里提供的接口 , 而不能调用其他内部接口luaaux.h#供外部使用的辅助库luaaux.c~ common/#vm和compiler共同使用的结构、接口均放置在这里lua.h#提供lua基本类型的定义 , 错误码定义 , 全项目都可能用到的宏均会放置在这里luamem.h#lua内存分配器luamem.cluaobject.h#lua基本类型luaobject.cluastate.h#虚拟机状态结构 , 以及对其相关操作的接口均放置于此luastate.cluabase.h#lua标准库 , 提供最基础的函数luabase.cluainit.c#第三方库加载机制的函数所在地luastring.h#lua字符串luastring.cluatable.h#lua tableluatable.c~ compiler/#编译器相关的部分放置在这里luacode.h#编译成虚拟机指令的库luacode.clualexer.h#词法分析器lualexer.cluaparser.h#语法分析器luaparser.cluazio.h#协助词法分析器 , 分析文本的库luazio.c+ scripts/#lua脚本测试用例+ test/#测试用例全部放置在这里~ vm/#虚拟机相关的部分放置在这里luado.h#函数调用相关的接口均放置于此luado.cluafunc.h#lua虚拟机函数体luafunc.cluagc.h#gc机制luagc.cluaopcodes.h#OPCODE定义luaopcodes.cmain.cmakefile
我们编译器相关的部分 , 都放在目录之下 , 其中.h|c是词法分析器相关 , .h|c是语法分析器相关(编译流程处理) , .h|c则是生成虚拟机指令的库 , 而.h|c则是定义的地方 。