昨天完成了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/1190000004049048javascript
首先這題的複雜度比以前要低的多,因此幾十分鐘就完成了。以前題目中的語言還算是結構完整,而這題裏的輸入都不能算是一個語言,只能說是帶參數的表達式而已。
沒有參數,沒有全局變量。相比算術表達式只多了參數而已。也所以,語法樹生成過程異常簡單,基本是和波蘭表達式生成沒區別了。java
這題比以前多出的部分則是語義分析和彙編代碼生成。
語義分析部分須要將常量運算優化掉,縮短代碼長度。
彙編代碼生成部分取代了前一題的執行部分。而是生成並返回彙編代碼便可。git
這個沒啥好講的了,就是波蘭表達式的生成略改而已,改動部分包括多了值棧和參數列表。
另外就是對參數和當即量作了區分,這一點作的比前一篇要好。前一篇裏面參數與當即量部分不分家帶來了很多麻煩。github
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。這步事後,a和b應該都是參數或當即量。
若是a和b都是當即量,那麼直接計算當前節點的結果。而後用計算出的結果構建一個新的當即量最後返回。
反之則直接返回當前節點。segmentfault
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; } };
首先全部操做都是以'PU'壓棧結束的。
其中當即量和參數這倆個分別是將數字和參數放入寄存器後壓棧。
其餘的操做則是首先分別執行ab子節點。執行完畢後棧頂的一二元素分別是b,a個操做的結果。經過'PO','SW','PO'取出後執行算術操做,最後壓棧就完成了。優化
須要注意的是這種方式生成的彙編代碼有大量冗餘,主要是無用的['PU', 'PO']以及['PU', 'IM/AR', 'PU', 'PO' "SW", "PO"]。
前者能夠徹底刪去,後者能夠優化爲["SW" , 'IM/AR', "SW"]。this
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"]); } };
這題真是至關簡單,我待會兒去看看最後一題去,那題彷佛和前兩題不太同樣。prototype