自制Monkey語言編譯器:實現函式閉包功能和為語言增加複雜資料結構
Monkey語言有點類似於JS,它的函式可以當做引數進行傳遞,而且語法支援函式閉包功能,例如下面程式碼:
let newAdder = fn(x) { return fn(y) { return x + y;};};let addTwo = newAdder(3); addTwo(2);
在上面程式碼中,我們把newAdder定義為一個函式變數,該函式里面又返回一個函式,在第二次定義變數addTwo時,它對應的是上面函式返回另一個函式,而且上面函式已經把x變數定義為3,於是addTwo(2)在執行時,它的返回值是5.為了實現這種函式閉包功能,我們必須為每個函式變數配置一個繫結環境,因此對上節程式碼做相應修改如下:
case "FunctionLiteral": var props = {} props.token = node.token props.identifiers = node.parameters props.blockStatement = node.body var funObj = new FunctionCall(props) funObj.enviroment = this.newEnclosedEnvironment(this.enviroment)
上面程式碼為函式構建符號物件時,會專門配置一個繫結環境物件,於是上面程式碼addTwo(3)執行時,它遇到變數x,就能在函式對應的繫結環境中查詢到。我們在函式的解析執行部分做如下修改:
case "CallExpression": .... // change 12 執行函式前保留當前繫結環境 var oldEnviroment = this.enviroment //設定新的變數繫結環境 this.enviroment = functionCall.enviroment //將輸入引數名稱與傳入值在新環境中繫結 for (i = 0; i上面程式碼執行時,在執行呼叫函式前會將解析器的變數繫結環境設定為要執行函式的變數環境,這樣一來在函式體內定義的變數,即使在函式體外查詢不到,但是當函式執行時,還是能透過它自帶的變數繫結環境找到對應變數的值,完成上面程式碼後,我們就可以解釋執行開頭的Monkey程式碼,執行結果如下:
這裡寫圖片描述
示例中的newAdder稱之為高階函式,所謂高階函式就是能返回函式物件或是接收函式物件作為引數的函式。由於它返回的函式包含著自己的變數繫結環境,因此我們也稱newAdder為一個函式閉包。
接下來我們要為Monkey語言增加複雜資料結構的支援,目前我們的語言智慧識別整數,Boolean,這兩種很基礎的資料型別,為了語言的表達力能更強,我們要新增相應的複雜資料型別,例如字串,雜湊表,陣列等,接下來我們先新增的資料型別是字串。
所謂字串就是雙引號中包含一連串字元,例如"Hello World",我們現在lexer裡面增加相應token標誌,在MonkeyLexer.js中新增:
initTokenType() { .... //change 1 this.STRING = 25} nextToken () { .... // change 2 case '"': var str = this.readString() tok = new Token(this.STRING, str, lineCount) break .... }// change 3 readString() { // 越過開始的雙引號 this.readChar() var str ="" while (this.ch != '"' && this.ch != this.EOF) { str += this.ch this.readChar() } if (this.ch != '"') { return undefined } return str }詞法解析器讀取第一個雙引號時,構造一個型別為STRING的token,然後依次讀取後面字元作為token物件內容,直到讀取第二個雙引號為止。完成上面程式碼後,詞法解析器就成功構造了型別為字串的Token。接下來我們在語法解析器中構造對應的語法節點。在MonkeyCompilerParser.js中新增如下程式碼:
class StringLiteral extends Node { constructor(props) { super(props) this.token = props.token this.tokenLiteral = props.token.getLiteral() this.type = "String" } } .... class MonkeyCompilerParser { constructor(lexer) { ... this.prefixParseFns[this.lexer.STRING] = this.parseStringLiteral ... } ... } parseStringLiteral(caller) { var props = {} props.token = caller.curToken return new StringLiteral(props) }上面程式碼定義了一個語法樹節點StringLiteral,然後在語法解析器的建構函式將字串的解析函式parseStringLiteral註冊到字首表示式解析函式呼叫表中,一旦型別為STRING的token物件傳遞給語法解析器時,它會呼叫parseStringLiteral構造一個StringLiteral語法節點。接下來我們要做解析器中,增加對字串節點物件的解析執行。在evaluator.js中新增如下程式碼:
class BaseObject { constructor (props) { ... //change 7 this.STRING_OBJ = "String" } }//change 8class String extends BaseObject { constructor(props) { super(props) this.value = props.value } inspect() { return "content of string is: " + this.value } type() { return this.STRING_OBJ } }class MonkeyEvaluator { .... eval (node) { var props = {} switch (node.type) { case "program": return this.evalProgram(node) // change 9 case "String": props.value = node.tokenLiteral return new String(props) .... } .... } evalInfixExpression(operator, left, right) { .... //change 9 增加字串加法操作 if (left.type() === left.STRING_OBJ && right.type() === right.STRING_OBJ) { return this.evalStringInfixExpression(operator, left, right) } } //change 10 實現字串加法操作 evalStringInfixExpression(operator, left, right) { if (operator != "+") { return this.newError("unknown operator for string operation") } var leftVal = left.value var rightVal = right.value var props = {} props.value = leftVal + rightVal console.log("reuslt of string add is: ", props.value) return new String(props) } .... }程式碼在直譯器中先增加了一個String型別的符號物件,一旦從語法解析器接收到String型別的語法物件時,解析器就會構造對應的符號物件。接著我們增加了對“+”運算子的處理,當做加法時,如果解析器發現加號兩邊對應的都是字串物件,那麼就把兩個字串前後串聯起來,當上面程式碼完成後,我們在編輯框中輸入如下程式碼:
let s1 = "hello ";let s2 = "world!";let s3 = s1 + s2;點選底下的parsing按鈕得到的結果為:
這裡寫圖片描述
從執行結果上看,我們的編譯器正確實現了兩個字串變數的加法操作。
作者:望月從良
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3705/viewspace-2810107/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- swift1.2語言函式和閉包函式介紹Swift函式
- C語言實現的資料結構之------雜湊表C語言資料結構
- Swift 1.1語言第7章 函式和閉包Swift函式
- 資料結構筆試題——基於C語言的連結串列功能函式實現資料結構筆試C語言函式
- 資料結構雜湊表(c語言)資料結構C語言
- 【Go語言學習】匿名函式與閉包Go函式
- 源語言、目標語言、翻譯器、編譯器、直譯器編譯
- C語言-->(十四)結構體、巨集、編譯C語言結構體編譯
- [譯]用javascript實現一門程式語言-語言構想JavaScript
- java開發C語言編譯器:為C語言提供API呼叫JavaC語言編譯API
- Go語言流程控制結構和函式解析Go函式
- 【資料結構】實現順序表(c語言)資料結構C語言
- GO語言————6.9 應用閉包:將函式作為返回值Go函式
- 資料結構——單連結串列介面實現(C語言)資料結構C語言
- 用c語言實現資料結構——單連結串列C語言資料結構
- GO語言————6.8 閉包Go
- 【資料結構】迴圈佇列 C語言實現資料結構佇列C語言
- 實現JavaScript語言直譯器(三)JavaScript
- C語言函式手冊:c語言庫函式大全|C語言標準函式庫|c語言常用函式查詢C語言函式
- 用msys2編譯《自制程式語言》程式碼編譯
- C 語言實現泛型 swap 函式泛型函式
- 資料結構c語言實現順序表基本操作資料結構C語言
- 倉頡程式語言技術指南:巢狀函式、Lambda 表示式、閉包巢狀函式
- Go 語言編譯期斷言Go編譯
- 編譯型語言與解釋型語言編譯
- Go 語言閉包詳解Go
- 為什麼自制指令碼語言是程式語言的最高境界?指令碼
- Go 語言實現解析器翻譯Go
- 中文程式語言中文程式語言實現:翻譯器
- Go 語言函式Go函式
- C語言編譯器手機版C語言編譯
- C語言編譯和連結過程簡介C語言編譯
- C語言編譯工具C語言編譯
- R語言kohonen包主要函式介紹R語言函式
- C 語言複雜知識點
- 解釋型語言、編譯型語言 區別編譯
- 使用函式式語言實踐DDD函式
- 資料結構之---C語言實現快速排序(多個版本)資料結構C語言排序