一、实验目的
设计、编制并调试一个语法分析程序,加深对语法分析原理的理解。
二、使用仪器、器材
计算机一台
操作系统:Windows10
编程软件:Intellij IDEA
三、实验内容及原理
设有文法G[E]:
E→E+T|T
T→T*F|F
F→(E)|i
消除左递归后的文法为:
E→TE’
E’→+TE’|ε
T→FT’
T’→*FT’|ε
F→(E)|i
建立LL(1)分析表如下:
I | + | * | ( | ) | # | |
---|---|---|---|---|---|---|
E | E→TE’ | E→TE’ | ||||
E’ | E’→+TE’ | E’→ε | E’→ε | |||
T | T→FT’ | T→FT’ | ||||
T’ | T’→ε | T’→*FT’ | T’→ε | T’→ε | ||
F | F→i | F→(E) |
请分析输入串i+i是否为该文法的句子,输出其分析过程
四、实验过程原始记录
这个类用来表示哈希表中的键,包含一个终结符和一个非终结符(也可以是#),这里要注意重写 hashCode() 和 equals() 方法。
/**
* @Author DELL
* @create 2020/11/26 14:26
*/
public class Pair {
/**
* 终结符
*/
private String vn;
/**
* 非终结符
*/
private String vt;
public Pair(String vn, String vt) {
this.vn = vn;
this.vt = vt;
}
public String getVt() {
return vt;
}
public void setVt(String vt) {
this.vt = vt;
}
public String getVn() {
return vn;
}
public void setVn(String vn) {
this.vn = vn;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair pair = (Pair) o;
return Objects.equals(vt, pair.vt) &&
Objects.equals(vn, pair.vn);
}
@Override
public int hashCode() {
return Objects.hash(vt, vn);
}
@Override
public String toString() {
return "Pair{" +
"vn='" + vn + '\'' +
", vt='" + vt + '\'' +
'}';
}
}
识别句子的一个类,核心方法为 public boolean check(String str) ;
。
/**
* @Author DELL
* @create 2020/11/26 14:26
*/
public class GrammarProcessor {
/**
* 分析表
*/
public static Map<Pair,String> map = new HashMap<>();
/**
* vt集合,即终结符集合
*/
public static Set<String> vtSet = new HashSet<>();
static {
map.put(new Pair("E","I"),"E→TE'");
map.put(new Pair("E","("),"E→TE'");
map.put(new Pair("E'","+"),"E'→+TE'");
map.put(new Pair("E'",")"),"E'→ε");
map.put(new Pair("E'","#"),"E'→ε");
map.put(new Pair("T","I"),"T→FT'");
map.put(new Pair("T","("),"T→FT'");
map.put(new Pair("T'","+"),"T'→ε");
map.put(new Pair("T'","*"),"T'→*FT'");
map.put(new Pair("T'",")"),"T'→ε");
map.put(new Pair("T'","#"),"T'→ε");
map.put(new Pair("F","I"),"F→i");
map.put(new Pair("F","("),"F→(E)");
vtSet.add("I");
vtSet.add("+");
vtSet.add("(");
vtSet.add(")");
vtSet.add("*");
vtSet.add("#");
}
public GrammarProcessor() {
}
/**
* 根据一个非终结符与终结符获取产生式
* @param vn
* @param vt
* @return
*/
public static String get(String vn,String vt) {
vt = vt.toUpperCase();
return map.get(new Pair(vn,vt));
}
/**
* 检查某个串是否符合 i+i 的语法
* @param str
* @return
*/
public boolean check(String str) {
printTitle(str);
// 初始时 # 与 文法开始符压栈
Deque<String> stack = new LinkedList<>();
stack.push("#");
stack.push("E");
// 输入串的下一个位置
int i = 0;
// 输入串当前指向的字符
String cur = String.valueOf(str.charAt(i++));
// 栈顶符号
String top = null;
do{
// 打印
print(stack,str,i,cur);
top = stack.pop();
// 栈顶符号 ∈ Vt,判断栈顶与输入串当前字符是否相等
if(vtSet.contains(top.toUpperCase())) {
// x == a != "#",则字符串指针移动
if(top.equalsIgnoreCase(cur)) {
if(!"#".equals(cur)) {
cur = "#";
System.out.println();
if(i < str.length()) {
cur = String.valueOf(str.charAt(i));
i++;
}
}
}
else {
return false;
}
} else {
// 根据栈顶查表
String t = get(top,cur);
// 找不到,识别失败
if(t == null) {
return false;
}
// 输出产生式
System.out.println(padWhitespaceRight(t,8));
// 获取产生式的右部
String sentence = t.split("→")[1];
// 将产生式的右部逆序压栈
int end = sentence.length();
for(int start = sentence.length()-1;start >= 0;start--) {
if(sentence.charAt(start) == '\'') {
continue;
} else {
String substring = sentence.substring(start, end);
// ε不压栈
if(!"ε".equals(substring)) {
stack.push(substring);
end = start;
}
}
}
}
} while(!"#".equals(top));
System.out.println();
return true;
}
private void printTitle(String str) {
int len = str.length() + 3;
String[] titles = new String[] {"符号栈","当前输入符号","输入串","所用产生式"};
for(String s:titles) {
System.out.print(padWhitespaceRight(s,len));
}
System.out.println();
}
private void print(Deque<String> stack, String str, int i, String cur) {
int len = str.length() + 5;
StringBuilder sb = new StringBuilder();
// 拼接栈内的符号
Iterator<String> iterator = stack.descendingIterator();
while(iterator.hasNext()) {
sb.append(iterator.next());
}
// 输出栈
System.out.print(padWhitespaceRight(sb.toString(),len));
// 输出当前输入符号
System.out.print(padWhitespaceRight(cur,len));
// 输出输入串
if(i < str.length()) {
System.out.print(padWhitespaceRight(str.substring(i-1),len));
} else {
System.out.print(padWhitespaceRight("#",len));
}
}
public static String padWhitespaceLeft(String s, int len) {
return String.format("%1$" + len + "s", s);
}
public static String padWhitespaceRight(String s, int len) {
return String.format("%1$-" + len + "s", s);
}
public static void main(String[] args) throws IOException {
List<String> tests = new ArrayList<>();
tests.add("i+i*i#");
tests.add("i+i#");
tests.add("i*i#");
tests.add("(i+i)*i#");
tests.add("(i*i)+(i)#");
for(String test:tests) {
System.out.println(new GrammarProcessor().check(test));
}
List<String> err = new ArrayList<>();
err.add("i&i#");
err.add("i/(i*i)#");
for(String test:err) {
System.out.println(new GrammarProcessor().check(test));
}
}
}
四、实验结果与分析
1.输入该文法的5个句子进行测试:
tests.add("i+i*i#");
tests.add("i+i#");
tests.add("i*i#");
tests.add("(i+i)*i#");
tests.add("(i*i)+(i)#");
测试结果分别如下:
2.使用2个不是该文法的句子进行测试:
err.add("i&i#");
err.add("i/(i*i)#");
测试结果如下:
实验分析及心得
如实验结果所示,面对该文法的一些句子,该程序能够按照LL(1)文法的规则进行识别并且输出符号栈、当前输入符号、输入串、所使用产生式等信息。面对非该文法的句子,也能够正确判断不符合该文法,达到了预期目的。
该实验已经给出了LL(1)的分析表,所以免去了用程序自动生成FIRST集、FOLLOW集合、进而构造分析表的这个步骤,只需要按照分析规则自上而下判断即可。在编写程序时主要的点有:(1)产生式E’→+TE’中的E’是一个字符,所以在程序识别的时候需要稍加注意;(2)产生式的右部的压栈顺序为逆序压栈;(3)空串不用压栈。