學習 bison 原理(三)

學習 bison 原理(三) 算法

工做老是不少, 還要學習不少, 因此時間老是不夠的. 本想好好總結多寫
點的, 但是懶?仍是想作別的?, 所以這裏只能簡單描述下了. 數組

在(二)中已經作了很多準備性工做了, 這一步 4. 計算 LR0 狀態集,
結果多是一個非肯定的(有衝突的)有限狀態機. 在 編譯原理 一書
對 LR0 有較詳細的說明, bison 中計算 LR0 的方法也和書上是一致的. 數據結構

LR0 計算狀態的主入口函數爲 LR0.generate_states(), 下面是主要步驟:
   1. 初始化分配計算所需空間, 初始化閉包(Closure)計算所需數據
   2. 創建初始狀態(state), 遍歷每一個狀態, 計算對每一個符號的 GOTO
      函數, 將計算出的新狀態加入到鏈表中
   3. 重複第2步, 直到掃描完成整個鏈表, 表示沒有新的狀態產生了.
      過程當中建立出每一個狀態 shift, reduce 數據.
   4. 設置初態和終態, 將狀態機補全爲增廣自動機. 閉包

這裏關鍵在 generate_states() 裏面的 while 大循環, 其遍歷全部狀態,
從 GOTO 計算出(可能的)新狀態. 在編譯原理龍書 P.157頁 圖4-33 有
給出規範 LR(0)項集族的計算, 基本和這裏的算法一致. 咱們僅描述下
程序中所使用的數據結構. 函數

在 LR0 中的 LR0.core 類用於描述一個狀態(state), 其主要屬性爲:
  number -- 編號, 每一個狀態給予一個從 0 開始的編號.
  accessing_symbol -- 和此狀態關聯的文法符號的編號.
  items[] -- 此狀態的核心(core)項(item)集, 其值是到 Gram.ritem[] 的索引.
      另有屬性 nitems 指項的數量. 性能

一個項 (item) 指在文法G的一個產生式再加上一個位於它的體中某處
的點.(編譯原理書上的定義). 例如一個產生式 A->XYZ 能夠有四個項:
  A->.XYZ    A->X.YZ    A->XY.Z   A->XYZ.
而產生式 A->ε 只有一個項 A->.ε. 學習

在論文1中也有關於項的更數學化的表述, 項型爲 A->α.β, 其中
A->αβ 是文法 G 的一個產生式. 項在 bison 程序中剛好能夠由 Gram.ritem[]
的索引惟一表示. 仔細查看 ritem[] 的結構, 就能夠發現這一點, 這也是
爲何前面用 ritem[] 數組來記錄每一個產生式右部(rhs)的緣由, 我剛開始
看到前面的代碼時, 一直不太明白爲什麼用 ritem[] 結構, 那這裏爲了表示項(item)
就是它的答案了. 測試

LR0.core 類中, 屬性 items[] 中, 有兩點須要注意: 1. items[] 中僅保存
這個狀態的核心(core)項. (因此這個結構名字叫 core?) 2. items[] 元素的值
是到 Gram.ritem[] 的索引, 且按照增序排列. this

在 LR0.generate_states() 中會調用 Closure.closure() 函數以用於計算一個
core(僅含核心項的 state) 的閉包. 這個閉包概念在書上有說明, 這裏不在細述,
加上後面的 new_itemsets() 函數調用, 計算的結果包括:
  1. 從 core 推導出的非 core 的項. 和 core 項構成當前正在掃描的
     這個狀態(this_state)的完整項集(itemset).
  2. 從項集爲每一個符號 X 計算的全部 GOTO(this_state, X).
  3. 獲得: 從當前狀態的全部 shift; 在當前狀態的全部 reduction;
     從當前狀態可達的下一個狀態的集合. 調試

這裏如何斷定某個狀態已經存在了呢? 方法是使用一個 hash 表將全部 core 對象
放在裏面, 鍵就是這個 core 的 items[] 核心項集合. 計算 hash 的方法是將所
有項的編號加起來, 作爲 hash 鍵. 這個方法比較簡單, 並且不隨項集順序變化
而變化(加法有可交換性). hash 提供了O(1)的速度性能, 使得查找添加新的狀態
足夠工程上快速. 具體方法參見 get_state() 函數.

save_shifts() 函數爲當前狀態建立 shifts 結構, 其記錄下在當前狀態下的全部
移入轉移(shift transmition). shifts 結構中的 shifts_arr[] 數組也是有序的,
有序的這一點的重要性在後面也會依賴到.

save_reductions() 函數爲當前狀態建立 reductions 結構, 其記錄下在當前狀態下
可以執行的全部歸約.

若是在一個狀態, 有多個可能的歸約, 就是歸約/歸約(r/r)衝突. 若是有一個歸約,
以及任意符號的移入, 則就是移入/歸約(s/r)衝突. 固然這是指 LR0 下的, 由於 LR0
文法比較弱, 容易產生衝突. 對於這一點編譯原理書上給出的以下文法例子可用於測試:
     S -> L = R | R
     L -> * R | id
     R -> L

在上述的多個步驟, 原 bison 有 print/dump 出信息以方便查看, 我也添加了一些打印
輸出, 建議調試的時候, 仔細看看這些輸出結構, 對於理解算法較有 幫助.

另外, 在這一步驟 4. 設置初態和終態, 將狀態機補全爲增廣自動機. 和編譯原理書上 所說直接加一個產生式 S'->S 以構成增廣文法 G' 相比, bison 彷佛作起來比較笨, 也許是由於早期版本? 後期版本會作得更容易理解嗎?

相關文章
相關標籤/搜索