基于JavaScript的小型Three-Pass编译器完成

媒介

昨天完成了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"]);
    }
};

总结

这题真是相称简朴,我待会儿去看看末了一题去,那题好像和前两题不太一样。

    原文作者:WoodenSail
    原文地址: https://segmentfault.com/a/1190000004049048
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞