六、編譯器(Compiler)﹣生成代碼(Code Generation)(下)
上回提要:我們開始著手編寫編譯部份,從那棵 Parse tree 生成代碼,做法跟之前的 Analyser 差不多,都是用 mutual recursion 來遍歷 Parse tree 。上回我們已經寫好了大部份的編譯,只剩下兩款 expression 未寫好,即 if
和 while
,本節就是要把這兩款 expression 都寫出來。
還記得我們 Wemachine 是如何做 label 的嗎?我們的 Wemachine 只能跳到程式上面已經定義好的 label ,如果要跳轉到的 label 之前還未被定義的話就會當是 runtime error,但問題是,我們要做 if-else
時,如果條件式不成立的話就要跳到 else block ,那可能是十丈遠,不可以跟之前做「相等比較」一樣先執行一次所有程式碼再判斷,因此,我們必須要有一個支援跳到在後面定義的 label 的 Wemachine ,所以第一步我們就要改寫 Wemachine 了。
西傑將會新增一個指令,叫做 vlabel
,定義方法跟 label
一樣,只是跳轉的時候,我們要在 label
前面加上 “_”
,以標示要跳轉的是 vlabel
而不是 label
。另外,跟 label
一樣,我們要定義一個 hash map 以記下 program counter ,但這次不同的是,我們要在程式開始之前就建立好這個 hash map ,或則我們就不能跳轉到未被定義的 label 了。因此,我們最好就是在一開始建立 instruction 列表時就順便建立這個 hash map 。
function Wemachine(code) {
//simple parser
this.vlabelMap = {};
var instructions = code.split(";");
var processedInstructions = [];
for (var i = 0, l = instructions.length; i < l; i++) {
var instruction = trim(instructions[i]);
if (instruction == "") {
continue;
}
var insObj = {};
var opcode = "";
for (var j = 0, k = instruction.length; j < k; j++) {
if (instruction[j] == " ") {
instruction = instruction.substr(j);
break;
} else {
opcode += instruction[j];
}
}
insObj.opcode = opcode;
insObj.operands = [];
var operands = instruction.split(",");
for (var j = 0, k = operands.length; j < k; j++) {
insObj.operands.push(trim(operands[j]));
}
if (insObj.opcode == "vlabel") {
this.vlabelMap[insObj.operands[0]] = i;
}
processedInstructions.push(insObj);
}
this.instructions = processedInstructions;
this.registers = [];
this.pc = 0;
this.labelMap = {};
}
看看新增了的那段代碼,如果我們讀到了 vlabel
的指令,我們就會記下 program counter ,就是這樣簡單了。另外,現在跳轉的方法有所改變,即要加上 “_”
來跳轉到 vlabel,所以我們最好也把跳轉的代碼抽出來。
Wemachine.prototype.easyJump = function (lbl) {
var nextPC;// = this.labelMap[lbl];
if (lbl.substr(0, 1) == "_") {
//it is vlabel
var realLbl = lbl.substr(1);
nextPC = this.vlabelMap[realLbl];
} else {
nextPC = this.labelMap[lbl];
}
if (nextPC == null) {
Errors.push({
type: Errors.RUNTIME_ERROR,
msg: "Label not found",
line: 0
});
} else {
this.pc = nextPC;
}
}
最後要改寫一下那些跳轉程式的寫法,把它們改成使用 easyJump
來跳轉,這裡就用 beq
來做例子。
Wemachine.prototype.beq = function (operand1, operand2, operand3) {
var val1 = this.getRegisterContent(operand1);
var val2 = this.getRegisterContent(operand2);
if (val1 == val2) {
this.easyJump(operand3);
}
}
比之前簡潔了很多吧,其他跳轉的做法也差不多,這裡不詳述了。
好了,改寫了 Wemachine,我們改寫了未來,現在要做的事就是要生成 if
和 while
的 Wemachine code 。
if
Compiler.prototype.evaluateIfNode = function (node) {
var condReg = this.evaluateExpressionNode(node.conditionExpression);
var elseLbl = this.getNextLabel();
var endLbl = this.getNextLabel();
this.writeln("beq " + condReg + "," + this.falseRegister + "," + "_" + elseLbl + ";");
this.evaluateExpressionBlockNode(node.expressions);
this.writeln("j _" + endLbl + ";");
this.writeln("vlabel " + elseLbl + ";");
this.evaluateExpressionBlockNode(node.elseExpressions);
this.writeln("vlabel " + endLbl + ";");
}
if
要做什麼呢?首先就是要做一個判斷,看看條件式是否不成立,不成立的話就要跳到 else
的位置,成立的話只需直接執行落去就可以了。但要緊記,不能一直執行下去,當完成執行時就要跳到 else
block 之後,不然條件式成立與否 else
block 都會運行。 if
就是這樣了,不太難吧,不暪你說,其實 while
更容易!
while
Compiler.prototype.evaluateWhileNode = function (node) {
var whileLbl = this.getNextLabel();
var endLbl = this.getNextLabel();
this.writeln("vlabel " + whileLbl + ";");
var condReg = this.evaluateExpressionNode(node.conditionExpression);
this.writeln("beq " + condReg + "," + this.falseRegister + "," + "_" + endLbl + ";");
this.evaluateExpressionBlockNode(node.expressions);
this.writeln("j _" + whileLbl + ";");
this.writeln("vlabel " + endLbl + ";");
}
又是判斷條件式,如果不成立的話就直接跳到 end
label 結束 while
迴圈,不然就繼續執行,並且每次執行完都跳到最頂再做一次條件式判斷。很容易吧!運行一下看看運作是否正常。
運作正常,那就大功告成啦!
本章就此完結了, Wescript compiler 也可以算是完成了,從讀取 Wescript 到建立 parse tree,再到本章生成代碼,一路走來用的技巧都是 mutual recursion ,可見其重要性,大家要好好掌握這個技巧,那麼大家編寫自己的 compiler 時也能得心應手~