自制Monkey編程語言編譯器:增長數組操做API和Mapsh數據類型

前一節,咱們爲Monkey語言以及其編譯器增長了內置API len,以及數組數據類型,內置的len函數調用能做用到數組和字符串上,分別返回數組的元素個數和字符串的字符長度。本節咱們繼續增長三個能做用到數組上的內置API,這樣Monkey語言能更方便的支持數組操做。express

咱們在這裏要增長的第一個API叫first。他返回數組首個元素,也就是它的做用與myArray[0]等價,但用first得到首個元素,能夠使得代碼的易讀性更強。咱們看看它的實現,在MonkeyEvaluator.js中,增長以下代碼:編程

  
  
  
   
   
            
   
   
  1. 數組

  2. 微信

  3. 數據結構

  4. app

  5. 函數

  6. flex

  7. ui

  8. this

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":            ....    }}

在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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索