前言

昨晚奋斗了一下,终于把这题了解了。今天完善了一下代码,把剩下的部分放上来。目前剩下的有两个主要模块即函数解析与函数执行,以及两个小模块即运算符执行和变量解析。
题目地址: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图。
图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐