成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于JavaScript的簡(jiǎn)單解釋器實(shí)現(xiàn)(一)——表達(dá)式的語(yǔ)法樹(shù)生成

DataPipeline / 540人閱讀

摘要:語(yǔ)法樹(shù)這一章主要是完成語(yǔ)法樹(shù)的生成。其中由于函數(shù)聲明部分過(guò)于簡(jiǎn)單,沒(méi)必要生成語(yǔ)法樹(shù),打算留到下一章一起處理。主循環(huán)結(jié)束后數(shù)據(jù)棧中的第一位元素則為語(yǔ)法樹(shù)。這是最后生成的語(yǔ)法樹(shù)總結(jié)總之,語(yǔ)法樹(shù)就算是生成完畢了。

前言

這個(gè)系列是關(guān)于CodeWars上的一條1Kyu題:Simple Interactive Interpreter。也就是實(shí)現(xiàn)一個(gè)簡(jiǎn)單的交互式解釋器。
題目地址:http://www.codewars.com/kata/52ffcfa4aff455b3c2000750/train/javascript
github地址:https://github.com/woodensail/SimpleInteractiveInterpreter
本文地址:http://segmentfault.com/a/1190000004044789

補(bǔ)充

11月26日更新:
增加了對(duì)左結(jié)合運(yùn)算符的支持。
增加了變量統(tǒng)計(jì)功能,用于支持下一步的函數(shù)parser
當(dāng)表達(dá)式不符合語(yǔ)法要求時(shí),拋出異常

實(shí)現(xiàn)要求

具體的細(xì)節(jié)可以參見(jiàn)上面的原題網(wǎng)站,大概的要求如下:
1:支持變量賦值語(yǔ)句

x = 7

2:支持四則運(yùn)算,表達(dá)式中可以使用變量

x = (1 + 2) * y

3:函數(shù)聲明:

fn add x y => x + y

4:函數(shù)調(diào)用

z = add a b

5:其他
也就是命名沖突檢測(cè),作用域鏈等,大家自己看吧。

語(yǔ)法樹(shù)

這一章主要是完成語(yǔ)法樹(shù)的生成。其中由于函數(shù)聲明部分過(guò)于簡(jiǎn)單,沒(méi)必要生成語(yǔ)法樹(shù),打算留到下一章一起處理。所以只做了表達(dá)式的語(yǔ)法樹(shù)生成。

首先,題目所給的語(yǔ)言結(jié)構(gòu)基本上是前綴表達(dá)式和中綴表達(dá)式的混雜。所以只需要將語(yǔ)句里面中綴的部分轉(zhuǎn)化為前綴即可得到波蘭式。
當(dāng)然,我為了方便下一步處理還是選擇將其進(jìn)一步轉(zhuǎn)化為語(yǔ)法樹(shù)的結(jié)構(gòu)。但是實(shí)現(xiàn)思路依舊可以參考波蘭式生成。

準(zhǔn)備工作
var SPACE = {}, params = [], operatorStack = [], dataStack = [SPACE], expressionFlag = true, lValue, rValue, operator, vars = {};

聲明變量:
params 用于存儲(chǔ)函數(shù)調(diào)用的參數(shù),其實(shí)這里不需要初始化,但我懶得改了。
operatorStack 運(yùn)算符棧,用于存儲(chǔ)各種操作符
dataStack 存儲(chǔ)數(shù)據(jù)。包括數(shù)值,變量以及語(yǔ)法樹(shù)的節(jié)點(diǎn)
expressionFlag 由于改語(yǔ)言中沒(méi)有逗號(hào),所以沒(méi)有顯式的標(biāo)志來(lái)分割相鄰的兩個(gè)表達(dá)式。因此需要自行判斷前一個(gè)表達(dá)式是否結(jié)束。
lValue,rValue 類似params, 只不過(guò)是給運(yùn)算符用的,其實(shí)可以去掉,但我懶得改。
operator 一個(gè)用于存儲(chǔ)當(dāng)前運(yùn)算符的臨時(shí)變量

tokens = tokens.slice();
tokens.push(")");
tokens.unshift("(");
while (tokens.length) {
  ……
}
var varList = [];
for (var k in vars) {
    varList.push(k);
}
if (dataStack[0] === SPACE) {
    dataStack.shift();
} else {
    throw "expression error"
}
if (dataStack.length > 1) {
    throw "expression error"
}
return [dataStack[0], varList];

復(fù)制token數(shù)組,防止修改原始數(shù)據(jù)。向開(kāi)頭和結(jié)尾加上括號(hào),以簡(jiǎn)化后面的操作。最后就是開(kāi)始主循環(huán)。
主循環(huán)結(jié)束后數(shù)據(jù)棧中的第一位元素則為語(yǔ)法樹(shù)。若數(shù)據(jù)棧中元素?cái)?shù)量多于一個(gè)或棧低占位符被取出,說(shuō)明語(yǔ)句有錯(cuò)。

主循環(huán)
var next = tokens.pop();

首先取出一個(gè)token。需要注意的是這里用的是pop,也就是從后向前掃描。然后根據(jù)token類型做不同處理。

1:token為運(yùn)算符

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] ) && (next !== "=")) {
            operatorStack.push(next);
            break;
        } else if (operators[operatorStack[operatorStack.length - 1]] > operators[next]) {
            operatorStack.push(next);
            break;
        } else {
            operator = operatorStack.pop();
            lValue = dataStack.pop();
            rValue = dataStack.pop();
            dataStack.push([operator, lValue, rValue]);
        }
    }
    expressionFlag = true;
    continue;
}

a:若此時(shí)運(yùn)算符棧為空,則將該運(yùn)算符入棧。
b:若棧頂運(yùn)算符為右括號(hào),則將該運(yùn)算符入棧。
c:若棧頂運(yùn)算符優(yōu)先級(jí)等于當(dāng)前運(yùn)算符且當(dāng)前運(yùn)算符不是左結(jié)合運(yùn)算符,則將該運(yùn)算符入棧。
d:若棧頂運(yùn)算符優(yōu)先級(jí)小于當(dāng)前運(yùn)算符,則將該運(yùn)算符入棧。
e:若非以上四種情況。則運(yùn)算符棧出棧存入operator,數(shù)據(jù)棧出棧兩次分別存入lValue,rValue,然后將[operator, lValue, rValue]壓入數(shù)據(jù)棧。并繼續(xù)循環(huán)直到出現(xiàn)前四種情況為止。
前面的循環(huán)結(jié)束后將expressionFlag設(shè)為真,以標(biāo)志當(dāng)前表達(dá)式未結(jié)束。最后調(diào)用continue跳過(guò)后面的部分。

2:token為左括號(hào)

else if (next === "(") {
    next = operatorStack.pop();
    while (next !== ")") {
        if (next === void 0) {
            break
        }
        lValue = dataStack.pop();
        rValue = dataStack.pop();
        dataStack.push([next, lValue, rValue]);
        next = operatorStack.pop();
    }
    continue;
}

持續(xù)出棧直到棧頂元素為右括號(hào)為止。對(duì)于每個(gè)出棧的操作符將其存入operator并從數(shù)據(jù)棧中出棧兩次得到lValue和rValue,并將[operator, lValue, rValue]壓入數(shù)據(jù)棧。最后continue跳過(guò)后續(xù)。

3:expressionFlag的判斷

if (expressionFlag) {
    expressionFlag = false;
} else {
    while (operatorStack.length) {
        operator = operatorStack.pop();
        if (operator === ")") {
            operatorStack.push(operator);
            break;
        } else {
            lValue = dataStack.pop();
            rValue = dataStack.pop();
            dataStack.push([operator, lValue, rValue]);
        }
    }
}

若token不是前兩種情況,則需要判斷expressionFlag。若expressionFlag為真則將其置為假,標(biāo)準(zhǔn)該token處理完后,當(dāng)前表達(dá)式可以結(jié)束。
若其為假則說(shuō)明當(dāng)前表達(dá)式已經(jīng)結(jié)束,需要開(kāi)始下一個(gè)表達(dá)式。此時(shí)需要將運(yùn)算符棧重復(fù)出棧并與數(shù)據(jù)棧頂?shù)膬晌缓喜⒑髩喝霐?shù)據(jù)棧,直到棧頂運(yùn)算符為右括號(hào)為止。

4:token為右括號(hào)或其他在函數(shù)列表中不存在的內(nèi)容

if (next === ")") {
    expressionFlag = true;
    operatorStack.push(next);
} else if (!this.functions[next]) {
    if (!this.regexNum.test(next)) {
        vars[next] = 1;
    } else {
        next = Number(next);
    }
    dataStack.push(next);
}

將token入棧,其中若token為右括號(hào),則expressionFlag置真表示表達(dá)式未結(jié)束。若不為右括號(hào),當(dāng)next為純數(shù)字時(shí)將其轉(zhuǎn)化為Number型,否則在變量表中標(biāo)記。

5:token在函數(shù)表中存在

else {
    params = [next];
    for (var i in this.functions[next].params) {
        params.push(dataStack.pop());
    }
    dataStack.push(params);
}

初始化params并且第一位為當(dāng)前token。根據(jù)函數(shù)表中形參的數(shù)量,從數(shù)據(jù)棧中取出同樣數(shù)量的數(shù)據(jù),壓入params。
將params壓入數(shù)據(jù)棧

運(yùn)行分析:

這里用"a*(test q (e+q))-(a+b)/e"做例子來(lái)跟蹤并展示程序是怎樣運(yùn)行的。
首先tokenize,結(jié)果是:

["(","a","*","(","test","q","(","e","+","q",")",")","-","(","a","+","b",")","/","e",")"]

然后開(kāi)始循環(huán),我會(huì)在每個(gè)操作的下發(fā)依次注明操作完成后的數(shù)據(jù)棧,運(yùn)算符棧以及expressionFlag
1:")", 右括號(hào),壓入運(yùn)算符棧。
[] [")"] true
2:"e", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
["e"] [")"] false
3:"/", 運(yùn)算符,棧頂為右括號(hào),壓入運(yùn)算符棧。
["e"] [")", "/"] true
4:")", 右括號(hào),壓入運(yùn)算符棧。
["e"] [")", "/", ")"] true
5:"b", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
["e", "b"] [")", "/", ")"] false
6:"+", 運(yùn)算符,棧頂為右括號(hào),壓入運(yùn)算符棧。
["e", "b"] [")", "/", ")", "+"] true
7:"a", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
["e", "b", "a"] [")", "/", ")", "+"] false
8:"a", 左括號(hào),執(zhí)行運(yùn)算符出棧操作,直到右括號(hào)為止。
["e", ["+","a","b"]] [")", "/"] false
9:"-", 運(yùn)算符,優(yōu)先級(jí)小于棧頂元素,執(zhí)行運(yùn)算符出棧操作,然后壓入運(yùn)算符棧。
[["/",["+","a","b"],"e"]] [")", "-"] true
10:")", 右括號(hào),壓入運(yùn)算符棧。
[["/",["+","a","b"],"e"]] [")", "-", ")"] true
11:")", 右括號(hào),壓入運(yùn)算符棧。
[["/",["+","a","b"],"e"]] [")", "-", ")", ")"] true
12:"q", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
[["/",["+","a","b"],"e"], "q"] [")", "-", ")", ")"] false
13:"+", 運(yùn)算符,棧頂為右括號(hào),壓入運(yùn)算符棧。
[["/",["+","a","b"],"e"], "q"] [")", "-", ")", ")", "+"] true
14:"e", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
[["/",["+","a","b"],"e"], "q", "e"] [")", "-", ")", ")", "+"] false
15:"(", 左括號(hào),執(zhí)行運(yùn)算符出棧操作,直到右括號(hào)為止。
[["/",["+","a","b"],"e"], ["+","e","q"]] [")", "-", ")"] false
16:"q", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。由于expressionFlag為false,需要提前出棧到右括號(hào)為止。
[["/",["+","a","b"],"e"], ["+","e","q"], "q"] [")", "-", ")",] false
17:"test", 函數(shù),執(zhí)行函數(shù)出棧程序。由于expressionFlag為false,需要提前出棧到右括號(hào)為止。
[["/",["+","a","b"],"e"], ["test","q",["+","e","q"]]] [")", "-", ")"] false
18:"(", 左括號(hào),執(zhí)行運(yùn)算符出棧操作,直到右括號(hào)為止。
[["/",["+","a","b"],"e"], ["test","q",["+","e","q"]]] [")", "-"] false
18:"*", 運(yùn)算符,優(yōu)先級(jí)大于等于棧頂運(yùn)算符,壓入運(yùn)算符棧。
[["/",["+","a","b"],"e"], ["test","q",["+","e","q"]]] [")", "-", "*"] true
18:"a", 非運(yùn)算符或括號(hào)或函數(shù),壓入數(shù)據(jù)棧。
[["/",["+","a","b"],"e"], ["test","q",["+","e","q"]], "a"] [")", "-", "*"] false
18:"(", 左括號(hào),執(zhí)行運(yùn)算符出棧操作,直到右括號(hào)為止。
[["-",["*","a",["test","q",["+","e","q"]]],["/",["+","a","b"],"e"]]] [] false

這是最后生成的語(yǔ)法樹(shù):

總結(jié)

總之,語(yǔ)法樹(shù)就算是生成完畢了。上面的代碼還有缺陷,主要是沒(méi)有做異常檢查之類的。但是至少對(duì)于符合語(yǔ)法的表達(dá)式是沒(méi)什么問(wèn)題了。下一章會(huì)做函數(shù)聲明的解析和保存。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78199.html

相關(guān)文章

  • javascript引擎——V8

    摘要:類將源代碼解釋并構(gòu)建成抽象語(yǔ)法樹(shù),使用類來(lái)創(chuàng)建它們,并使用類來(lái)分配內(nèi)存。類抽象語(yǔ)法樹(shù)的訪問(wèn)者類,主要用來(lái)遍歷抽象語(yǔ)法樹(shù)。在該函數(shù)中,先使用類來(lái)生成抽象語(yǔ)法樹(shù)再使用類來(lái)生成本地代碼。 通過(guò)上一篇文章,我們知道了JavaScript引擎是執(zhí)行JavaScript代碼的程序或解釋器,了解了JavaScript引擎的基本工作原理。我們經(jīng)常聽(tīng)說(shuō)的JavaScript引擎就是V8引擎,這篇文章我們...

    luoyibu 評(píng)論0 收藏0
  • 第6章:可維護(hù)性軟件構(gòu)建方法 6.3可維護(hù)性構(gòu)建技術(shù)

    摘要:遵循特定規(guī)則,利用操作符,終止節(jié)點(diǎn)和其他非終止節(jié)點(diǎn),構(gòu)造新的字符串非終結(jié)符是表示字符串的樹(shù)的內(nèi)部節(jié)點(diǎn)。語(yǔ)法中的生產(chǎn)具有這種形式非終結(jié)符終結(jié),非終結(jié)符和運(yùn)算符的表達(dá)式語(yǔ)法的非終結(jié)點(diǎn)之一被指定為根。 大綱 基于狀態(tài)的構(gòu)建 基于自動(dòng)機(jī)的編程 設(shè)計(jì)模式:Memento提供了將對(duì)象恢復(fù)到之前狀態(tài)的功能(撤消)。 設(shè)計(jì)模式:狀態(tài)允許對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變其行為。 表驅(qū)動(dòng)結(jié)構(gòu)* 基于語(yǔ)法的構(gòu)...

    young.li 評(píng)論0 收藏0
  • JavaScript 是如何工作:解析、抽象語(yǔ)法樹(shù)(AST)+ 提升編譯速度5個(gè)技巧

    摘要:無(wú)論你使用的是解釋型語(yǔ)言還是編譯型語(yǔ)言,都有一個(gè)共同的部分將源代碼作為純文本解析為抽象語(yǔ)法樹(shù)的數(shù)據(jù)結(jié)構(gòu)。和抽象語(yǔ)法樹(shù)相對(duì)的是具體語(yǔ)法樹(shù),通常稱作分析樹(shù)。這是引入字節(jié)碼緩存的原因。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 14 篇。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過(guò)了前面的章節(jié),可以在這里找到它們: JavaS...

    raoyi 評(píng)論0 收藏0
  • WebAssembly 那些事兒

    摘要:的目標(biāo)是對(duì)高級(jí)程序中間表示的適當(dāng)?shù)图?jí)抽象,即代碼旨在由編譯器生成而不是由人來(lái)寫。表示把源代碼變成解釋器可以運(yùn)行的代碼所花的時(shí)間表示基線編譯器和優(yōu)化編 WebAssembly 那些事兒 什么是 WebAssembly? WebAssembly 是除 JavaScript 以外,另一種可以在網(wǎng)頁(yè)中運(yùn)行的編程語(yǔ)言,并且相比之下在某些功能和性能問(wèn)題上更具優(yōu)勢(shì),過(guò)去我們想在瀏覽器中運(yùn)行代碼來(lái)對(duì)網(wǎng)...

    邱勇 評(píng)論0 收藏0
  • 瀏覽器是如何工作?(How browser work?)

    摘要:解析器的工作通常分為兩個(gè)內(nèi)容詞法分析器有時(shí)稱為標(biāo)記生成器負(fù)責(zé)把輸入分解為很多符號(hào),解析器負(fù)責(zé)根據(jù)該語(yǔ)言的語(yǔ)法規(guī)則來(lái)分析文檔結(jié)構(gòu),從而構(gòu)建解析樹(shù)。解析器通常會(huì)向詞法分析器詢問(wèn)是否有新的符號(hào),并且試圖通過(guò)一條語(yǔ)法規(guī)則的來(lái)進(jìn)行匹配。 瀏覽器是如何工作的(How browser work) 1. 介紹 1.1 本文涉及到的瀏覽器 1.2 瀏覽器的主要功能 1.3 瀏覽器的主要結(jié)構(gòu) 1.4...

    miguel.jiang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<