接着之前的总结内容,我一直对于解释函数的定义,解释,执行感到比较迷惑,与最终生成的二进制执行不同,我没有 想过模拟栈的处理,因此,对于比如局部变量,全局变量,平台实现的接口函数,自定义函数的调用和执行感到迷惑。
这里主要以lua为例子进行测试的,我最终要实现的是执行grammars-v4中的lua的下列代码:
-- defines a factorial function function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print("enter a number:") a = 5 -- 随便的一个数字 print(fact(a))
为了保存变量,或者程序执行的全局变量, 我在自己的LuaListener中加了部分变量来保存全局的函数或自定义的函数或变量
export default class myLuaListener extends LuaListener { constructor() { super(); this.global_api = { print: function(...args) { console.log("lua print:", ...args); } }; this.state_stack = []; this.nodeid = 0; this.prog = { type: "chunk", nodeid:0, block:[], }; this.var_map = {}; } //... }
我的想法是如果底层提供了某个api,则优先底层的函数,这样这些函数就类似于保留字或者关键字了。为了处理全局变量 和局部变量,在函数的调用中,可以将局部变量和全局变量合并为新的结构,在这一点上,js的…的方式是很方便的了。
function eval(node, local_obj) { //... switch(node.type) { //... case "func_dec": { let funcname = this.eval(node.funcname, local_obj); local_obj[funcname] = node.functionbody; return local_obj[funcname]; } case "make_functioncall": { let ret = null; ret = this.eval(node.functioncall, local_obj); return ret; } case "functioncall": { let ret = null; let varorexp = this.eval(node.varorexp, local_obj); let args = this.eval(node.nameandargs, local_obj); if(this.global_api[varorexp]) ret = this.global_api[varorexp](...args); else { let func = local_obj[varorexp]; let parlist = func.parlist; let localobj = {}; if(parlist && parlist.namelist) { let names = this.eval(parlist.namelist, local_obj); for(let i =0, j=names.length; i<j; i++) { localobj[names[i]] = args[i]; } } for(let i =0,j=func.block.length; i<j;i++) { ret = this.eval(func.block[i], {...local_obj, ...localobj}); } //,,, } //... }
函数的调用必须要有函数名,根据lua的语法,无论是var或者exp,最后必须产生一个名称, 如果在global_api中,实现了 函数,则直接调用其实现。否则就认为是代码自己实现的,则从local_obj中提取对应的函数定义的结构,然后根据 parlist,依次将参数的名字和参数的值对应起来,如果参数的值多于参数的名字个数,会被直接忽略(我没有处理不定 参数)。然后生成新的本地变量来执行函数体, {…local_obj, ….localobj} 后面的是新组成的,在js中这样的 写法确保后面相同的key会覆盖前面的,比如{…{a:4},…{a:5}} 最后生成的结构是{a:5}, 这样来处理局部变量 和全局变量的优先级。 但对于lua的下面的这种赋值,在生成的语法树中,会使用到prefixexp的调用:
a = test(1,2) -- test为使用lua实现的函数
prefixexp下可以是var, 也可以是exp, 如果是exp, 则这个exp是需要执行的, 如果是var_, 则真实返回的应该是变量的值, 而不是变量,如果exp是函数,返回的应该是函数的执行结果,因此,就需要新生成函数的执行节点了
case "prefixexp": { let ret = null; if (node.varorexp) { let var_ = this.eval(node.varorexp, local_obj); if(node.varorexp.type === "varorexp_exp") { ret = var_; } else { ret = local_obj[var_]; } } if(ret.type === "functionbody") { ret = this.eval({...node, type: "functioncall"}, local_obj); } return ret; }
在eval的参数中,我每次都传进去了一个local_obj的结构,这个用来保存当前用的变量列表, 如果这个结构中放入了预先设置的变量,则可以直接在执行过程中使用。
我用来解释lua执行的处理流程:
const chars = new antlr4.InputStream(lua_script); const lexer = new LuaLexer(chars); const tokens = new antlr4.CommonTokenStream(lexer); const parser = new LuaParser(tokens); parser.buildParseTrees = true; const tree = parser.chunk(); const printer = new myLuaListener(); antlr4.tree.ParseTreeWalker.DEFAULT.walk(printer, tree); console.log("printer.prog===", printer.prog); try { let ret = printer.eval(printer.prog, printer.var_map); console.log("eval ret=", ret, printer.var_map); } catch (e) { console.log("eval error===", e); }