自底向上語法分析


什么是自底向上的語法分析

一個自底向上的語法分析過程對應為一個輸入串構造語法分析書的過程,它從葉子節點開始,通過shift和reduce操作逐漸向上到達根節點

自底向上的語法分析需要一個堆棧來存放解析的符號,例如對於如下語法:

0.	statement -> expr
1.	expr -> expr + factor
2.	         | factor
3.	factor ->  ( expr )
4.	         | NUM

來解析1+2

stack input
null 1 + 2
NUM + 2 開始讀入一個字符,並把對應的token放入解析堆棧,稱為shift操作
factor + 2 根據語法推導式,factor -> NUM,將NUM出棧,factor入棧,這個操作稱為reduce
expr + 2 這里繼續做reduce操作,但是由於語法推導式有兩個產生式,所以需要向前看一個符合才能判斷是進行shift還是reduce,也就是語法解析的LA
expr + 2 shift操作
expr + NUM null shift操作
expr + factor null 根據fator的產生式進行reduce
expr null reduce操作
statement null reduce操作

此時規約到開始符號,並且輸入串也為空,代表語法解析成功

所以實現自底向上的語法解析關鍵就是識別堆棧上是應該進行shift還是reduce操作。

  • 進行暴力匹配,搜索堆棧上的符號和所有的語法推導式進行匹配 x
  • 構造一個狀態機來根據堆棧壓入或彈出后的狀態來決定是否進行reduce操作

使用狀態表來指導操作

  • 先構建有限狀態自動機

  • 再根據狀態機構建跳轉表

EOI NUM + expr term
0 err s1 err state_2 state_3
1 r3 err r3 err err
2 Accept err s4 err err
3 r2 err r2 err err
4 err s1 err err state_5
5 r1 err r1 err err

有限狀態自動機的構建

0.	s -> e
1.	e -> e + t
2.	e -> t
3.	t -> t * f
4.	t -> f
5.	f -> ( e )
6.	f -> NUM

對起始推導式做閉包操作

先在起始產生式->右邊加上一個.

s -> .e

對.右邊的符號做閉包操作,也就是說如果 . 右邊的符號是一個非終結符,那么肯定有某個表達式,->左邊是該非終結符,把這些表達式添加進來

s -> . e
e -> . e + t
e -> .t

對新添加進來的推導式反復重復這個操作,直到所有推導式->右邊是非終結符的那個所在推導式都引入

private void makeClosure() {
    	
    	Stack<Production> productionStack = new Stack<Production>();
    	for (Production production : productions) {
        productionStack.push(production);
      }
    	    	
      while (productionStack.empty() == false) {
    		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)) == false) {
    				closureSet.add(closures.get(i));
    				productionStack.push(closures.get(i));
    			}
    		}
    	}
    }

對引入的產生式進行分區

把 . 右邊擁有相同非終結符的表達式划入一個分區,比如

e -> t .
t ->t . * f

就作為同一個分區。最后把每個分區中的表達式中的 . 右移動一位,形成新的狀態節點

private void partition() {
    	for (int i = 0; i < closureSet.size(); i++) {
    		int symbol = closureSet.get(i).getDotSymbol();
    		if (symbol == SymbolDefine.UNKNOWN_SYMBOL) {
    			continue;
    		}
    		
    		ArrayList<Production> productionList = partition.get(symbol);
    		if (productionList == null) {
    			productionList = new ArrayList<Production>();
    			partition.put(closureSet.get(i).getDotSymbol(), productionList);
    		}
    		
    		if (productionList.contains(closureSet.get(i)) == false) {
    	        productionList.add(closureSet.get(i));	
    		}
    	} 
    }

對所有分區節點構建跳轉關系

根據每個節點 . 左邊的符號來判斷輸入什么字符來跳入該節點

比如, . 左邊的符號是 t, 所以當狀態機處於狀態0時,輸入時 t 時, 跳轉到狀態1。

private void makeTransition() {
    for (Map.Entry<Integer, ArrayList<Production>> entry : partition.entrySet()) {
      GrammarState nextState = makeNextGrammarState(entry.getKey());
      transition.put(entry.getKey(), nextState);
    }
}

對所有新生成的節點重復構建

最后對每個新生成的節點進行重復的構建,直到完成所有所有的狀態節點的構建和跳轉

private void extendFollowingTransition() {
    for (Map.Entry<Integer, GrammarState> entry : transition.entrySet()) {
      GrammarState state = entry.getValue();
      if (state.isTransitionMade() == false) {
        state.createTransition();
      }
    }
}

LookAhead集合對有限狀態機的完善

讓我們來看節點1的兩個產生式

e -> t .
t -> t . * f

這時候通過狀態節點0輸入t跳轉到節點1,但是這時候狀態機無法分清是做shift還是reduce操作, 這種情況也就是稱之為shift / reduce矛盾。

SLR(1)語法

在之前的LL(1)語法分析過程中,有一個FOLLOW set,也就是指的是,對某個非終結符,根據語法推導表達式構建出的所有可以跟在該非終結符后面的終結符集合,我們稱作該非終結符的FOLLOW set.

FOLLOW(s) = {EOI}
FOLLOW(e) = {EOI, },+}
FOLLOW(t) = {EOI, }, + , * } 
FOLLOW(f) = {EOI, }, +, * }

只要判斷當前的輸入字符是否在該產生式的follor set就可以判斷是不是應該進行reduce操作,但是這只能解決一部分的shift / reduce矛盾

所以就需要LALR(1)語法分析了,通過向前看一個符號來解決shift/reduce矛盾

LALR(1)

對於shift.reduce矛盾,我們需要考慮的事,當我們做了reduce操作后,在狀態機當前上下文環境下,是否能合法的跟在reduce后的非終結符的后面。

這個集合,我們稱之為look ahead set, 它是FOLLOW set 的子集。

look ahead set的構建

s -> α .x β, C
x -> . r

C是S的look ahead集合,α, β, r 是0個或多個終結符或非終結符的組合,x是非終結符,那么 x -> . r 的look ahead就是FIRST(β C)

添加look ahead set只要修改在構建閉包部分

private void makeClosure() {
    	Stack<Production> productionStack = new Stack<Production>();
      for (Production production : productions) {
        productionStack.push(production);
      }
    	    	
    	while (!productionStack.empty()) {
    		Production production = productionStack.pop();
    		if (SymbolDefine.isSymbolTerminals(production.getDotSymbol()) == true) {
    			    continue;	
    			}
    		
    		int symbol = production.getDotSymbol();
    		ArrayList<Production> closures = productionManager.getProduction(symbol);
    		ArrayList<Integer> lookAhead = production.computeFirstSetOfBetaAndC();
    		
    		Iterator it = closures.iterator();
    		while (it.hasNext()) {
    			Production oldProduct = (Production) it.next();
    			Production newProduct = (oldProduct).cloneSelf();
    			newProduct.addLookAheadSet(lookAhead);
    			
    			if (!closureSet.contains(newProduct)) {
    				closureSet.add(newProduct);
    				productionStack.push(newProduct);
    				
    				removeRedundantProduction(newProduct);
    			}
    		}
    	}
    }

有限狀態自動機的壓縮

在狀態機里有很多狀態節點,產生式以及產生式中,點的位置是一樣的,唯一不同的是,兩個節點中,產生式對應的look ahead集合不一樣,這樣的節點,我們就可以將他們結合起來。

State Number: 1
EXPR -> TERM .look ahead set: { EOI }
TERM -> TERM .TIMES FACTOR look ahead set: { EOI }
TERM -> TERM .TIMES FACTOR look ahead set: { TIMES }
EXPR -> TERM .look ahead set: { PLUS }
TERM -> TERM .TIMES FACTOR look ahead set: { PLUS }

State Number: 8
EXPR -> TERM .look ahead set: { RIGHT_PARENT }
TERM -> TERM .TIMES FACTOR look ahead set: { RIGHT_PARENT }
TERM -> TERM .TIMES FACTOR look ahead set: { TIMES }
EXPR -> TERM .look ahead set: { PLUS }
TERM -> TERM .TIMES FACTOR look ahead set: { PLUS }

像這種產生式一樣,只有look ahead集合不同的就可以合並為一個節點

public void addTransition(GrammarState from, GrammarState to, int on) {
    	if (isTransitionTableCompressed) { 
    		from = getAndMergeSimilarStates(from);
        	to   = getAndMergeSimilarStates(to);	
    	} 
    	HashMap<Integer, GrammarState> map = transitionMap.get(from);
    	if (map == null) {
    		map = new HashMap<Integer, GrammarState>();
    	} 

    	map.put(on, to);
    	transitionMap.put(from, map);
    }

通過狀態機構建跳轉表

通過之前的算法已經可以給出各個節點之前的跳轉關系,但是還有一個信息沒有表現出來,就是對於當前的輸入是否應該進行reduce

創建redcueMap

先看點是否在產生式的最后來判斷是否可以做reduce操作,

private void  reduce(HashMap<Integer, Integer> map, ArrayList<Production> productions) {
  for (int i = 0; i < productions.size(); i++) {
    if (productions.get(i).canBeReduce()) {
      ArrayList<Integer> lookAhead = productions.get(i).getLookAheadSet();
      for (int j = 0; j < lookAhead.size(); j++) {
        map.put(lookAhead.get(j), (productions.get(i).getProductionNum()));
      }
    }
  }
}

創建跳轉表

public HashMap<Integer, HashMap<Integer, Integer>> getLRStateTable() {
    	Iterator it = null;
        if (isTransitionTableCompressed) {
        	it = compressedStateList.iterator();
        } else {
        	it = stateList.iterator();
        }
        
        while (it.hasNext()) {
        	GrammarState state = (GrammarState) it.next();
        	HashMap<Integer, GrammarState> map = transitionMap.get(state);
        	HashMap<Integer, Integer> jump = new HashMap<Integer, Integer>();
        
            if (map != null) {
            	for (Entry<Integer, GrammarState> item : map.entrySet()) {
            		jump.put(item.getKey(), item.getValue().stateNum);
            	}
            }
            
            HashMap<Integer, Integer> reduceMap = state.makeReduce();
        	if (reduceMap.size() > 0) {
        		for (Entry<Integer, Integer> item : reduceMap.entrySet()) {
        			
        			jump.put(item.getKey(), -(item.getValue()));
        		}
        	}
        	
        	lrStateTable.put(state.stateNum, jump);
        }
        
        return lrStateTable;
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM