摘要:函數(shù)執(zhí)行函數(shù)執(zhí)行使用后續(xù)遍歷的方式來遍歷語法樹。對于每一個子節(jié)點,若其為函數(shù)則遞歸調(diào)用執(zhí)行函數(shù)。如果當(dāng)前方法是運算符方法,則調(diào)用該運算符的執(zhí)行函數(shù),并返回結(jié)果如果當(dāng)前方法是函數(shù),則解析所有形參的值后生產(chǎn)函數(shù)作用域,并以改作用域執(zhí)行當(dāng)前函數(shù)。
前言
昨晚奮斗了一下,終于把這題了解了。今天完善了一下代碼,把剩下的部分放上來。目前剩下的有兩個主要模塊即函數(shù)解析與函數(shù)執(zhí)行,以及兩個小模塊即運算符執(zhí)行和變量解析。
題目地址:http://www.codewars.com/kata/52ffcfa4aff455b3c2000750/train/javascript
github地址:https://github.com/woodensail/SimpleInteractiveInterpreter
前文地址:http://segmentfault.com/a/1190000004044789
本文地址:http://segmentfault.com/a/1190000004047915
var index = tokens.indexOf("=>"), paramObj = {}, params = [], fnName = tokens[1];
初始化參數(shù),paramObj用于統(tǒng)計函數(shù)體中用到的參數(shù),params為形參列表,index為函數(shù)運算符的位置,fnName為函數(shù)名
if (this.vars[fnName] !== void 0) { throw "name conflicting" }
如果全局變量中存在該名稱的變量,則拋出異常
for (var i = 2; i < index; i++) { if (paramObj[tokens[i]]) { throw "param conflicting" } paramObj[tokens[i]] = 1; params.push(tokens[i]); }
統(tǒng)計形參,如果同名的形參則拋出異常。
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}
調(diào)用表達式解析器解析函數(shù)體部分。檢查函數(shù)體中用到的參數(shù),如果存在形參列表中不存在的參數(shù)則拋出異常。
最后將該函數(shù)存入函數(shù)表。
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"; };
按照就優(yōu)先級分別嘗試提取作用域中的變量和全局變量以及key自身。提取完畢后若value不為number則所請求的值不存在。
運算符實現(xiàn)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" };
加減乘除模沒什么特殊的就是解析變量后運算然后返回結(jié)果即可。
賦值語句需要對被賦值變量進行判斷,如果當(dāng)前函數(shù)作用域中有該變量則賦值后返回,如果被賦值對象為數(shù)字,則拋出左值異常。如果函數(shù)表中不存在對應(yīng)函數(shù)則存入全局變量,否則拋出重名異常。
函數(shù)執(zhí)行使用后續(xù)遍歷的方式來遍歷語法樹。先依次計算每個參數(shù)的結(jié)果后,再用獲得的結(jié)果集執(zhí)行根節(jié)點。
Interpreter.prototype.exec = function (syntaxTree, scope) { scope = scope || {}; …… };
形參為語法樹和作用域。若未指定作用域則新建空作用域。
for (var i = 1; i < syntaxTree.length; i++) { if (syntaxTree[i] instanceof Array) { syntaxTree[i] = this.exec(syntaxTree[i], scope); } }
對于每一個子節(jié)點,若其為函數(shù)則遞歸調(diào)用執(zhí)行函數(shù)。這一步執(zhí)行完畢后當(dāng)前參數(shù)列表中應(yīng)該只存在變量或數(shù)字立即量。
if (this.native[name]) { params = syntaxTree.slice(1); params.push(scope); return this.native[name].apply(this, params); }
如果當(dāng)前方法是運算符方法,則調(diào)用該運算符的執(zhí)行函數(shù),并返回結(jié)果
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); }
如果當(dāng)前方法是函數(shù),則解析所有形參的值后生產(chǎn)函數(shù)作用域,并以改作用域執(zhí)行當(dāng)前函數(shù)。
else { return this.extractValue(syntaxTree, scope); }
如果不是以上任一種,則當(dāng)前執(zhí)行的語句為數(shù)據(jù),直接提取后返回。
總結(jié)一個基本的解釋器就算是完成了,有些沒有技術(shù)含量的銜接代碼我沒有貼上來,大家可以去git上看。這個解釋器再加上輸入輸出部分就可以構(gòu)成一個REPL了。順便,曬個AC圖。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/78194.html
摘要:在開始解析之前,先通過詞法分析器運行源碼,這會將源碼打散成語法中全大寫的部分。我們基于每個規(guī)則的名稱的左側(cè)為其創(chuàng)建一個方法,再來看右側(cè)內(nèi)容如果是全大寫的單詞,說明它是一個終止符即一個,詞法分析器會用到它。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/661原文:http://tadeuzagallo.com/blog/writing-a-l...
摘要:引擎可以用標(biāo)準(zhǔn)解釋器或即時編譯器來實現(xiàn),即時編譯器以某種形式將代碼編譯為字節(jié)碼。這里的主要區(qū)別在于不生成字節(jié)碼或任何中間代碼。請注意,不使用中間字節(jié)碼表示法,不需要解釋器。這允許在正常執(zhí)行期間非常短的暫停。 本系列的第一篇文章重點介紹了引擎,運行時和調(diào)用棧的概述。第二篇文章將深入V8的JavaScript引擎的內(nèi)部。我們還會提供一些關(guān)于如何編寫更好的JavaScript代碼的技巧。 概...
摘要:通常一個完成的不僅僅包含了還包括了以及相關(guān)版本該版本在中使用。基于原型函數(shù)先行的語言使用基于原型的的繼承機制,函數(shù)是的第一等公民其他相關(guān)的語言特性編譯型語言把做好的源程序全部編譯成二進制代碼的可運行程序。 轉(zhuǎn)載請注明出處,創(chuàng)作不易,更多文章請戳 https://github.com/ZhengMaste... 前言:JavaScript誕生于1995年,它是一門腳本語言,起初的目...
摘要:無論你使用的是解釋型語言還是編譯型語言,都有一個共同的部分將源代碼作為純文本解析為抽象語法樹的數(shù)據(jù)結(jié)構(gòu)。和抽象語法樹相對的是具體語法樹,通常稱作分析樹。這是引入字節(jié)碼緩存的原因。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 14 篇。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯過了前面的章節(jié),可以在這里找到它們: JavaS...
摘要:類將源代碼解釋并構(gòu)建成抽象語法樹,使用類來創(chuàng)建它們,并使用類來分配內(nèi)存。類抽象語法樹的訪問者類,主要用來遍歷抽象語法樹。在該函數(shù)中,先使用類來生成抽象語法樹再使用類來生成本地代碼。 通過上一篇文章,我們知道了JavaScript引擎是執(zhí)行JavaScript代碼的程序或解釋器,了解了JavaScript引擎的基本工作原理。我們經(jīng)常聽說的JavaScript引擎就是V8引擎,這篇文章我們...
閱讀 727·2023-04-25 20:32
閱讀 2282·2021-11-24 10:27
閱讀 4528·2021-09-29 09:47
閱讀 2248·2021-09-28 09:36
閱讀 3643·2021-09-22 15:27
閱讀 2763·2019-08-30 15:54
閱讀 379·2019-08-30 11:06
閱讀 1277·2019-08-30 10:58