項目的完整代碼在 C2j-Compilerjava
經過上一篇對幾個構造自動機的基礎數據結構的描述,如今就能夠正式來構造有限狀態自動機node
咱們先用一個小一點的語法推導式來描述這個過程git
s -> e e -> e + t e -> t t -> t * f t -> f f -> ( e ) f -> NUM
狀態0是狀態機的初始狀態,它包含着語法表達式中的起始表達式,也就是編號爲0的表達式:github
0: s -> . e數據結構
這裏的點也就是以前Production類中的dosPos閉包
負責這個操做的方法在StateNodeManager類中,前面先判斷當前目錄下是否是已經構建好語法分析表了,若是有的話就不須要再次構建了。ui
productionManager.buildFirstSets();能夠先略過,後面會講到。this
ProductionsStateNode就是用來描述狀態節點的debug
public static int stateNumCount = 0; /** Automaton state node number */ public int stateNum; /** production of state node */ public ArrayList<Production> productions;
接着就是放入開始符號做爲第一個狀態節點,也就是這一步的初始化code
public void buildTransitionStateMachine() { File table = new File("lrStateTable.sb"); if (table.exists()) { return; } ProductionManager productionManager = ProductionManager.getInstance(); productionManager.buildFirstSets(); ProductionsStateNode state = getStateNode(productionManager.getProduction(Token.PROGRAM.ordinal())); state.buildTransition(); debugPrintStateMap(); }
注意以前的 . ,也就是Production裏的dosPos,這一步就有用了,利用這個點來作閉包操做
對.右邊的符號作閉包操做,也就是說若是 . 右邊的符號是一個非終結符,那麼確定有某個表達式,->左邊是該非終結符,把這些表達式添加進來
s -> . e e -> . e + t e -> . t
對新添加進來的推導式反覆重複這個操做,直到全部推導式->右邊是非終結符的那個所在推導式都引入,這也就是ProductionsStateNode裏的makeClosure方法
主要邏輯就是先將這個節點中的全部產生式壓入堆棧中,再反覆的作閉包操做。closureSet是每一個節點中保存閉包後的產生式
private void makeClosure() { Stack<Production> productionStack = new Stack<Production>(); for (Production production : productions) { productionStack.push(production); } if (Token.isTerminal(production.getDotSymbol())) { ConsoleDebugColor.outlnPurple("Symbol after dot is not non-terminal, ignore and process next item"); continue; } while (!productionStack.empty()) { Production production = productionStack.pop(); int symbol = production.getDotSymbol(); ArrayList<Production> closures = productionManager.getProduction(symbol); for (int i = 0; closures != null && i < closures.size(); i++) { if (!closureSet.contains(closures.get(i))) { closureSet.add(closures.get(i)); productionStack.push(closures.get(i)); } } } }
把 . 右邊擁有相同非終結符的表達式劃入一個分區,好比
s -> . e e -> . e + t
就做爲同一個分區。最後把每一個分區中的表達式中的 . 右移動一位,造成新的狀態節點
s -> e . e -> e . + t
分區操做就在ProductionsStateNode類中的partition方法中
主要邏輯也很簡單,遍歷當前的closureSet,若是分區不存在,就以產生式點的右邊做爲key,產生式列表做爲value,而且若是當前產生式列表裏不包含這個產生式,就把這個產生式加入當前的產生式列表
private void partition() { ConsoleDebugColor.outlnPurple("==== state begin make partition ===="); for (Production production : closureSet) { int symbol = production.getDotSymbol(); if (symbol == Token.UNKNOWN_TOKEN.ordinal()) { continue; } ArrayList<Production> productionList = partition.get(symbol); if (productionList == null) { productionList = new ArrayList<>(); partition.put(production.getDotSymbol(), productionList); } if (!productionList.contains(production)) { productionList.add(production); } } debugPrintPartition(); ConsoleDebugColor.outlnPurple("==== make partition end ===="); }
根據每一個節點 . 左邊的符號來判斷輸入什麼字符來跳入該節點
好比, . 左邊的符號是 t, 因此當狀態機處於狀態0時,輸入時 t 時, 跳轉到狀態1。
. 左邊的符號是e, 因此當狀態機處於狀態 0 ,且輸入時符號e時,跳轉到狀態2:
0 – e -> 2
這個操做的實現再ProductionsStateNode的makeTransition方法中
主要邏輯是遍歷全部分區,每一個分區都是一個新的節點,因此拿到這個分區的跳轉關係,也就是partition的key,即以前產生式的點的右邊。而後構造一個新的節點和兩個節點之間的關係
private void makeTransition() { for (Map.Entry<Integer, ArrayList<Production>> entry : partition.entrySet()) { ProductionsStateNode nextState = makeNextStateNode(entry.getKey()); transition.put(entry.getKey(), nextState); stateNodeManager.addTransition(this, nextState, entry.getKey()); } debugPrintTransition(); extendFollowingTransition(); }
makeNextStateNode的邏輯也很簡單,就是拿到這個分區的產生式列表,而後返回一個新節點
private ProductionsStateNode makeNextStateNode(int left) { ArrayList<Production> productions = partition.get(left); ArrayList<Production> newProductions = new ArrayList<>(); for (int i = 0; i < productions.size(); i++) { Production production = productions.get(i); newProductions.add(production.dotForward()); } return stateNodeManager.getStateNode(newProductions); }
stateNodeManager已經出現不少次了,它是類StateNodeManager,它的做用是管理節點,分配節點,統一節點。以後對節點的壓縮和語法分析表的最終構建都在這裏完成,這是後話了。
上面用到的兩個方法:
transitionMap至關於一個跳轉表:key是起始節點,value是一個map,這個map的key是跳轉關係,也就是輸入一個終結符或者非終結符,value則是目標節點
public void addTransition(ProductionsStateNode from, ProductionsStateNode to, int on) { HashMap<Integer, ProductionsStateNode> map = transitionMap.get(from); if (map == null) { map = new HashMap<>(); } map.put(on, to); transitionMap.put(from, map); }
getStateNode先從判斷若是這個節點沒有建立過,建立過的節點都會加入stateList中,就建立一個新節點。若是存在就會返回這個原節點
public ProductionsStateNode getStateNode(ArrayList<Production> productions) { ProductionsStateNode node = new ProductionsStateNode(productions); if (!stateList.contains(node)) { stateList.add(node); ProductionsStateNode.increaseStateNum(); return node; } for (ProductionsStateNode sn : stateList) { if (sn.equals(node)) { node = sn; } } return node; }
這時候的第一輪新節點纔剛剛完成,到等到全部節點都完成節點的構建纔算是真正的完成,在makeTransition中調用的extendFollowingTransition正是這個做用
private void extendFollowingTransition() { for (Map.Entry<Integer, ProductionsStateNode> entry : transition.entrySet()) { ProductionsStateNode state = entry.getValue(); if (!state.isTransitionDone()) { state.buildTransition(); } } }
建立有限狀態自動機的四個步驟
至此咱們對
public void buildTransition() { if (transitionDone) { return; } transitionDone = true; makeClosure(); partition(); makeTransition(); }
的四個過程都已經完成,自動機的構建也算完成,應該進行語法分析表的建立了,可是這個自動機還有些問題,下一篇會來改善它。
另外個人github博客:https://dejavudwh.cn/