写在前面
编译原理课程的实验,实现 pl0 的LL1文法判别。
git地址为:https://github.com/HiHaker/PL0-compiler
LL1文法的判定
需要编写一个程序,判定一个给定的文法是不是LL1文法的,重点在于实现文法的机内表示以及判定算法。
关于文法的机内表示,终结符号集和非终结符号集可以用Set<String>
来表示,为什么不使用 Character
类呢?,主要考虑到一些符号是形如E'
这样的,产生试集合可以使用一种数据结构,映射,一个左部对应一个右部列表,Map<String, String[]>
。
首先定义好一些成员变量:
// 存储文法的映射(产生式规则集合)
private Map<String, String[]> P;
// 文法的开始符号
private String start;
// 非终结符号集
private Set<String> nonEndChars;
// 终结符号集
private Set<String> endChars;
// FIRST集合
private Map<String, Set<String>> FIRST;
// FOLLOW集合
private Map<String, Set<String>> FOLLOW;
// SELECT集合
private Map<String, Set<String>> SELECT;
读取文法并且存储入映射:
// 存储文法
private void storeGrammar(String[] Vn, String[] Vt, String[] P, String start){
this.nonEndChars = new HashSet<>();
this.endChars = new HashSet<>();
this.start = start;
this.P = new HashMap<>();
try{
// 存储产生式
for (String value : P) {
// 分割左部、右部
String[] split1 = value.split("->");
// 分割右部的不同产生式
String[] split2 = split1[1].split("\\|");
this.P.put(split1[0], split2);
}
// 存储符号集
Collections.addAll(nonEndChars, Vn);
Collections.addAll(endChars, Vt);
} catch (Exception e){
System.out.println("输入文法有错误!");
e.printStackTrace();
}
}
计算FIRST集合
对于FIRST(x)
集合,按照以下的处理方式计算:
- 如果x是终结符,
FIRST(x)
为{x}
。 - 如果x是空串,
FIRST(x)
为{空串}
。 - 如果x是非终结符,
x->Y1Y2Y3...
,逐步地从Y1计算起,如果Y1的FIRST集包含空串,将FIRST(Y1)-空串
加入到FIRST(X)
,继续计算FIRST(Y2)
,… ,如果计算到某个符号Yn
不包含空串,就结束计算。如果计算到最后一个符号也包含空串,也就是说Y1Y2Y3...->空串
,则加入最后一个符号的FIRST集。
从以上处理方式我们看出,需要递归地进行处理,先写一个方法,递归的求出某符号的FIRST集:
// 递归地计算某符号的FIRST
private Set<String> FIRSTx(String x){
Set<String> first = new HashSet<>();
// 如果x是终结符,FIRST集就是它本身
// 这里,空串也包含在终结符号里
if (endChars.contains(x)){
first.add(x);
return first;
} else {
// 否则,就等于其右部各个符号的FIRST集相加
String[] rightItems = P.get(x);
// 遍历每一个右部
for