本篇文章内的源码: 这里
一. 概念
1.1 串首终结符集
定义: 给定一个文法符号串α
, α
的串首终结符集FIRST(α)
被定义为可以从α
推导出的所有串首终结符构成的集合。
串首终结符意思就是符号串的首字符是终结符,所以由
α
推导出的所有首字母是终结符的文法符号串,这些终结符首字母组成的集合就是FIRST(α)
。
定义已经了解清楚了,那么该如何求呢?
例如一个文法符号串 BCDe
, 其中 B
C
D
都是非终结符,e
是终结符。
仔细思考一下,你会发现:
- 要想求文法符号串
BCDe
推导出多少个串首终结符,要先求非终结符B
能够推导出多少个串首终结符,这个由B
产生式组来求解。- 如果
B
能够推导出空串ε
,即(B
=>+ε
),那么代表B
是可以消除的,文法符号串BCDe
相当于文法符号串CDe
,所以也要求非终结符C
能够推导出多少个串首终结符。
因此对于一个文法符号串 X1X2 … Xn
,求解串首终结符集 FIRST(X1X2 … Xn)
算法:
遍历这个文法符号串,然后判断当前文法符号串中的字符
Xi
(i
属于1
到n
)。
- 如果
Xi
是终结符,那么它的串首终结符集FIRST(Xi)
中就只有它自己,将FIRST(Xi)
加入FIRST(X1X2 … Xn)
中;因为FIRST(Xi)
也不可能包含空串 ε,不用再向下遍历,循环到此为止,得到最终的串首终结符集FIRST(X1X2 … Xn)
。- 如果
Xi
是非终结符,那么就将它的串首终结符集FIRST(Xi)
加入FIRST(X1X2 … Xn)
中。
- 如果
FIRST(Xi)
包含空串ε,那么再向下遍历一个字符;但是如果Xi
已经是最后一个字符了,那就说明整个文法符号串X1X2 … Xn
可以推导出空串ε,因此将空串ε加到FIRST(X1X2 … Xn)
中。- 如果
FIRST(Xi)
不包含空串ε,不用再向下遍历,循环到此为止,得到最终的串首终结符集FIRST(X1X2 … Xn)
。
但是这里有一个关键点,如何求非终结符的串首终结符集?
如求非终结符
A
的串首终结符集FIRST(A)
,其实就是看这个非终结符A
能够推导的所有首字符是终结符的文法符号串,那么就是看这个非终结符A
的产生式组。
因此对于一个非终结符 A
, 求解串首终结符集FIRST(A)
算法:
遍历非终结符
A
的所有产生式:
A→aβ
:a
是终结符,那么这个产生式推导的所有文法符号串,它们的首字符只能是终结符a
,因此将这个a
添加到FIRST(A)
中。A→ε
: 非终结符A
能够推导出空串ε,那么将这个空串 ε 添加到FIRST(A)
中。A→Bβ
: 产生式右部首字符是非终结符,那么就将FIRST(Bβ)
添加到FIRST(A)
中。
这里大家可能有个疑惑,怎么能将 FIRST(Bβ)
添加到 FIRST(A)
中,如果问文法符号串 Bβ
中包含非终结符 A
,就产生了循环调用的情况,该怎么办?
如果非终结符
A
的产生式组中,有包含A
的产生式,那么在计算FIRST(A)
时候:
- 先计算不包含
A
的其他产生式,得到一个FIRST(A)
。- 然后计算包含
A
的产生式,遇到A
的时候跳过,判断之前的FIRST(A)
包不包含空串 ε,来决定是否将产生式A
后面的文法符号串的串首终结符集加入FIRST(A)
中。
对于串首终结符集,我想大家疑惑的点就是,串首终结符集到底是针对文法符号串的,还是针对非终结符的,这个容易弄混。
其实我们应该知道,非终结符本身就属于一个特殊的文法符号串。
而求解文法符号串的串首终结符集,其实就是要知道文法符号串中每个字符的串首终结符集:
- 对于终结符,它的串首终结符集就是它自己。
- 对于非终结符,它的串首终结符集是要通过它对应的产生式组计算得来的。
- 再判断当前字符对应的串首终结符集包不包含空串,来决定要不要添加文法符号串中下一个字符的串首终结符集。
1.2 后继符号集
定义: 对于任一非终结符A
,它的后继符号集就是由文法G
推导出来的所有句型,可以出现在非终结符A
后边的终结符的集合,记为FOLLOW(A)
。
仔细想一下,什么样的终结符可以出现在非终结符A
后面,应该是在产生式中就位于A
后面的终结符。例如 S -> Aa
,那么终结符 a
肯定属于FOLLOW(A)
。
因此求非终结符A
的后继符号集算法:
遍历文法所有的产生式,判断产生式右部中是否包含非终结符
A
:
S -> αAβ
: 包含非终结符A
,其中α
和β
都属于文法符号串,那么就将文法符号串β
的串首终结符集FIRST(β)
中除了空串ε外的所有终结符添加到FOLLOW(A)
。如果FIRST(β)
存在空串ε,那么就需要将FOLLOW(S)
也添加到FOLLOW(A)
中。
注意:这里的β
是文法符号串,不要把它当成一个特定的非终结符。S -> αA
: 包含非终结符A
, 其中α
属于文法符号串,那么将FOLLOW(S)
添加到FOLLOW(A)
中。
如果非终结符A
是产生式结尾,那么说明这个产生式左部非终结符后面能出现的终结符,也都可以出现在非终结符A
后面。- 刚开始的时候,需要将结束符号
$
添加到文法开始符号S
的后继符号集FOLLOW(S)
中。- 后继符号集中是不会包含空串ε的。
1.3 可选集
我们可以求出LL(1)
文法中每个产生式可选集:
-
A→aβ
:a
是终结符,β
是文法符号串,那么这个产生式的可选集SELECT(A→aβ)
就是这个终结符,即{a}
。 -
A→ε
: 空产生式对应的可选集SELECT(A→ε)
就是A
的后继符号集,即FOLLOW(A)
。 -
A→Bβ
:B
是非终结符,β
是文法符号串,那么这个产生式的可选集SELECT(A→Bβ)
就是文法符号串Bβ
的串首终结符集,即FIRST(Bβ)
。注意,如果
FIRST(Bβ)
包含空串ε,即文法符号串Bβ
能推导出空串ε,那么还要将A
的后继符号集添加到产生式对应的可选集中。
1.4 预测分析表
根据产生式可选集,我们可以构建一个预测分析表,表中的每一行都是一个非终结符,表中的每一列都是一个终结符,包括结束符号 $
,而表中的值就是产生式。
这样进行语法推导的时候,非终结符遇到当前输入字符,就可以从预测分析表中获取对应的产生式了。
二. 代码实现
2.1 相关类
2.1.1 Symbol
类
/**
* @author by xinhao 2021/8/13
* 表示文法中字母表中一个符号,包括终结符和非终结符
*/
public class Symbol implements Comparable {
public static final String EPSILON = "ε";
// 文法结束符号
public static final Symbol END = new Symbol("$", true);
// 表示符号的值, 这里用 String 表示,而不是 char,
// 是因为有些符号我们不好用一个 char 表示。 比如 A 对应的 A'
private final String label;
private final boolean isTerminator;
...
}
表示字母表中一个符号, label
表示符号值, isTerminator
表示终结符或非终结符。
2.1.2 Alphabet
类
/**
* @author by xinhao 2021/8/13
* 字母表, 这个类不用实例化。用来记录字母表中所有的符号
*/
public abstract class Alphabet {
/**
* 字母表中所有的符号
*/
public static final Map ALL_SYMBOL = new HashMap<>();
public static Symbol addSymbol(String label, boolean isTerminator) {
Symbol symbol = new Symbol(label, isTerminator);
ALL_SYMBOL.put(label, symbol);
return symbol;
}
public static Symbol getSymbol(String label) {
return ALL_SYMBOL.get(label);
}
}
字母表,用来记录字母表中所有的符号。
2.1.3 Production
类
public class Production {
public static final List EMPTY = new ArrayList<>(0);
/** 产生式左边,而且必须是非终结符 */
private final Symbol left;
/** 这里的 List 希望是不可变的,你们可以自己引入 ImmutableList */
private final List right;
...
}
表示一个产生式 A -> αβ
, left
产生式左边,而且必须是非终结符,right
产生式右部符号集合。
2.1.4 Grammar
类
public class Grammar {
// 开始符号
private final Symbol start;
/**
* 这里为什么用 map 类型?
* S -> aA | bB
* A -> aaaa
* A -> abab | ε
* B -> bbb
* B -> bababa
* B -> ε
* 所以对于同一个左部 Symbol (A) 其实是有多个产生式的,就是产生式租
* 使用 LinkedHashMap ,因为产生式是要有记录顺序的
*/
private final LinkedHashMap> productions;
...
}
表示一个文法,包含文法开始符号和产生式组,终结符号集合非终结符号集没有包含在这个里面。
2.2 求串首终结符集
2.2.1 FirstSet
类
package xinhao.ll;
import xinhao.Symbol;
import java.util.HashSet;
import java.util.Set;
/**
* @author by xinhao 2021/8/14
* 单个符号的串首终结符集
*/
public class FirstSet {
/**
* 这个串首终结符集对应的单个符号,一般都是非终结符。
* 终结符的串首终结符集就是它自己。
*/
private final Symbol key;
/**
* 这个串首终结符集是否包含 空串
* 空串ε 不加入到集合中,用hasEpsilon表示这个串首终结符集中有没有空串。
*/
private boolean hasEpsilon;
/**
* 这个字符 key 对应的串首终结符集合
* 注意这里不包含 ε,ε 我们使用hasEpsilon, 表示是否包含空串
*/
private Set set = new HashSet<>();
public FirstSet(Symbol key) {
this.key = key;
}
public void addToSet(Symbol symbol) {
set.add(symbol);
}
public void addAllToSet(Set symbols) {
set.addAll(symbols);
}
public Symbol getKey() {
return key;
}
public boolean isHasEpsilon() {
return hasEpsilon;
}
public void setHasEpsilon(boolean hasEpsilon) {
this.hasEpsilon = hasEpsilon;
}
public Set getSet() {
return set;
}
public void setSet(Set set) {
this.set = set;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("FIRST(").append(key).append(") = {");
set.forEach(symbol -> sb.append(symbol).append(" "));
if (hasEpsilon) sb.append("ε ");
sb.append("}");
return sb.toString();
}
}
这个类表示单个字符对应的串首终结符集:
-
key
: 这个串首终结符集对应的单个符号。 -
hasEpsilon
: 这个串首终结符集是否包含 空串。 -
set
: 这个字符 key 对应的串首终结符集合,注意里面不包含空串。
2.2.2 FirstsSet
类
/**
* @author by xinhao 2021/8/14
* 文法符号串对应串首终结符集
*/
public class FirstsSet {
/**
* 当前的文法符号串
*/
private final List keys;
/**
* 这个串首终结符集是否包含 空串
*/
private boolean hasEpsilon;
/**
* 字符 key 对应的串首的终结符集合
* 注意这里不包含 ε,ε 我们使用hasEpsilon, 表示是否包含空串
*/
private Set set = new HashSet<>();
......
}
这个和 FirstSet
几乎一模一样,只不过它代表一个文法符号串对应的串首终结符集。
2.2.3 获取所有非终结符的串首终结符集
public class Grammar {
....
/**
* 该文法所有非终结符对应的串首终结符集
*/
private Map firstSetMap;
/**
* 获取所有非终结符对应的串首终结符集
* @return
*/
public Map getFirstSetMap() {
// 如果有值直接返回,只求值一次
if (firstSetMap != null) {
return firstSetMap;
}
firstSetMap = new HashMap<>();
// 遍历文法中所有非终结符,求解对应的串首终结符集
productions.keySet().forEach(key -> getOrCreateFirstSetByKey(key));
return firstSetMap;
}
/**
* 通过递归的方式,来求非终结符的串首终结符集
*
* @param key
* @return
*/
private FirstSet getOrCreateFirstSetByKey(Symbol key) {
// 说明之前已经求过了,直接返回
if (firstSetMap.containsKey(key)) {
return firstSetMap.get(key);
}
/**
* 右部包含 key 非终结符的产生式列表。例如 A -> aAb
* 因为产生式右部包含key,那么就不能递归调用 getOrCreateFirstSetByKey(key) 方法,这样会形成死循环。
* 先计算不包含 `A` 的其他产生式,得到一个 firstSet,然后再求包含`A` containsKeyProductionList
*
*/
List containsKeyProductionList = new ArrayList<>();
// 该符号key 对应的串首终结符集
FirstSet firstSet = new FirstSet(key);
for (Production production : productions.get(key)) {
// 该产生式是空串ε
if (production.isEpsilon()) {
firstSet.setHasEpsilon(true);
} else {
List rightSymbols = production.getRight();
/**
* 如果这个产生式右部包含这个非终结符 key,那么需要特殊处理,
* 加入到containsKeyProductionList中。
*/
if (rightSymbols.contains(key)) {
containsKeyProductionList.add(production);
continue;
}
Iterator rightIterator = rightSymbols.iterator();
/**
* isAllHasEpsilon 变量表示这个产生式都是非终结符,且这些非终结符的 First集都包含空串ε
* 那么此 First(key) 也应该包含空串ε
* 这里 isAllHasEpsilon 初始值是 true,因为 production.getRight() 不可能是空集合
*/
boolean isAllHasEpsilon = true;
while (rightIterator.hasNext()) {
Symbol currentSymbol = rightIterator.next();
// 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
if (currentSymbol.isTerminator()) {
firstSet.addToSet(currentSymbol);
isAllHasEpsilon = false;
// 跳出当前 while,不用再遍历这个产生式后面的字符了
break;
}
/**
* 如果是非终结符,递归调用 getOrCreateFirstSetByKey 获取这个非终结符对应的串首终结符集。
* 但是不允许有间接左递归,即 A -> B; B -> Aa
* 这样会出现死循环,就无法求解了。
*/
FirstSet currentFirstSet = getOrCreateFirstSetByKey(currentSymbol);
firstSet.addAllToSet(currentFirstSet.getSet());
/**
* 如果当然 currentSymbol 对应的FirstSet (currentFirstSet) 包含空串,那么就需要考察下一个字符
* 例如 对于 S -> ABC 产生式, currentSymbol = A 且对应currentFirstSet包含空串,那么就需要将 B 的First集加入其中
* 如果当前字符 currentSymbol 的串首终结符集不包含空串,那么就不用再遍历这个产生式后面的字符了。
*/
if (!currentFirstSet.isHasEpsilon()) {
isAllHasEpsilon = false;
break;
}
}
if (isAllHasEpsilon) {
firstSet.setHasEpsilon(true);
}
}
}
/**
* 为什么分为两次遍历,因为 First(key) 是否包括空串ε,是由不包含key的其他产生式决定的。
* 所以在遍历包含 key 的产生式的时候,就可以通过 First(key)是否包含空串ε,
* 来决定是否添加产生式中 key 后面的字符
*/
for (Production containsKeyProduction : containsKeyProductionList) {
Iterator rightIterator = containsKeyProduction.getRight().iterator();
while (rightIterator.hasNext()) {
Symbol currentSymbol = rightIterator.next();
// 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
if (currentSymbol.isTerminator()) {
firstSet.addToSet(currentSymbol);
break;
}
if (currentSymbol.equals(key)) {
// 如果不包含空串ε,就直接跳出循环,不用遍历产生式下一个字符了
if (!firstSet.isHasEpsilon()) {
break;
}
} else {
FirstSet currentFirstSet = getOrCreateFirstSetByKey(currentSymbol);
firstSet.addAllToSet(currentFirstSet.getSet());
if (!currentFirstSet.isHasEpsilon()) {
break;
}
}
}
}
firstSetMap.put(key, firstSet);
return firstSet;
}
}
其实求解过程很容易理解,就是利用递归的方式,获取非终结符的串首终结符集。
例如 : A -> a | Bb; B -> b | c
求解非终结符A
的FIRST(A)
, 遍历A
的产生式组,到FIRST(a)
是{a}
,FIRST(Bb)
就是递归调用FIRST(B)
。
所以特别注意,这种求解算法要求文法一定不能存在直接左递归,否则就会出现循环调用的情况,如 A -> B; B -> Aa
。
2.2.4 文法符号串的串首终结符集
public abstract class GrammarUtils {
.......
/**
* 获取文法符号串对应的串首终结符集
* 要传递所有非终结符对应的串首终结符集 firstSetMap
* @param keySymbols
* @param firstSetMap
* @return
*/
public static FirstsSet getFirstsSetByKeys(List keySymbols, Map firstSetMap) {
// 文法符号串的串首终结符集
FirstsSet firstsSet = new FirstsSet(keySymbols);
Iterator rightIterator = keySymbols.iterator();
/**
* isAllHasEpsilon 变量表示这个产生式都是非终结符,且这些非终结符的 First集都包含空串ε
* 那么此 First(key) 也应该包含空串ε
* 这里 isAllHasEpsilon 初始值是 true,因为 keySymbols 不可能是空集合
*/
boolean isAllHasEpsilon = true;
while (rightIterator.hasNext()) {
Symbol currentSymbol = rightIterator.next();
// 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
if (currentSymbol.isTerminator()) {
firstsSet.addToSet(currentSymbol);
isAllHasEpsilon = false;
break;
}
// 如果是非终结符,那么需要判断
FirstSet currentFirstSet = firstSetMap.get(currentSymbol);
firstsSet.addAllToSet(currentFirstSet.getSet());
/**
* 如果当然 currentSymbol 对应的FirstSet (currentFirstSet) 包含空串,那么就需要考察下一个字符
* 例如 对于 S -> ABC 产生式, currentSymbol = A 且对应currentFirstSet包含空串,那么就需要将 B 的First集加入其中
*/
if (!currentFirstSet.isHasEpsilon()) {
isAllHasEpsilon = false;
break;
}
}
if (isAllHasEpsilon) {
firstsSet.setHasEpsilon(true);
}
return firstsSet;
}
}
这个就比较简单了,因为先获取了所有非终结符对应的串首终结符集 firstSetMap
, 遍历文法符号串所有的符号,计算它的串首终结符集 。
2.3 求后继符号集
2.3.1 FollowSet
类
package xinhao.ll;
import xinhao.Symbol;
import java.util.HashSet;
import java.util.Set;
/**
* @author by xinhao 2021/8/15
* Follow集
* 对于任一非终结符A,它的后继符号集就是由文法G推导出来的所有句型,可以出现在非终结符A后边的终结符的集合,记为FOLLOW(A)。
*
* 将$放入FOLLOW(S)中,其中S是开始符号,$是输入右端的结束标记;
* 如果存在一个产生式A→αBβ,那么FIRST(β)中除ε之外的所有符号都在FOLLOW(B)中;
* 如果存在一个产生式A→αB,或存在产生式A→αBβ且FIRST(β) 包含ε,那么FOLLOW(A)中的所有符号都在FOLLOW(B)中。
*/
public class FollowSet {
/**
* 当前 Follow集对应的字母, 必须是非终结符;
* 终结符不可能有Follow集
*/
private final Symbol key;
/**
* 非终结符 key 对应的Follow集
*/
private Set set = new HashSet<>();
public FollowSet(Symbol key) {
this.key = key;
}
public void addToSet(Symbol symbol) {
set.add(symbol);
}
public void addAllToSet(Set symbols) {
set.addAll(symbols);
}
public Symbol getKey() {
return key;
}
public Set getSet() {
return set;
}
public void setSet(Set set) {
this.set = set;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Follow(");
sb.append(key).append(") = {");
set.forEach(symbol -> sb.append(symbol).append(" "));
sb.append('}');
return sb.toString();
}
}
2.3.2 获取所有非终结符的后继符号集
public class Grammar {
....
/**
* 该文法对应的 Follow 集
*/
private Map followSetMap;
/**
* 取所有非终结符对应的后继符号集
* @return
*/
public Map getFollowSetMap() {
if (followSetMap != null) {
return followSetMap;
}
followSetMap = new HashMap<>();
// 遍历文法中所有非终结符,求解对应的后继符号集
productions.keySet().forEach(key -> getFollowSetByKey(key));
return followSetMap;
}
}
/**
* 通过递归的方式,来求非终结符的后继符号集
* @param key
* @return
*/
private FollowSet getOrCreateFollowSetByKey(Symbol key) {
// 说明之前已经求过了,直接返回
if (followSetMap.containsKey(key)) {
return followSetMap.get(key);
}
// 该符号key 对应的后继符号集
FollowSet followSet = new FollowSet(key);
// 如果是文法开始符号, 要将语法终结符号放入它的 Follow 集中
if (start.equals(key)) {
followSet.addToSet(Symbol.END);
}
for (Map.Entry> entry : productions.entrySet()) {
for (Production production : entry.getValue()) {
/**
* 如果此产生式是空串,产生式右部不会包含字符key,遍历下一个产生式
* 空串是加入 First 集中,间接对 Follow集产生影响
*/
if (production.isEpsilon()) {
continue;
}
List rightSymbols = production.getRight();
/**
* 判断我们要得到Follow集的 非终结符 key 在不在当前这个产生式中。
* 因为要得到非终结符 key 的Follow集,就要看这个产生式中此非终结符 key 后面字符的情况
*/
int pos = rightSymbols.indexOf(key);
// pos == -1, 说明没有找到,那么就从下一个产生式Production寻找
if (pos == -1) {
continue;
}
/**
* 如果pos == rightSymbols.size() - 1,
* 说明非终结符 key就是当前产生式最后一个字符,
* 那么就需要将该产生式左部的Follow集加入到此key 的Follow集中。
* 即 A -> aB, 因为B 是产生式最后一个非终结符,那么就要将 Follow(A) 加入到 Follow(B) 中
*/
if (pos == rightSymbols.size() - 1) {
// A -> aA 这种情况下,不需要做其他操作
if (key.equals(production.getLeft())) {
System.out.println("key:"+key+" production.getLeft():"+production.getLeft());
} else {
// 将产生式左部的后继符号集添加到当前符号 key 的后继符号集中
// 对于 S -> aA, 要将 Follow(S) 添加到 Follow(A) 中
followSet.addAllToSet(getOrCreateFollowSetByKey(production.getLeft()).getSet());
}
continue;
}
// 当前符号 key 之后的文法符合串
List remainSymbols = rightSymbols.subList(pos + 1, rightSymbols.size());
// 获取文法符合串对应的串首终结符集
FirstsSet firstsSet = GrammarUtils.getFirstsSetByKeys(remainSymbols, firstSetMap);
followSet.addAllToSet(firstsSet.getSet());
if (firstsSet.isHasEpsilon()) {
if (key.equals(production.getLeft())) {
System.out.println("key:"+key+" production.getLeft():"+production.getLeft());
} else {
// 将产生式左部的后继符号集添加到当前符号 key 的后继符号集中
followSet.addAllToSet(getOrCreateFollowSetByKey(production.getLeft()).getSet());
}
}
}
}
followSetMap.put(key, followSet);
return followSet;
}
与串首终结符集求解类似,都是通过递归的方式,获取所有非终结符的后继符号集。
2.4 求产生式的可选集
2.4.1 SelectSet
类
/**
* @author by xinhao 2021/8/15
* 可选集 Select集
* A→aβ : a 是终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→aβ) 就是这个终结符,即{a}。
* A→ε : 空产生式对应的可选集SELECT(A→ε) 就是A的后继符号集,即 FOLLOW(A)。
* A→Bβ : B 是非终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→Bβ) 就是文法符号串Bβ的串首终结符集,即FIRST(Bβ)。
* 注意,如果`FIRST(Bβ)`包含空串ε,即文法符号串`Bβ`能推导出空串ε,那么还要将`A`的后继符号集添加到产生式对应的可选集中。
*/
public class SelectSet {
// 这个可选集对应产生式
private final Production production;
// 可选的符号集合
private final Set set;
public SelectSet(Production production, Set set) {
this.production = production;
this.set = set;
}
public Production getProduction() {
return production;
}
public Set getSet() {
return set;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("SelectSet{");
sb.append("production=").append(production);
sb.append(", set=").append(set);
sb.append('}');
return sb.toString();
}
}
表示某个产生式对应的可选集。
2.4.2 求文法所有产生式对应的可选集
public abstract class GrammarUtils {
......
/**
* 求这个文法所有产生式对应的可选集List
* @param grammar
* @return
*/
public static List getAllSelectSetByGrammar(Grammar grammar) {
// 可选集列表
List selectSetList = new ArrayList<>();
// 当前文法的所有非终结符的后继符号集
Map followSetMap = grammar.getFollowSetMap();
for (Map.Entry> entry : grammar.getProductions().entrySet()) {
// 遍历文法中的每个产生式
for (Production production : entry.getValue()) {
// 如果产生式是空产生式 A -> ε,那么 FOLLOW(A) 就是这个产生式的可选集
if (production.isEpsilon()) {
FollowSet followSet = followSetMap.get(production.getLeft());
selectSetList.add(new SelectSet(production, followSet.getSet()));
} else {
List rightSymbols = production.getRight();
// 产生式右部首字符是终结符,那么这个产生式可选集就是这个终结符
if (rightSymbols.get(0).isTerminator()) {
Set set = new HashSet<>();
set.add(rightSymbols.get(0));
selectSetList.add(new SelectSet(production, set));
} else {
//
Set set = new HashSet<>();
FirstsSet firstsSet = getFirstsSetByKeys(rightSymbols, grammar.getFirstSetMap());
set.addAll(firstsSet.getSet());
// 产生式右部的文法符号串能推导出空串ε,那么还要将产生式左部的FOLLOW集添加到这个产生式的可选集中
if (firstsSet.isHasEpsilon()) {
FollowSet followSet = followSetMap.get(production.getLeft());
set.addAll(followSet.getSet());
}
selectSetList.add(new SelectSet(production, set));
}
}
}
}
return selectSetList;
}
......
}
2.5 获取预测分析表
public abstract class GrammarUtils {
......
/**
* 将可选集转换成预测分析表
* @param selectSetList
* @return
*/
public static Map> listToSelectTable(List selectSetList) {
/**
* 使用Map> 表示一个二维表结构
* 每一行都是一个非终结符,表中的每一列都是一个终结符,值就是产生式
*/
Map> table = new HashMap<>();
for (SelectSet selectSet : selectSetList) {
Production production = selectSet.getProduction();
/**
* 分析表中每一行都是非终结符,也是产生式的左部
*/
Map map = table.get(production.getLeft());
if (map == null) {
map = new HashMap<>();
table.put(production.getLeft(), map);
}
/**
* 说明当前这个产生式 production, 是非终结符 production.getLeft(), 遇到产生式的可选集字符就能选择。
*/
for (Symbol symbol : selectSet.getSet()) {
map.put(symbol, production);
}
}
return table;
}
......
}
2.5 进行预测分析法
public abstract class GrammarUtils {
......
public static List match(Symbol start, Map> table, List matchSymbols) {
Stack stack = new Stack<>();
stack.push(start);
int pos = 0;
List productions = new ArrayList<>();
while (!stack.isEmpty()) {
// 获取当前栈顶字符
Symbol currentSymbol = stack.pop();
// 获取待匹配输入字符串当前位置 pos 对应的待匹配字符
Symbol matchSymbol = matchSymbols.get(pos);
// 如果匹配成功说明,就让pos自增,匹配下一个字符
if (currentSymbol.equals(matchSymbol)) {
pos++;
} else if (currentSymbol.isTerminator()){
// 当前符号 currentSymbol 是终结符,还不能匹配成功,那么说明语法匹配错误
throw new RuntimeException("当前符号:" + currentSymbol + " 待匹配符号:" + matchSymbol);
} else {
Map map = table.get(currentSymbol);
if (map == null) {
throw new RuntimeException("当前符号:" + currentSymbol+" 对应的分析表");
}
Production production = map.get(matchSymbol);
if (production == null) {
throw new RuntimeException("当前符号:" + currentSymbol+" 对应的分析表");
}
// 匹配成功的产生式
productions.add(production);
// 将产生式倒序存入栈中
List right = production.getRight();
for (int index = right.size() - 1; index >= 0; index--) {
stack.push(right.get(index));
}
}
}
return productions;
}
......
}
2.6 例子
public static void main(String[] args) {
Symbol start = Alphabet.getSymbol('E');
List productions = new ArrayList<>();
productions.addAll(Production.createList(start, "E+T", "T"));
productions.addAll(Production.createList(Alphabet.getSymbol('T'), "T*F", "F"));
productions.addAll(Production.createList(Alphabet.getSymbol('F'), "(E)", "d"));
Grammar grammar = Grammar.create(start, productions);
Grammar deepGrammar = GrammarUtils.eliminateDeepLeftRecursion(grammar);
Map firstSetMap = deepGrammar.getFirstSetMap();
Map followSetMap = deepGrammar.getFollowSetMap();
List selectSetList = GrammarUtils.getAllSelectSetByGrammar(deepGrammar);
Map> table = GrammarUtils.listToSelectTable(selectSetList);
List matchSymbols = new ArrayList<>();
matchSymbols.add(Alphabet.getSymbol('d'));
matchSymbols.add(Alphabet.getSymbol('+'));
matchSymbols.add(Alphabet.getSymbol('d'));
matchSymbols.add(Alphabet.getSymbol('*'));
matchSymbols.add(Alphabet.getSymbol('d'));
matchSymbols.add(Symbol.END);
List matchProductionList = GrammarUtils.match(deepGrammar.getStart(), table, matchSymbols);
System.out.println("原始的文法:");
System.out.println(grammar);
System.out.println();
System.out.println("去除左递归之后的文法:");
System.out.println(deepGrammar);
System.out.println();
System.out.println("文法first集和follow集:");
deepGrammar.getProductions().keySet().forEach(symbol ->
System.out.println(firstSetMap.get(symbol) +"\t\t" + followSetMap.get(symbol)));
System.out.println();
System.out.println("文法产生式的可选集:");
selectSetList.forEach(selectSet -> System.out.println(selectSet));
System.out.println();
System.out.println("匹配 d+d*d 的产生组:");
System.out.println(matchProductionList);
}
输出结果:
原始的文法:
开始符号:E
产生式:
E->E+T|T
T->T*F|F
F->(E)|d
去除左递归之后的文法:
开始符号:E
产生式:
E->TE'
E'->+TE'|ε
T->FT'
T'->*FT'|ε
F->(E)|d
文法first集和follow集:
FIRST(E) = {d ( } Follow(E) = {$ ) }
FIRST(E') = {+ ε } Follow(E') = {$ ) }
FIRST(T) = {d ( } Follow(T) = {$ ) + }
FIRST(T') = {* ε } Follow(T') = {$ ) + }
FIRST(F) = {d ( } Follow(F) = {$ ) * + }
文法产生式的可选集:
SelectSet{production=E->TE', set=[d, (]}
SelectSet{production=E'->+TE', set=[+]}
SelectSet{production=E'->ε, set=[$, )]}
SelectSet{production=T->FT', set=[d, (]}
SelectSet{production=T'->*FT', set=[*]}
SelectSet{production=T'->ε, set=[$, ), +]}
SelectSet{production=F->(E), set=[(]}
SelectSet{production=F->d, set=[d]}
匹配 d+d*d 的产生组:
[E->TE', T->FT', F->d, T'->ε, E'->+TE', T->FT', F->d, T'->*FT', F->d, T'->ε, E'->ε]