IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    基于JavaScript的简单解释器实现(二)——函数解析与执行

    WoodenSail发表于 2015-11-26 11:47:33
    love 0

    前言

    昨晚奋斗了一下,终于把这题了解了。今天完善了一下代码,把剩下的部分放上来。目前剩下的有两个主要模块即函数解析与函数执行,以及两个小模块即运算符执行和变量解析。
    题目地址:http://www.codewars.com/kata/52ffcfa4aff455b3c2000750/train/javascript
    github地址:https://github.com/woodensail/SimpleInteractiveInterpreter
    前文地址:http://segmentfault.com/a/1190000004044789
    本文地址:http://segmentfault.com/a/1190000004047915

    函数解析

    var index = tokens.indexOf('=>'), paramObj = {}, params = [], fnName = tokens[1];

    初始化参数,paramObj用于统计函数体中用到的参数,params为形参列表,index为函数运算符的位置,fnName为函数名


    if (this.vars[fnName] !== void 0) {
        throw 'name conflicting'
    }

    如果全局变量中存在该名称的变量,则抛出异常


    for (var i = 2; i < index; i++) {
        if (paramObj[tokens[i]]) {
            throw 'param conflicting'
        }
        paramObj[tokens[i]] = 1;
        params.push(tokens[i]);
    }

    统计形参,如果同名的形参则抛出异常。


    var result = this.expressionParser(tokens.slice(index + 1));
    var syntaxTree = result[0], varList = result[1];
    varList.forEach(function (v) {
        if (!paramObj[v]) {
            throw 'nonexistent param'
        }
    });
    this.functions[fnName] = {params: params, syntaxTree: syntaxTree}

    调用表达式解析器解析函数体部分。检查函数体中用到的参数,如果存在形参列表中不存在的参数则抛出异常。
    最后将该函数存入函数表。

    变量解析

    Interpreter.prototype.extractValue = function (key, scope) {
        scope = scope || {};
        var value = scope[key];
        if (value === void 0) {
            value = this.vars[key];
        }
        if (value === void 0) {
            value = key;
        }
        if ('number' === typeof value) {
            return value;
        }
        throw 'nonexistent var';
    };

    按照就优先级分别尝试提取作用域中的变量和全局变量以及key自身。提取完毕后若value不为number则所请求的值不存在。

    运算符实现

    Interpreter.prototype.add = function (x, y, scope) {
        return this.extractValue(x, scope) + this.extractValue(y, scope);
    };
    Interpreter.prototype.sub = function (x, y, scope) {
        return this.extractValue(x, scope) - this.extractValue(y, scope);
    };
    Interpreter.prototype.mul = function (x, y, scope) {
        return this.extractValue(x, scope) * this.extractValue(y, scope);
    };
    Interpreter.prototype.div = function (x, y, scope) {
        return this.extractValue(x, scope) / this.extractValue(y, scope);
    };
    Interpreter.prototype.mod = function (x, y, scope) {
        return this.extractValue(x, scope) % this.extractValue(y, scope);
    };
    Interpreter.prototype.assign = function (x, y, scope) {
        var value = this.extractValue(y, scope);
        if (scope.x !== void 0) {
            return scope[x] = value;
        } else if ('number' === typeof x) {
            throw 'assign to lValue'
        } else if (!this.functions[x]) {
            return this.vars[x] = value;
        }
        throw 'name conflicting'
    };

    加减乘除模没什么特殊的就是解析变量后运算然后返回结果即可。
    赋值语句需要对被赋值变量进行判断,如果当前函数作用域中有该变量则赋值后返回,如果被赋值对象为数字,则抛出左值异常。如果函数表中不存在对应函数则存入全局变量,否则抛出重名异常。

    函数执行

    函数执行使用后续遍历的方式来遍历语法树。先依次计算每个参数的结果后,再用获得的结果集执行根节点。

    Interpreter.prototype.exec = function (syntaxTree, scope) {
        scope = scope || {};
        ……
    };

    形参为语法树和作用域。若未指定作用域则新建空作用域。


    for (var i = 1; i < syntaxTree.length; i++) {
        if (syntaxTree[i] instanceof Array) {
            syntaxTree[i] = this.exec(syntaxTree[i], scope);
        }
    }

    对于每一个子节点,若其为函数则递归调用执行函数。这一步执行完毕后当前参数列表中应该只存在变量或数字立即量。


    if (this.native[name]) {
        params = syntaxTree.slice(1);
        params.push(scope);
        return this.native[name].apply(this, params);
    }

    如果当前方法是运算符方法,则调用该运算符的执行函数,并返回结果


    else if (this.functions[name]) {
        var fun = this.functions[name];
        params = {};
    
        fun.params.forEach(function (key, i) {
            var k = syntaxTree[i + 1];
            params[key] = _this.extractValue(k, scope);
        });
    
        return this.exec(fun.syntaxTree, params);
    }

    如果当前方法是函数,则解析所有形参的值后生产函数作用域,并以改作用域执行当前函数。


     else {
        return this.extractValue(syntaxTree, scope);
    }

    如果不是以上任一种,则当前执行的语句为数据,直接提取后返回。

    总结

    一个基本的解释器就算是完成了,有些没有技术含量的衔接代码我没有贴上来,大家可以去git上看。这个解释器再加上输入输出部分就可以构成一个REPL了。顺便,晒个AC图。
    图片描述



沪ICP备19023445号-2号
友情链接