本篇文章内的源码: 这里
一. SLR文法
在上文我们知道LR(0)
文法有移入-归约冲突和归约-归约冲突。
回想一下在构建LR(0)分析表时,我们何时进行归约的,只要项目集中有归约项目,遇到任何终结符和终止符号$
都进行归约。
但是其实是不对的,应该是遇到归约产生式左部非终结符的后继符号集才能进行归约的。
例如文法
S -> Ab ; A->a
,现在有一个归约项目a•
,对应的产生式就是A->a
; 它遇到终结符b
才能进行归约,因为只有b
能出现非终结符A
后面,也就是A
的后继符号集FOLLOW(A)
。
1.1 SLR分析表构造算法
SLR分析表与LR(0)分析表的区别就是: LR(0)分析表是整个终结符集和结束符号
$
,而SLR分析表是产生式左部的后继符号集。
1.2 代码实现
/**
* 得到文法对应的 SLR 分析表 actionMap 和 gotoMap
* @param grammar
* @param allItemSetList
* @param actionMap
* @param gotoMap
*/
private static void createSLRTable(Grammar grammar, List allItemSetList,
Map> actionMap,
Map> gotoMap) {
// 获取文法的后继符号集
Map followSetMap = grammar.getFollowSetMap();
// 开始符号
Symbol start = grammar.getStart();
// 所有的非终结符
Set vtSymbols = grammar.getVtSet();
// 将结束符号添加进去
vtSymbols.add(Symbol.END);
// 遍历文法所有的项目集
for (ProductionItemSet itemSet : allItemSetList) {
// 遍历项目集中的项目
for (ProductionItem item : itemSet.getItemSet()) {
// 得到当前项目 pos 位置的符号
Symbol posSymbol = item.getPosSymbol();
// 如果是非终结符,那么就要添加到 gotoMap 中
if (!posSymbol.isTerminator()) {
Utils.addToDoubleMapPrintConflict(gotoMap, itemSet, posSymbol, LR0Utils.Goto(itemSet, posSymbol, grammar),
"gotoMap 有冲突: old: %s, new: %s");
} else {
// 是终结符但不是结束符号 END
if (!Symbol.END.equals(posSymbol)) {
// 获取后继项目集
ProductionItemSet nextItemSet = LR0Utils.Goto(itemSet, posSymbol, grammar);
// 想 action 中添加移入操作action
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, posSymbol, Action.createS(nextItemSet),
"actionMap 有冲突: old: %s, new: %s");
} else {
// 当前项目是 aBb• 格式,说明需要归约,这个就是归约需要的产生式
Production production = item.getProduction();
// 如果产生式左部和开始符号相同,说明是总结了,添加 acc 的action
if (start.equals(production.getLeft())) {
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, posSymbol, Action.createAcc(),
"actionMap 有冲突: old: %s, new: %s");
} else {
// /**
// * 在LR0 算法中,对于 aBb• 这类项目,只要它对应的产生式不是开始符号的产生式
// * 那么遇到任意终结符包括终止符号$ 都进行归约。
// * 但是这种方式是会有冲突的,例如:
// * 1. 对于同一个项目集 itemSet 中有两个项目 S-> a• 和 S-> a•b,
// * 遇到终结符b,那么既可以归约,又可以移入,这就是移入-归约冲突
// * 2. 对于同一个项目集 itemSet 中有两个项目 S-> a• 和 A-> b•
// * 两个项目都可以归约,但是归约的产生式不同,这个时候就是归约-归约冲突
// */
// for (Symbol symbol : vtSymbols) {
// Utils.addToDoubleMapPrintConflict(actionMap, itemSet, symbol, Action.createR(production),
// "actionMap 有冲突: old: %s, new: %s");
// }
/**
* 在SLR 算法中,对于 aBb• 这类项目,只要它对应的产生式不是开始符号的产生式
* 那么只有遇到产生式左部符号的后继符号集才能都进行归约。
* 这样SLR 与 LR0 对比,它进行归约的操作少的多。
*/
FollowSet followSet = followSetMap.get(production.getLeft());
for (Symbol symbol : followSet.getSet()) {
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, symbol, Action.createR(production),
"actionMap 有冲突: old: %s, new: %s");
}
}
}
}
}
}
}
1.3 SLR文法的不足
SLR文法虽然减少了冲突的产生,但是还没有解决根本问题,移入-归约冲突和归约-归约冲突还是都存在的:
- 项目集中有两个项目(A -> α•bβ 和 A -> α• ), 而
A
的后继符号集中就包含b
的话,那么移入和归约又都可以同时进行了。 - 项目集中有两个项目(A -> α• 和 B -> α•), 如果
A
和B
的后继符号集有相交部分,那么就会产生归约-归约冲突。
因此SLR文法是要求:
二. LR(1)分析法
要解决移入-归约冲突和归约-归约冲突,光靠后继符号集是做不到的,最主要的是进行归约的时候,必须是确定的某个终结符才进行归约;
那么我们就在项目上再加一个终结符字段,这样只有当这个项目遇到这个终结符才能进行归约。
2.1 LR(1)项目
将一般形式为[A→α·β, a]
的项称为LR(1) 项,其中A→αβ
是一个产生式,a
是一个终结符(结束符号$
视为一个特殊的终结符),它表示在当前状态下,A后面必须紧跟的终结符,称为该项的展望符(lookahead
):
- LR(1) 中的1指的是项的第二个分量的长度。
- 在形如
[A→α·β, a]
且β ≠ ε
的项中,展望符a
没有任何作用。 - 但是一个形如
[A→α·, a]
的项在只有在下一个输入符号等于a时才可以按照A→α
进行归约。
2.2 LR(1)的等价项目
[ A→α·Bβ, a ]
等价于[ B→·γ, b ]
,其中:
- 如果
β
可以为空,则b=a
叫继承的后继符。 - 否则叫自生的后继符。
2.3 LR(1)项目集闭包
这里与LR(0) 区别就是 βa
的串首终结符集FIRST(βa)
都是等价项目,将它们都加入到闭包中。
2.4 LR(1)的GOTO函数
这个与LR(0)在算法逻辑上没有区别。
2.5 LR(1)的所有项目集
LR(1)获取所有项目集的算法与LR(0) 也几乎一样。
2.6 LR(1)分析表
与LR(0) 作比较你会发现,它们就在进行归约的时候有区别,只有遇到LR(1)项目中的展望符才能进行归约。
三. LR(1)代码实现
3.1 Action 类
package xinhao.lr.lr1;
import xinhao.Production;
import java.util.Objects;
/**
* @author by xinhao 2021/8/23
* LR1 对应的动作
*/
public class Action {
// 移入
public static final String TYPE_S = "s";
// 归约
public static final String TYPE_R = "r";
// 终结
public static final String TYPE_ACC = "acc";
private final String type;
// 如果action是移入的时候,stateItemSet代表移入状态,通过stateItemSet的state表示
private final ProductionItemSet stateItemSet;
// 当action是归约的时候,production就是归约的产生式
private final Production production;
private Action(String type, ProductionItemSet stateItemSet, Production production) {
this.type = type;
this.stateItemSet = stateItemSet;
this.production = production;
}
public static Action createAcc() {
return new Action(TYPE_ACC, null, null);
}
public static Action createS(ProductionItemSet stateItemSet) {
return new Action(TYPE_S, stateItemSet, null);
}
public static Action createR(Production production) {
return new Action(TYPE_R, null, production);
}
public boolean isAcc() {
return TYPE_ACC.equals(type);
}
public boolean isS() {
return TYPE_S.equals(type);
}
public boolean isR() {
return TYPE_R.equals(type);
}
public String getType() {
return type;
}
public ProductionItemSet getStateItemSet() {
return stateItemSet;
}
public Production getProduction() {
return production;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Action action = (Action) o;
return Objects.equals(type, action.type) &&
Objects.equals(stateItemSet, action.stateItemSet) &&
Objects.equals(production, action.production);
}
@Override
public int hashCode() {
return Objects.hash(type, stateItemSet, production);
}
@Override
public String toString() {
if (isAcc()) {
return "acc";
}
if (isR()) {
return "r->" + production;
}
if (isS()) {
return "s"+stateItemSet.getState();
}
return "";
}
}
3.2 ProductionItem 类
package xinhao.lr.lr1;
import xinhao.Production;
import xinhao.Symbol;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @author by xinhao 2021/8/24
* LR(1) 文法的项目
* [A→α·β, a]
* 其中`A→αβ` 是一个产生式,`a` 是一个终结符,表示该项的展望符
*/
public class ProductionItem implements Comparable {
public static final String ITEM_SYMBOL = "•";
/** 项目对应的产生式 */
private final Production production;
/** 表示当前项目对应的位置,即圆点的位置。*/
private final int pos;
/** 表示当前pos位置对应的字符。 */
private final Symbol posSymbol;
/**
* 当前项目的展望符
*/
private final Symbol expectSymbol;
/**
* 这个产生式项目的标签
* 也是这个产生式项目是否相等判断的依据
*/
private final String label;
private ProductionItem(Production production, int pos, Symbol posSymbol,
Symbol expectSymbol, String label) {
this.production = production;
this.pos = pos;
this.posSymbol = posSymbol;
this.expectSymbol = expectSymbol;
this.label = label;
}
public static ProductionItem createByProduction(Production production, Symbol expectSymbol) {
return create(production, expectSymbol, 0);
}
public static ProductionItem createNextByItem(ProductionItem item) {
return create(item.production, item.expectSymbol, item.pos + 1);
}
private static ProductionItem create(Production production, Symbol expectSymbol, int pos) {
List rightList = production.getRight();
StringBuilder labelBuilder = new StringBuilder();
Symbol posSymbol = null;
for (int index = 0; index < rightList.size(); index++) {
Symbol symbol = rightList.get(index);
if (index == pos) {
posSymbol = symbol;
labelBuilder.append(ITEM_SYMBOL);
}
labelBuilder.append(symbol.getLabel());
}
if (pos == rightList.size()) {
posSymbol = Symbol.END;
labelBuilder.append(ITEM_SYMBOL);
}
labelBuilder.append("-");
labelBuilder.append(expectSymbol.getLabel());
return new ProductionItem(production, pos, posSymbol, expectSymbol, labelBuilder.toString());
}
public List getFirstKeysSymbols() {
List keySymbols = new ArrayList<>();
List rightList = production.getRight();
for (int index = pos + 1; index < rightList.size(); index++) {
keySymbols.add(rightList.get(index));
}
keySymbols.add(expectSymbol);
return keySymbols;
}
public Production getProduction() {
return production;
}
public int getPos() {
return pos;
}
public Symbol getPosSymbol() {
return posSymbol;
}
public Symbol getExpectSymbol() {
return expectSymbol;
}
public String getLabel() {
return label;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductionItem that = (ProductionItem) o;
return label.equals(that.label);
}
@Override
public int hashCode() {
return Objects.hash(label);
}
@Override
public int compareTo(ProductionItem o) {
return label.compareTo(o.label);
}
@Override
public String toString() {
return label;
}
}
3.3 ProductionItemSet 方法
package xinhao.lr.lr1;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* @author by xinhao 2021/8/24
* 项目集( I ),称为项目集闭包(Closure of Item Sets),每个项目集闭包对应着自动机的一个状态。
*/
public class ProductionItemSet {
/** 记录所有创建过的项目集 */
public static final Map allItemSet = new HashMap<>();
public static int stateGenerate = 0;
/**
* 当前项目集对应的状态
*/
private final int state;
/**
* 项目的集合
*/
private final Set items;
/**
* 项目集的label,用来它来判断两个项目集是否相等
*/
private final String label;
public ProductionItemSet(int state, Set items, String label) {
this.state = state;
this.items = items;
this.label = label;
}
public static ProductionItemSet create(Set items) {
StringBuilder labelBuilder = new StringBuilder();
// 这里必须进行排序,保证不同Set 集合有相同顺序,这样才能比较两个项目集
items.stream().sorted().forEach(item -> labelBuilder.append(item.getLabel()).append(","));
if (labelBuilder.length() != 0) {
labelBuilder.deleteCharAt(labelBuilder.length() - 1);
}
String label = labelBuilder.toString();
/**
* 如果 label 相同,说明是同一个项目集,那么返回之前创建的;
* 保证相同 items 的是返回的是同一个 ProductionItemSet
*/
ProductionItemSet itemSet = allItemSet.get(label);
if (itemSet == null) {
itemSet = new ProductionItemSet(stateGenerate++, items, label);
allItemSet.put(label, itemSet);
}
return itemSet;
}
public int getState() {
return state;
}
public Set getItems() {
return items;
}
public String getLabel() {
return label;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductionItemSet itemSet = (ProductionItemSet) o;
return Objects.equals(label, itemSet.label);
}
@Override
public int hashCode() {
return Objects.hash(label);
}
@Override
public String toString() {
return state + ":"+label;
}
}
3.4 closure 方法
/**
* 求解等价项目的集合
* 对于任何 S -> [a•Bb, a] 的项目,因为圆点后面的是非终结符,
* 那么这个非终结符 B 的产生式 B -> b B ->c, 且文法符号串`ba` 的串首符号集就是[b]
* 对应的移入项目组 B -> [•b, b] B -> [•c, b] 都是相同项目,加入同一个项目集。
* closure 方法就是计算项目集
* @param items
* @param grammar
* @return
*/
private static Set closure(Set items, Grammar grammar) {
// 所有等价项目集合
Set resultItems = new HashSet<>(items);
Stack stack = new Stack<>();
// 先将传入的items添加到栈里面
stack.addAll(items);
// 栈不为空,说明还有项目没有遍历到
while (!stack.isEmpty()) {
ProductionItem item = stack.pop();
Symbol posSymbol = item.getPosSymbol();
/**
* 当前项目圆点位置是非终结符,那么这个非终结符的产生式组对应的移进项目和当前项目属于一个闭包
*/
if (!posSymbol.isTerminator()) {
// 非终结符对应的产生式组
List posSymbolProductions = grammar.getProductionsMap().get(posSymbol);
/**
* 获取非终结符后面的文符号串,再加上当前项目展望符得到的文法符号串
* 例如 当前项目[a•Bb, a], 那么keySymbols就是 [ba]
*/
List keySymbols = item.getFirstKeysSymbols();
// 获取串首终结符集
Set firstSymbols = grammar.getFirstsSetByKeys(keySymbols).getSet();
/**
* 要进行两次循环,得到所有的等价项目
*/
for (Production production : posSymbolProductions) {
for (Symbol symbol : firstSymbols) {
ProductionItem newItem = ProductionItem.createByProduction(production, symbol);
if (!resultItems.contains(newItem)) {
resultItems.add(newItem);
stack.push(newItem);
}
}
}
}
}
return resultItems;
}
3.5 Goto 方法
// 为 Goto 创建一个缓存 table,这样就只需要求解一次
public static final Map> GOTO_TABLE = new HashMap<>();
private static ProductionItemSet Goto(ProductionItemSet itemSet, Symbol symbol, Grammar grammar) {
// 如果缓存里有,那么直接返回,不用再求解了
if (GOTO_TABLE.containsKey(itemSet)) {
Map map = GOTO_TABLE.get(itemSet);
if (map.containsKey(symbol)) {
return map.get(symbol);
}
}
// 当前项目集 itemSet 遇到符号 symbol 的后继项目集合
Set nextItems = new HashSet<>();
for (ProductionItem item : itemSet.getItems()) {
/**
* 项目pos点的符号与 symbol 是否相同
* 即项目 [a•Bb, a] 匹配 符号B
* 后继项目就是 [aB•b, a]
*/
if (item.getPosSymbol().equals(symbol)) {
nextItems.add(ProductionItem.createNextByItem(item));
}
}
// 如果nextItems为空,说明当前项目集没有 符号symbol的后继项目
if (nextItems.isEmpty()) {
return null;
}
// 创建后继项目集
ProductionItemSet nextItemSet = ProductionItemSet.create(closure(nextItems, grammar));
// 存放到缓存中
Utils.addToDoubleMap(GOTO_TABLE, itemSet, symbol, nextItemSet);
return nextItemSet;
}
3.6 getAllItemSet 方法
/**
* 得到当前文法所有的项目集
* @param grammar
* @return
*/
private static List getAllItemSet(Grammar grammar) {
// 开始符号对应的产生式
Production startProduction = grammar.getProductionsMap().get(grammar.getStart()).get(0);
// 开始符号对应的项目
ProductionItem startItem = ProductionItem.createByProduction(startProduction, Symbol.END);
// 开始符号对应的项目集,也是第一个项目集
ProductionItemSet startItemSet = ProductionItemSet.create(closure(Utils.asSet(startItem), grammar));
// 文法中所有符号,包括终结符和非终结符,但是不包括结束符号 END
Set allSymbols = grammar.getAllSet();
// 当前文法所有的项目集
List allItemSets = new ArrayList<>();
allItemSets.add(startItemSet);
Stack stack = new Stack<>();
stack.push(startItemSet);
while (!stack.isEmpty()) {
ProductionItemSet itemSet = stack.pop();
// 遍历所有符号
for (Symbol eachSymbol : allSymbols) {
// 当前项目集遇到字符eachSymbol得到后继项目集
ProductionItemSet gotoItemSet = Goto(itemSet, eachSymbol, grammar);
/**
* 如果得到的后继项目集gotoItemSet 在allItemSetList中没有,
* 那么代表它是全新的,那么添加进行。
*/
if (gotoItemSet != null && !allItemSets.contains(gotoItemSet)) {
allItemSets.add(gotoItemSet);
// 新的项目集要添加到栈中,进行遍历
stack.push(gotoItemSet);
}
}
}
return allItemSets;
}
3.7 createLR1Table 方法
/**
* 得到文法对应的 LR1 分析表 actionMap 和 gotoMap
* @param grammar
* @param allItemSetList
* @param actionMap
* @param gotoMap
*/
private static void createLR1Table(Grammar grammar, List allItemSetList,
Map> actionMap,
Map> gotoMap) {
// 开始符号
Symbol start = grammar.getStart();
// 遍历文法所有的项目集
for (ProductionItemSet itemSet : allItemSetList) {
// 遍历项目集中的项目
for (ProductionItem item : itemSet.getItems()) {
// 得到当前项目 pos 位置的符号
Symbol posSymbol = item.getPosSymbol();
// 如果是非终结符,那么就要添加到 gotoMap 中
if (!posSymbol.isTerminator()) {
ProductionItemSet gotoItemSet = Goto(itemSet, posSymbol, grammar);
Utils.addToDoubleMapPrintConflict(gotoMap, itemSet, posSymbol, gotoItemSet,
"gotoMap 有冲突 old:%s new:%s");
} else {
// 是终结符但不是结束符号 END
if (!Symbol.END.equals(posSymbol)) {
// 获取后继项目集
Action action = Action.createS(Goto(itemSet, posSymbol, grammar));
// 想 action 中添加移入操作action
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, posSymbol, action,
"actionMap 有冲突 old:%s new:%s");
} else {
// 当前项目是 [aBb•, a] 格式,说明需要归约,这个就是归约需要的产生式
Production production = item.getProduction();
// 如果产生式左部和开始符号相同,说明是总结了,添加 acc 的action
if (start.equals(production.getLeft())) {
Action action = Action.createAcc();
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, posSymbol, action,
"actionMap 有冲突 old:%s new:%s");
} else {
/**
* 在LR1 算法中,只有遇到当前项目的展望符才进行归约
*/
Action action = Action.createR(production);
Utils.addToDoubleMapPrintConflict(actionMap, itemSet, item.getExpectSymbol(), action,
"actionMap 有冲突 old:%s new:%s");
}
}
}
}
}
}
3.8 match 方法
/**
* 判断文法能否匹配这个输入符号串inputSymbols
* @param inputSymbols
* @param startState
* @param actionMap
* @param gotoMap
* @return
*/
private static boolean match(List inputSymbols, ProductionItemSet startState,
Map> actionMap,
Map> gotoMap) {
// 匹配的产生式列表
List matchProductions = new ArrayList<>();
// 状态栈
Stack stateStack = new Stack<>();
// 放入开始状态,即开始项目集
stateStack.push(startState);
// 符号栈
Stack symbolStack = new Stack<>();
// 先放入结束符号$
symbolStack.push(Symbol.END);
// 表示读取输入符号的位置
int inputPos = 0;
while (true) {
// 当前状态栈栈顶状态
ProductionItemSet currentItemSet = stateStack.peek();
// 当然输入字符
Symbol inputSymbol = inputSymbols.get(inputPos);
// 获取当前栈顶状态遇到输入符号得到的 action
Action action = actionMap.get(currentItemSet).get(inputSymbol);
// 如果是 acc
if (action.isAcc()) {
// 当输入字符是最后一个字符,表示匹配成功。否则输入字符还没有匹配完成,匹配失败
if (inputPos == inputSymbols.size() -1) {
System.out.println("匹配成功");
System.out.println(matchProductions);
return true;
}
System.out.println("匹配失败");
System.out.println(matchProductions);
return false;
} else if (action.isS()) {
// 移入操作
stateStack.push(action.getStateItemSet());
symbolStack.push(inputSymbol);
// 下一个输入字符
inputPos ++;
} else if (action.isR()){
// 归约操作的产生式
Production production = action.getProduction();
int size = production.getRight().size();
// 记录一下状态栈弹出的状态
List popStateList = new ArrayList<>();
// 记录一下弹出的字符
List popSymbol = new ArrayList<>();
// 根据产生式右部的字符数量,从状态栈和字符栈中弹出对应数量的元素
for (int index = 0; index < size; index++) {
popStateList.add(stateStack.pop().getState());
popSymbol.add(symbolStack.pop());
}
System.out.println("popStateList: " + popStateList + " popSymbol:"+popSymbol+" production:"+production);
// 添加归约的产生式
matchProductions.add(production);
// 将产生式左部添加到字符栈
symbolStack.push(production.getLeft());
// 获取这个字符在 gotoMap 中对应的状态,添加到状态栈
ProductionItemSet itemSet = gotoMap.get(stateStack.peek()).get(production.getLeft());
stateStack.push(itemSet);
} else {
throw new RuntimeException("匹配错误");
}
}
}
3.9 例子
public static void main(String[] agrs) {
Symbol start = Alphabet.addSymbol("S'");
Grammar grammar = Grammar.create(start,
Production.create(start, "S"),
Production.create(Alphabet.getSymbol("S"), "BB"),
Production.create(Alphabet.getSymbol("B"), "aB"),
Production.create(Alphabet.getSymbol("B"), "b")
);
List allItemSetList = getAllItemSet(grammar);
System.out.println(allItemSetList);
Map> actionMap = new HashMap<>();
Map> gotoMap = new HashMap<>();
createLR1Table(grammar, allItemSetList, actionMap, gotoMap);
List inputSymbols = Utils.createSymbolsByString("bab");
inputSymbols.add(Symbol.END);
match(inputSymbols, allItemSetList.get(0), actionMap, gotoMap);
}
输出结果
[0:•BB-$,•S-$,•aB-a,•aB-b,•b-a,•b-b, 1:a•B-a,a•B-b,•aB-a,•aB-b,•b-a,•b-b, 2:B•B-$,•aB-$,•b-$, 3:b•-a,b•-b, 4:S•-$, 5:a•B-$,•aB-$,•b-$, 6:BB•-$, 7:b•-$, 8:aB•-$, 9:aB•-a,aB•-b]
popStateList: [3] popSymbol:[b] production:B->b
popStateList: [7] popSymbol:[b] production:B->b
popStateList: [8, 5] popSymbol:[B, a] production:B->aB
popStateList: [6, 2] popSymbol:[B, B] production:S->BB
匹配成功
[B->b, B->b, B->aB, S->BB]
文章中大部分图来自 https://piperliu.blog.csdn.net/article/details/107631357