昨晚奮鬥了一下,終於把這題了解了。今天完善了一下代碼,把剩下的部分放上來。目前剩下的有兩個主要模塊即函數解析與函數執行,以及兩個小模塊即運算符執行和變量解析。
題目地址:http://www.codewars.com/kata/52ffcfa4aff455b3c2000750/train/javascript
github地址:https://github.com/woodensail/SimpleInteractiveInterpreter
前文地址:http://segmentfault.com/a/1190000004044789
本文地址:http://segmentfault.com/a/1190000004047915javascript
var index = tokens.indexOf('=>'), paramObj = {}, params = [], fnName = tokens[1];
初始化參數,paramObj用於統計函數體中用到的參數,params爲形參列表,index爲函數運算符的位置,fnName爲函數名java
if (this.vars[fnName] !== void 0) { throw 'name conflicting' }
若是全局變量中存在該名稱的變量,則拋出異常git
for (var i = 2; i < index; i++) { if (paramObj[tokens[i]]) { throw 'param conflicting' } paramObj[tokens[i]] = 1; params.push(tokens[i]); }
統計形參,若是同名的形參則拋出異常。github
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}
調用表達式解析器解析函數體部分。檢查函數體中用到的參數,若是存在形參列表中不存在的參數則拋出異常。
最後將該函數存入函數表。express
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則所請求的值不存在。segmentfault
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' };
加減乘除模沒什麼特殊的就是解析變量後運算而後返回結果便可。
賦值語句須要對被賦值變量進行判斷,若是當前函數做用域中有該變量則賦值後返回,若是被賦值對象爲數字,則拋出左值異常。若是函數表中不存在對應函數則存入全局變量,不然拋出重名異常。app
函數執行使用後續遍歷的方式來遍歷語法樹。先依次計算每一個參數的結果後,再用得到的結果集執行根節點。函數
Interpreter.prototype.exec = function (syntaxTree, scope) { scope = scope || {}; …… };
形參爲語法樹和做用域。若未指定做用域則新建空做用域。this
for (var i = 1; i < syntaxTree.length; i++) { if (syntaxTree[i] instanceof Array) { syntaxTree[i] = this.exec(syntaxTree[i], scope); } }
對於每個子節點,若其爲函數則遞歸調用執行函數。這一步執行完畢後當前參數列表中應該只存在變量或數字當即量。spa
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圖。