[編譯原理與實踐]求解First集合,並嘗試用Javascript實現

編譯原理與實踐

First集合

理解

求非終結符A的First集合,就是求A全部可能打頭出現的終結符的集合。函數

假設有個文法 ( A=XXX, ... ) ,它定義了什麼是Javascript中的合法變量名 ( 必須以字母或$, _開頭 ) ,那麼 First(A) = { number, $, _ } 。工具

例子

下面經過解例題來描述一種更適合人腦的First集合求法,相似填字遊戲。this

簡單整型表達式文法prototype

exp -> exp addop term | term
addop -> + | -
term -> term mulop factor | factor
mulop -> *
factor -> ( exp ) | number

PS: +, -, *, (, ), number 爲終結符code

步驟一,完整展開文法並編號對象

(1) exp -> exp addop term
(2) exp -> term

(3) addop -> +
(4) addop -> -

(5) term -> term mulop factor
(6) term -> factor

(7) mulop -> *

(8) factor -> ( exp )
(9) factor -> number

步驟二,寫出全部須要求的First集合 ( First集合羣 )繼承

First(exp) = {}
First(addop) = {}
First(term) = {}
First(mulop) = {}
First(factor) = {}

這些First集合都是空集,接下來就是不斷往裏填入終結符。可參考對First集合的理解和特定文法快速腦補進行,也可參考下面的技巧,慢慢來。遊戲

步驟三,從上到下遍歷已編號的產生式並逐條處理ip

處理編號爲(1)式子,exp -> exp addop term。它代表非終結符exp可能還以exp開頭。因而往集合First(exp)中添加集合First(exp)的全部內容 ( 求並集 )。但集合First(exp)爲空集,沒有產生變化。原型

處理編號爲(2)式子,exp -> term,代表非終結符exp可能以非終結符term開頭。因而往集合First(exp)中添加集合First(term)的全部內容 ( 求並集 )。但集合First(term)仍是空集,沒有產生變化。

接着處理(3)、(4)式子,addop -> +、addop -> -,代表非終結符addop可能以終結符 +- 開頭。因而往集合First(addop)中加入 +- 。獲得First(addop) = { +- },First集合羣產生變化,以下。

First(exp) = {}
First(addop) = { +, - }
First(term) = {}
First(mulop) = {}
First(factor) = {}

接着處理(5)、(6)式子,但沒有產生變化。

接着處理(7)式子,mulop -> *,獲得First(mulop) = { * },First集合羣產生變化,以下。

First(exp) = {}
First(addop) = { +, - }
First(term) = {}
First(mulop) = { * }
First(factor) = {}

接着處理(8)、(9)式子,獲得First(factor) = { (, number },產生變化。

First(exp) = {}
First(addop) = { +, - }
First(term) = {}
First(mulop) = { * }
First(factor) = { (, number }

到此第一次遍歷完成,因爲這次遍歷中First集合羣有產生變化,因此還須要從頭再遍歷一次。

第二遍處理(1), (2)式子,因爲目前First集合羣中First(exp)和First(term)仍是都爲空集,因此(1), (2)式子仍是沒有產生變化。

第二遍處理(3), (4)式子,結果是再往集合First(addop)中加入 +-,沒有產生變化。

第二遍處理(5), (6)式子,結果是往集合First(term)加入集合First(factor)的所有內容,產生變化。

First(exp) = {}
First(addop) = { +, - }
First(term) = { (, number }
First(mulop) = { * }
First(factor) = { (, number }

第二遍處理(7), (8), (9)式子,都沒有產生變化。

到此第二次遍歷結束,因爲這次遍歷中First集合羣有產生變化,因此還需從頭再遍歷一次。

第三遍,只有(2)式子產生變化,往集合First(exp)中加入集合First(term)的內容。

First(exp) = { (, number }
First(addop) = { +, - }
First(term) = { (, number }
First(mulop) = { * }
First(factor) = { (, number }

到此第三次遍歷結束,因爲本次遍歷產生變化,還須要再來一次。

第四次遍歷,集合羣沒有產生變化,求解結束,最終答案以下。

First(exp) = { (, number }
First(addop) = { +, - }
First(term) = { (, number }
First(mulop) = { * }
First(factor) = { (, number }

再來解一道例題,並考慮一種特殊的狀況。

虛構的文法

A = a | ε
B = b | ε
C = A B c

PS: a, b, c 爲終結符,ε 爲空字符

步驟1、二,展開並編號,寫出要求的First集合羣

(1) A -> a
(2) A -> ε

(3) B -> b
(4) B -> ε

(5) C -> A B c
First(A) = {}
First(B) = {}
First(C) = {}

步驟三,遍歷處理

處理(1), (2), (3), (4)獲得,First(A) = { a, ε }、First(B) = { b, ε }。

處理(5),獲得非終結符C可能以非終結符A開頭,獲得First(C) = { a, ε }。

注意此時遇到特殊狀況,因爲非終結符A的First集中包含空字符ε,意味着即便非終結符A爲空也是合法的。試想,若A爲空則非終結符C也可能以非終結符B開頭。

PS: 存在定理,當且僅當First(A)包含ε時,非終結符A爲可空的。

繼續處理(5),其實是處理式子C -> B c。獲得First(C) = { a, b, ε }。集合First(B)也包含ε,繼續處理C -> c,獲得First(C) = { a, b, c, ε }。

因爲First集合羣產生變化,再循環處理一遍。

第二次循環處理無變化,求解結束,最終答案以下。

First(A) = { a, ε }
First(B) = { b, ε }
First(C) = { a, b, c, ε }

代碼

接着嘗試將上述的方法整理成代碼,使用Javascript語言,使用用面向對象的方法來表示終結符、非終結符、空符號與產生式。代碼中也將嘗試求解上述的兩個例子。

// 原型繼承輔助函數,配合繼承屬性的xxx.call(this, xxx)使用
function extend (superClass, subClass) {
  var prototype = clean(superClass.prototype)
  prototype.constructor = subClass
  subClass.prototype = prototype

  function clean (prototype) {
    var F = function () {}
    F.prototype = prototype
    return new F()
  }

  return subClass
}

// 終結符類
function Terminator (symbol) {
  this.symbol = symbol
}

// 特殊的終結符,空符號
var NullTerminator = extend(Terminator, function () {
  Terminator.call(this, 'ε')
})

// 非終結符類
function NonTerminator (symbol) {
  this.symbol = symbol
}

// 產生式類
function Production (leftItem, rightItemList) {
  this.leftItem = leftItem
  this.rightItemList = rightItemList
}

// 求並集工具函數
function union (main, sub, judge) {
  var added = null

  var _judge = function (a, b) {
    return a === b
  }

  if (judge) {
    _judge = judge
  }

  for (var i = 0; i < sub.length; i++) {
    var subItem = sub[i]

    for (var j = 0; j < main.length; j++) {
      var mainItem = main[j]
      if (_judge(subItem, mainItem)) {
        break
      }
    }

    if (j >= main.length) {
      main.push(subItem)
      if (!added) {
        added = []
      }
      added.push(subItem)
    }
  }

  return added
}

// 求給定productionList ( 產生式列表 ) ,firstSetGroup ( First集合羣對象 ) 的First集合
function solvefirstSet (productionList, firstSetGroup) {
  while(firstSetGroup.changed) {
    firstSetGroup.changed = false
    for (var i = 0; i < productionList.length; i++) {
      var production = productionList[i]
      dealWith(production)
    }
  }

  function dealWith (production) {
    var main = firstSetGroup.group[production.leftItem.symbol]
    var subList = []

    // 遍歷式子右側,逐個處理
    for (var i = 0; i < production.rightItemList.length; i++) {
      var rightItem = production.rightItemList[i]

      // sub爲右側單個項目對應的First集合
      var sub = null
      if (rightItem instanceof NonTerminator) {
        sub = firstSetGroup.group[rightItem.symbol]
      } else {
        sub = [rightItem]
      }
      subList.push(sub)

      // 若是sub中不包含空符號,則可跳出循環,不然繼續處理下一項
      var canBreak = true
      for (var j = 0; j < sub.length; j++) {
        if (sub[j] instanceof NullTerminator) {
          canBreak = false
        }
      }

      if (canBreak) {
        break
      }
    }

    // 遍歷subList中的sub,每一個子集合都合併到main中
    for (var i = 0; i < subList.length; i++) {
      var sub = subList[i]
      var changed = union(main, sub, function (a, b) {
        return a.symbol === b.symbol
      })

      if(changed) {
        firstSetGroup.changed = true
      }
    }
  }

  return firstSetGroup
}

// 準備數據
var productionList = []

var firstSetGroup = {
  // 初始標記爲true,方便第一次遍歷處理
  changed: true,
  group: {
  }
}

// 初始化簡單整型表達式文法
var exp = new NonTerminator('exp')
var addop = new NonTerminator('addop')
var term = new NonTerminator('term')
var mulop = new NonTerminator('mulop')
var factor = new NonTerminator('factor')

var plus = new Terminator('+')
var minus = new Terminator('-')
var multiple = new Terminator('*')
var leftBracket = new Terminator('(')
var rightBracket = new Terminator(')')
var number = new Terminator('number')

productionList.push(new Production(exp, [exp, addop, term]))
productionList.push(new Production(exp, [term]))

productionList.push(new Production(addop, [plus]))
productionList.push(new Production(addop, [minus]))

productionList.push(new Production(term, [term, mulop, factor]))
productionList.push(new Production(term, [factor]))

productionList.push(new Production(mulop, [multiple]))

productionList.push(new Production(factor, [leftBracket, exp, rightBracket]))
productionList.push(new Production(factor, [number]))


firstSetGroup.group.exp = []
firstSetGroup.group.addop = []
firstSetGroup.group.term = []
firstSetGroup.group.mulop = []
firstSetGroup.group.factor = []

/*
var A = new NonTerminator('A')
var B = new NonTerminator('B')
var C = new NonTerminator('C')

var a = new Terminator('a')
var b = new Terminator('b')
var c = new Terminator('c')

var nt = new NullTerminator()

productionList.push(new Production(A, [a]))
productionList.push(new Production(A, [nt]))

productionList.push(new Production(B, [b]))
productionList.push(new Production(B, [nt]))

productionList.push(new Production(C, [A, B, c]))

firstSetGroup.group.A = []
firstSetGroup.group.B = []
firstSetGroup.group.C = []
*/

console.log(solvefirstSet(productionList, firstSetGroup))

結尾

我想着,如何不帶目的性地作好本身喜歡的,寫出來的文章水分少一點乾貨多一些,互相溝通不要好爲人師(不裝B)認真傾聽探討重點.....還想着,許多聽不清的聲音,不連貫的畫面,不清晰的笑顏......因而,迷(Riddle)同樣地就有了這篇。感受好多事情我都作很差,其中就包括如何把感情傳達給別人(不帶目的性地)。有時就只能打個表情了,⁄(⁄ ⁄ ⁄ω⁄ ⁄ ⁄)⁄

相關文章
相關標籤/搜索