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