前一節,咱們爲Monkey語言以及其編譯器增長了內置API len,以及數組數據類型,內置的len函數調用能做用到數組和字符串上,分別返回數組的元素個數和字符串的字符長度。本節咱們繼續增長三個能做用到數組上的內置API,這樣Monkey語言能更方便的支持數組操做。express
咱們在這裏要增長的第一個API叫first。他返回數組首個元素,也就是它的做用與myArray[0]等價,但用first得到首個元素,能夠使得代碼的易讀性更強。咱們看看它的實現,在MonkeyEvaluator.js中,增長以下代碼:編程
builtins (name, args) { //實現內嵌API switch (name) { // change 1 case "first": if (args.length != 1) { return this.newError("Wrong number of arguments when calling len") } if (args[0].type() != args[0].ARRAY_OBJ) { return this.newError("arguments of first must be ARRAY") } if (args[0].elements.length > 0) { console.log("the first element of array is :", args[0].elements[0].inspect()) return args[0].elements[0] } return null case "len": .... }}
數組
微信
數據結構
app
函數
flex
ui
this
在builtins函數中,咱們增長了對被調函數名字的檢測,當調用函數名爲"first"時,咱們知道代碼執行的是內嵌API。在函數執行時,它先檢測輸入參數的類型是不是數組,是的話,確保數組元素不爲空,而後返回數組中的第一個元素。上面代碼完成後,在頁面的編輯框輸入下面代碼:
let array = [1,2,3,4]first(array);
點擊"parsing"按鈕進行解釋執行後,獲得結果以下:
咱們的編譯器成功解析數組後,在執行first調用時,成功將數組第一個元素返回。
咱們繼續接着實現的第二個API叫rest,它的輸入參數是數組,而後返回一個除了第一個元素外的新數組,它的實現以下:
builtins (name, args) { //實現內嵌API switch (name) { ... //change 2 case "rest": if (args.length != 1) { return this.newError("Wrong number of arguments when calling len") } if (args[0].type() != args[0].ARRAY_OBJ) { return this.newError("arguments of first must be ARRAY") } if (args[0].elements.length > 1) { var props = {} //去掉第一個元素 props.elements = args[0].elements.slice(1) var obj = new Array(props) console.log("rest return: ", obj.inspect()) return obj } return null .... }}
上面代碼執行後,在編輯框中輸入以下代碼:
let array = [1,2,3,4];rest(array);
點擊按鈕"parsing"進行解釋執行後,獲得結果以下:
從返回結果看,函數將輸入數組的第一個元素去除後,返回了一個新數組。 最後一個有關數組操做的API叫push,它的做用是將一個新元素添加到給定數組的末尾,但它並不改變就數組,而是構造一個新數組,新數組包含舊數組的全部元素,同時在末尾添加了新的元素,它的實現以下:
builtins (name, args) { //實現內嵌API switch (name) { .... case "append": if (args.length != 2) { return this.newError("Wrong number of arguments when calling len") } if (args[0].type() != args[0].ARRAY_OBJ) { return this.newError("arguments of first must be ARRAY") } var props = {} props.elements = args[0].elements.slice(0) props.elements.push(args[1]) var obj = new Array(props) console.log("new array after calling append is: ", obj.inspect()) return obj .... }}
完成上面代碼後,在編輯框中輸入以下代碼:
let array = [1,2,3,4];append(array, 5);
而後點擊"parsing"後,編譯器對上面代碼的執行結果以下:
接下來,咱們爲Monkey語言增添一種最爲經常使用的數據結構,那就是map,它可以把key和value一一對應起來,該數據結構是除了數組外,編程中最爲經常使用的數據結構。咱們但願編譯器能支持下面的代碼:
let myHash = {"name":"Jimmy", "age":72, "band":"Led Zeppelin"};if (myHash["age"] == 72) { return 1;}
編譯器在讀取"myHash["age"]"時會找到它對應的數值72。任何數據類型均可以作map的key和value。爲了可以執行map有關的代碼,咱們須要先讓詞法分析器識別有關字符,在MonkeyLexer.js中添加以下代碼:
initTokenType() { .... //change 4 this.LEFT_BRACE = 28 this.RIGHT_BRACE = 29 this.COLON = 30}getLiteralByTokenType(type) { switch (type) { .... // change 5 case this.LEFT_BRACE: reuturn "{" case this.RIGHT_BRACE: return "}" case this.COLON: return ":" }}nextToken () { .... switch (this.ch) { .... // change 6 case '{': tok = new Token(this.LEFT_BRACE, "{", lineCount) break case '}': tok = new Token(this.RIGHT_BRACE, "}", lineCount) break case ':': tok = new Token(this.COLON, ":", lineCount) break .... }}
有了上面代碼後,編譯器就能夠識別與map有關的字符,例如"{","}"和":"。接下來咱們增長對map代碼的語法解析。map的語法結構能夠用下面的格式來抽象表達:
{<expression>:<expression>,...<expression>:<expression>}
也就是map必須以一個左括號開始,中間是表達式加一個冒號而後跟着另外一個表達式,這種格式直到以右括號終止。咱們先爲哈希表定義一個語法節點,在MonkeyCompilerPaser.js中添加以下代碼:
class HashLiteral extends Expression { constructor(props) { super(props) this.token = props.token //for '{' //對應 expression:expression this.keys = props.keys this.values = props.values this.type = "HashLiteral" } getLiteral() { var s = "{" for (var i = 0; i < this.keys.length; i++) { s += this.keys[i].getLiteral(); s += ":" s += this.values[i].getLiteral() if (i < this.keys.length - 1) { s += "," } } s += "}" this.tokenLiteral = s return s }}
上面語法節點的定義邏輯,會體如今接下來實現的語法解析過程當中。語法解析器的職責就是,當讀取到代碼字符串"{"one":1, "two":2}"後,將其解析並生成上面定義的語法節點對象。在實現解析邏輯時,咱們必定要注意對空哈希表"{}"的處理,這些邊緣狀況是最讓人掉頭髮的地方所在。
咱們在前序解析表中定義一個解析函數,當解析器讀取到左括號時,它就從解析表中取出解析函數解讀後面的字符,代碼實現以下:
class MonkeyCompilerParser { constructor(lexer) { .... //change 8 this.prefixParseFns[this.lexer.LEFT_BRACE] = this.parseHashLiteral .... } .... parseHashLiteral(caller) { var props = {} props.token = caller.curToken props.keys = [] props.values = [] while (caller.peekTokenIs(caller.lexer.RIGHT_BRACE) != true) { caller.nextToken() //先解析expression:expression中左邊的算術表達式 var key = caller.parseExpression(caller.LOWEST) //越過中間的冒號 if (!caller.expectPeek(caller.lexer.COLON)) { return null } caller.nextToken() //解析冒號右邊的表達式 var value = caller.parseExpression(caller.LOWEST) props.keys.push(key) props.values.push(value) //接下來必須跟着逗號或者右括號 if (!caller.peekTokenIs(caller.lexer.RIGHT_BRACE) && !caller.expectPeek(caller.lexer.COMMA)) { return null } } //最後必須以右括號結尾 if (!caller.expectPeek(caller.lexer.RIGHT_BRACE)) { return null } var obj = new HashLiteral(props) console.log("parsing map obj: ", obj.getLiteral()) return obj }....}
完成上面代碼後,在編輯框中輸入以下代碼:
let key = 1;let k = 3;let obj = {key : 1, 2 + k : 5}
而後點擊parsing按鈕開始語法解析,執行結果以下:
從上圖能夠看出,咱們的編譯器可以正確解析map的語法代碼。至於有關map對象代碼的執行,咱們將在下一節去實現。
掃描二維碼成爲課程推廣員獲取很多於三成的收益:
視頻講解和代碼演示請點擊‘閱讀原文’。
本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。