媒介
昨天完成了codewars上的1级题简朴诠释器完成,本日突发奇想上去看看统共有若干1级题,然后发明统共也只要三题。而且,这三题都是编译器诠释器相干的,所以痛快都做了了事。
昨天做的是简朴诠释器,另有两题离别是编译器以及一个以范例为重点的不完整的类lisp诠释器。个中编译器这题和之前做的诠释器很像,所以就从编译器最先吧:
问题地点:http://www.codewars.com/kata/tiny-three-pass-compiler/train/javascript
github地点:https://github.com/woodensail/SimpleInteractiveInterpreter/blob/master/tiny-three-pass-compiler.js
前文地点:http://segmentfault.com/a/1190000004047915
本文地点:http://segmentfault.com/a/1190000004049048
与前文中诠释器的差异
起首这题的复杂度比之前要低的多,所以几十分钟就完成了。之前问题中的言语还算是构造完整,而这题里的输入都不能算是一个言语,只能说是带参数的表达式罢了。
没有参数,没有全局变量。比拟算术表达式只多了参数罢了。也因而,语法树天生历程非常简朴,基本是和波兰表达式天生没区别了。
这题比之前多出的部份则是语义剖析和汇编代码天生。
语义剖析部份须要将常量运算优化掉,收缩代码长度。
汇编代码天生部份庖代了前一题的实行部份。而是天生并返回汇编代码即可。
Pass1
这个没啥好讲的了,就是波兰表达式的天生略改罢了,修改部份包含多了值栈和参数列表。
别的就是对参数和马上量做了辨别,这一点做的比前一篇要好。前一篇内里参数与马上量部份不分爨带来了不少贫苦。
Compiler.prototype.pass1 = function (program) {
var tokens = this.tokenize(program), index = tokens.indexOf(']'), args = {}, next, dataStack = [];
operatorStack = [];
for (var i = 1; i < index; i++) {
args[tokens[i]] = i - 1;
}
tokens = tokens.slice(index + 1);
tokens.unshift('(');
tokens.push(')');
while ((next = tokens.pop()) !== void 0) {
if (operators[next]) {
while (true) {
if (!operatorStack.length) {
operatorStack.push(next);
break;
} else if (operatorStack[operatorStack.length - 1] === ')') {
operatorStack.push(next);
break;
} else if (operators[operatorStack[operatorStack.length - 1]] >= operators[next]) {
operatorStack.push(next);
break;
} else {
dataStack.push({op: operatorStack.pop(), a: dataStack.pop(), b: dataStack.pop()});
}
}
} else if (next === '(') {
while ((next = operatorStack.pop()) !== ')') {
if (next === void 0) {
break
}
dataStack.push({op: next, a: dataStack.pop(), b: dataStack.pop()});
}
} else if (next === ')') {
operatorStack.push(next);
} else {
if (args[next] !== void 0) {
dataStack.push({op: 'arg', n: args[next]});
} else {
dataStack.push({op: 'imm', n: Number(next)});
}
}
}
return dataStack[0];
};
Pass2
pass2的目标是把马上量运算优化掉。完成体式格局是递归扫描。
假如当前节点是参数或马上量则直接返回当前节点。
不然顺次对当前节点的两个参数挪用pass2。这步事后,a和b应当都是参数或马上量。
假如a和b都是马上量,那末直接盘算当前节点的效果。然后用盘算出的效果构建一个新的马上量末了返回。
反之则直接返回当前节点。
Compiler.prototype.pass2 = function (ast) {
if ((ast.op === 'arg') || (ast.op === 'imm')) {
return ast;
}
ast.a = this.pass2(ast.a);
ast.b = this.pass2(ast.b);
if ((ast.a.op === 'imm') && (ast.b.op === 'imm')) {
return {op: 'imm', n: this.execOp(ast.op, ast.a.n, ast.b.n)}
} else {
return ast;
}
};
Pass3
起首一切操纵都是以’PU’压栈终了的。
个中马上量和参数这俩个离别是将数字和参数放入寄存器后压栈。
其他的操纵则是起首离别实行ab子节点。实行终了后栈顶的一二元素离别是b,a个操纵的效果。经由过程’PO’,’SW’,’PO’掏出后实行算术操纵,末了压栈就完成了。
须要注重的是这类体式格局天生的汇编代码有大批冗余,主如果无用的[‘PU’, ‘PO’]以及[‘PU’, ‘IM/AR’, ‘PU’, ‘PO’ “SW”, “PO”]。
前者能够完整删去,后者能够优化为[“SW” , ‘IM/AR’, “SW”]。
Compiler.prototype.pass3 = function (ast) {
switch (ast.op) {
case 'imm':
return ["IM " + ast.n, "PU"];
case 'arg':
return ["AR " + ast.n, "PU"];
case '+':
return this.pass3(ast.a).concat(this.pass3(ast.b)).concat(["PO", "SW", "PO", "AD", "PU"]);
case '-':
return this.pass3(ast.a).concat(this.pass3(ast.b)).concat(["PO", "SW", "PO", "SU", "PU"]);
case '*':
return this.pass3(ast.a).concat(this.pass3(ast.b)).concat(["PO", "SW", "PO", "MU", "PU"]);
case '/':
return this.pass3(ast.a).concat(this.pass3(ast.b)).concat(["PO", "SW", "PO", "DI", "PU"]);
}
};
总结
这题真是相称简朴,我待会儿去看看末了一题去,那题好像和前两题不太一样。