antlr4使用总结2

接着之前的总结内容,我一直对于解释函数的定义,解释,执行感到比较迷惑,与最终生成的二进制执行不同,我没有 想过模拟栈的处理,因此,对于比如局部变量,全局变量,平台实现的接口函数,自定义函数的调用和执行感到迷惑。

这里主要以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);
            }

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据