摘要:語法分析對(duì)輸入的文本按照語法規(guī)則進(jìn)行分析并確定其語法結(jié)構(gòu)的一種過程,稱為語法分析。遞歸下降分析法遞歸下降分析法,也稱為自頂向下分析法。表達(dá)式代碼生成我們通常用的四則運(yùn)算表達(dá)式是中綴表達(dá)式,但是對(duì)于計(jì)算機(jī)來說中綴表達(dá)式不便于計(jì)算。
四則運(yùn)算的語法規(guī)則(語法規(guī)則是分層的)
x* 表示 x 出現(xiàn)零次或多次
x | y 表示 x 或 y 將出現(xiàn)
( ) 圓括號(hào),用于語言構(gòu)詞的分組
以下規(guī)則從左往右看,表示左邊的表達(dá)式還能繼續(xù)往下細(xì)分成右邊的表達(dá)式,一直細(xì)分到不可再分為止。
expression: addExpression
addExpression: mulExpression (op mulExpression)*
mulExpression: term (op term)*
term: "(" expression ")" | integerConstant
op: + - * /
PS: addExpression 對(duì)應(yīng) + - 表達(dá)式,mulExpression 對(duì)應(yīng) * / 表達(dá)式。
語法分析對(duì)輸入的文本按照語法規(guī)則進(jìn)行分析并確定其語法結(jié)構(gòu)的一種過程,稱為語法分析。
一般語法分析的輸出為抽象語法樹(AST)或語法分析樹(parse tree)。但由于四則運(yùn)算比較簡(jiǎn)單,所以這里采取的方案是即時(shí)地進(jìn)行代碼生成和錯(cuò)誤報(bào)告,這樣就不需要在內(nèi)存中保存整個(gè)程序結(jié)構(gòu)。
先來看看怎么分析一個(gè)四則運(yùn)算表達(dá)式 1 + 2 * 3。
首先匹配的是 expression,由于目前 expression 往下分只有一種可能,即 addExpression,所以分解為 addExpression。
依次類推,接下來的順序?yàn)?mulExpression、term、1(integerConstant)、+(op)、mulExpression、term、2(integerConstant)、*(op)、mulExpression、term、3(integerConstant)。
如下圖所示:
這里可能會(huì)有人有疑問,為什么一個(gè)表達(dá)式搞得這么復(fù)雜,expression 下面有 addExpression,addExpression 下面還有 mulExpression。
其實(shí)這里是為了考慮運(yùn)算符優(yōu)先級(jí)而設(shè)的,mulExpr 比 addExpr 表達(dá)式運(yùn)算級(jí)要高。
1 + 2 * 3 compileExpression ???|?compileAddExpr ???|??|?compileMultExpr ???|??|??|?compileTerm ???|??|??|??|_?matches integerConstant push 1 ?|??|??|_ ? ?|??|?matches "+" ???|??|?compileMultExpr ???|??|??|?compileTerm ???|??|??|??|_?matches integerConstant push 2 ???|??|??|?matches "*" ???|??|??|?compileTerm ??|??|??|??|_?matches integerConstant push 3 ???|??|??|_?compileOp("*") * ???|??|_?compileOp("+") + ? |_
有很多算法可用來構(gòu)建語法分析樹,這里只講兩種算法。
遞歸下降分析法遞歸下降分析法,也稱為自頂向下分析法。按照語法規(guī)則一步步遞歸地分析 token 流,如果遇到非終結(jié)符,則繼續(xù)往下分析,直到終結(jié)符為止。
LL(0)分析法遞歸下降分析法是簡(jiǎn)單高效的算法,LL(0)在此基礎(chǔ)上多了一個(gè)步驟,當(dāng)?shù)谝粋€(gè) token 不足以確定元素類型時(shí),對(duì)下一個(gè)字元采取“提前查看”,有可能會(huì)解決這種不確定性。
以上是對(duì)這兩種算法的簡(jiǎn)介,具體實(shí)現(xiàn)請(qǐng)看下方的代碼實(shí)現(xiàn)。
表達(dá)式代碼生成我們通常用的四則運(yùn)算表達(dá)式是中綴表達(dá)式,但是對(duì)于計(jì)算機(jī)來說中綴表達(dá)式不便于計(jì)算。所以在代碼生成階段,要將中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式。
后綴表達(dá)式
后綴表達(dá)式,又稱逆波蘭式,指的是不包含括號(hào),運(yùn)算符放在兩個(gè)運(yùn)算對(duì)象的后面,所有的計(jì)算按運(yùn)算符出現(xiàn)的順序,嚴(yán)格從左向右進(jìn)行(不再考慮運(yùn)算符的優(yōu)先規(guī)則)。
示例:
中綴表達(dá)式: 5 + 5 轉(zhuǎn)換為后綴表達(dá)式:5 5 +,然后再根據(jù)后綴表達(dá)式生成代碼。
// 5 + 5 轉(zhuǎn)換為 5 5 + 再生成代碼 push 5 push 5 add代碼實(shí)現(xiàn)
編譯原理的理論知識(shí)像天書,經(jīng)常讓人看得云里霧里,但真正動(dòng)手做起來,你會(huì)發(fā)現(xiàn),其實(shí)還挺簡(jiǎn)單的。
如果上面的理論知識(shí)看不太懂,沒關(guān)系,先看代碼,再和理論知識(shí)結(jié)合起來看。
注意:這里需要引入上一篇文章詞法分析的代碼。
// 匯編代碼生成器 function AssemblyWriter() { this.output = "" } AssemblyWriter.prototype = { writePush(digit) { this.output += `push ${digit} ` }, writeOP(op) { this.output += op + " " }, //輸出匯編代碼 outputStr() { return this.output } } // 語法分析器 function Parser(tokens, writer) { this.writer = writer this.tokens = tokens // tokens 數(shù)組索引 this.i = -1 this.opMap1 = { "+": "add", "-": "sub", } this.opMap2 = { "/": "div", "*": "mul" } this.init() } Parser.prototype = { init() { this.compileExpression() }, compileExpression() { this.compileAddExpr() }, compileAddExpr() { this.compileMultExpr() while (true) { this.getNextToken() if (this.opMap1[this.token]) { let op = this.opMap1[this.token] this.compileMultExpr() this.writer.writeOP(op) } else { // 沒有匹配上相應(yīng)的操作符 這里為沒有匹配上 + - // 將 token 索引后退一位 this.i-- break } } }, compileMultExpr() { this.compileTerm() while (true) { this.getNextToken() if (this.opMap2[this.token]) { let op = this.opMap2[this.token] this.compileTerm() this.writer.writeOP(op) } else { // 沒有匹配上相應(yīng)的操作符 這里為沒有匹配上 * / // 將 token 索引后退一位 this.i-- break } } }, compileTerm() { this.getNextToken() if (this.token == "(") { this.compileExpression() this.getNextToken() if (this.token != ")") { throw "缺少右括號(hào):)" } } else if (/^d+$/.test(this.token)) { this.writer.writePush(this.token) } else { throw "錯(cuò)誤的 token:第 " + (this.i + 1) + " 個(gè) token (" + this.token + ")" } }, getNextToken() { this.token = this.tokens[++this.i] }, getInstructions() { return this.writer.outputStr() } } const tokens = lexicalAnalysis("100+10*10") const writer = new AssemblyWriter() const parser = new Parser(tokens, writer) const instructions = parser.getInstructions() console.log(instructions) // 輸出生成的匯編代碼 /* push 100 push 10 push 10 mul add */
編譯原理實(shí)戰(zhàn)入門:用 JavaScript 寫一個(gè)簡(jiǎn)單的四則運(yùn)算編譯器(一)詞法分析
編譯原理實(shí)戰(zhàn)入門:用 JavaScript 寫一個(gè)簡(jiǎn)單的四則運(yùn)算編譯器(二)語法分析
編譯原理實(shí)戰(zhàn)入門:用 JavaScript 寫一個(gè)簡(jiǎn)單的四則運(yùn)算編譯器(三)模擬執(zhí)行
編譯原理實(shí)戰(zhàn)入門:用 JavaScript 寫一個(gè)簡(jiǎn)單的四則運(yùn)算編譯器(四)結(jié)語
完整源碼
參考資料:計(jì)算機(jī)系統(tǒng)要素文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105165.html
摘要:四則運(yùn)算編譯器,雖然說功能很簡(jiǎn)單,只能編譯四則運(yùn)算表達(dá)式。再?gòu)?fù)雜的編譯器再簡(jiǎn)單的編譯器,功能上是差不多的,只是復(fù)雜的編譯器實(shí)現(xiàn)上會(huì)更困難。每一章都是理論與實(shí)踐結(jié)合的經(jīng)典,從計(jì)算機(jī)硬件知識(shí)到軟件體系,再到編譯原理和操作系統(tǒng)。 四則運(yùn)算編譯器,雖然說功能很簡(jiǎn)單,只能編譯四則運(yùn)算表達(dá)式。但是編譯原理前端部分幾乎都有涉及,詞法分析,語法分析,還有代碼生成。 再?gòu)?fù)雜的編譯器、再簡(jiǎn)單的編譯器,功能...
摘要:一般的程序,是無法直接執(zhí)行的,因?yàn)橹荒茏R(shí)別機(jī)器指令。所以要想執(zhí)行一個(gè)程序,首先要將高級(jí)語言編寫的程序翻譯為匯編代碼,再將匯編代碼翻譯為機(jī)器指令,這樣才能識(shí)別并執(zhí)行。 編譯器 編譯器是一個(gè)程序,作用是將一門語言翻譯成另一門語言。 一般的程序,CPU 是無法直接執(zhí)行的,因?yàn)?CPU 只能識(shí)別機(jī)器指令。所以要想執(zhí)行一個(gè)程序,首先要將高級(jí)語言編寫的程序翻譯為匯編代碼,再將匯編代碼翻譯為機(jī)器指令...
摘要:棧在內(nèi)存中,棧的特點(diǎn)是只能在同一端進(jìn)行插入和刪除的操作,即只有和兩種操作。指令的作用是將一個(gè)操作數(shù)推入棧中。指令的作用是執(zhí)行兩次操作,彈出兩個(gè)操作數(shù)和,然后執(zhí)行,再將結(jié)果到棧中。 現(xiàn)在來模擬一下 CPU 執(zhí)行機(jī)器指令的情況,由于匯編代碼和機(jī)器指令一一對(duì)應(yīng),所以我們可以創(chuàng)建一個(gè)直接執(zhí)行匯編代碼的模擬器。在創(chuàng)建模擬器前,先來講解一下相關(guān)指令的操作。 棧 在內(nèi)存中,棧的特點(diǎn)是只能在同一端進(jìn)行...
摘要:入門,第一個(gè)這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數(shù)式編程語言,它的代碼運(yùn)行在之上。它通過編輯類工具,帶來了先進(jìn)的編輯體驗(yàn),增強(qiáng)了語言服務(wù)。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經(jīng)到來了,總結(jié)過去的 2017,相信小伙們一定有很多收獲...
摘要:入門,第一個(gè)這是一門很新的語言,年前后正式公布,算起來是比較年輕的編程語言了,更重要的是它是面向程序員的函數(shù)式編程語言,它的代碼運(yùn)行在之上。它通過編輯類工具,帶來了先進(jìn)的編輯體驗(yàn),增強(qiáng)了語言服務(wù)。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不覺已經(jīng)到來了,總結(jié)過去的 2017,相信小伙們一定有很多收獲...
閱讀 3254·2021-11-12 10:36
閱讀 1344·2019-08-30 15:56
閱讀 2473·2019-08-30 11:26
閱讀 580·2019-08-29 13:00
閱讀 3635·2019-08-28 18:08
閱讀 2775·2019-08-26 17:18
閱讀 1930·2019-08-26 13:26
閱讀 2459·2019-08-26 11:39