自底向上语法分析

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

一个自底向上的语法分析过程对应为一个输入串构造语法分析书的过程,它从叶子节点开始,通过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;
    }
原文地址:https://www.cnblogs.com/secoding/p/11193716.html