目录
一 、实验目的
二 、实验内容及步骤
三、程序分析
1. 需求分析
2. 功能分析
1. LL(1)文法功能分析
2. 算符优先文法功能分析
3. 信创分析-主要针对能力提升中国产操作系统上开发内容。
四、源代码
1. LL(1)文法代码
2. 算符优先文法
五、测试结果
1. LL(1)文法:
2. 算符优先文法:
一 、实验目的
(1)通过完成LL(1)和SLR(1)(或者算符优先分析法)语法分析程序,了解自顶向下
和自底向上语法分析的过程。
(2)掌握两种语法分析程序的设计和实现过程。
二 、实验内容及步骤
(1)根据实验二的结果,至少使用以上语法分析中的一种完成语法分析。
(2)实现一个 LL(1)和SLR(1)(或者算符优先分析法)语法分析程序,可以对用户输入的符号串进行分析。
(3)以上内容2选1完成,要求输出分析过程及分析结果。
能力提升:在国产操作系统上开发功能模块。
三、程序分析
1. 需求分析
本次实验需要实现 LL(1)语法分析程序和算符优先分析程序,要实现这两个程序,首先要了解这两个文法的分析原理,而程序的需求也就是将文法分析的各个步骤与过程实现出来。下面分别介绍两个文法的分析过程与原理:
(1)LL(1)语法分析程序需求分析:
LL(1)语法分析程序的分析步骤为:
①判断是否是有左递归和左公共因子:
LL(1)文法没有左递归与左公共因子,拿到一个文法首先开始检查有无左递归和左公因子,若有则需要改造该文法才能继续进行分析,若无则直接开始第四步的分析。
②消除左递归:
左递归包括直接左递归与间接左递归,直接左递归是可以直接推导出由自身作为开头的产生式,如A->A...,间接左递归是间接地推导出由自身作为开头的产生式,如A->B..., B->A...。
若是直接左递归,则按图3-1的方法直接消除,若是间接左递归,则先将其转换为直接左递归,再按直接左递归的方法进行消除,方法也如图3-1所示:
图3-1 消除左递归方法
③提取左公共因子:
形如A->abb..,A->acc...的规则即含有左公共因子,提取方法是保留公共因子,将剩下的其余部分换成一个新非终结符,再由该新非终结符推导出非公共因子的其余部分,具体过程如图3-2所示:
图3-2 提取左公因子方法
④求所有非终结符的FIRST集合:
FIRST集合即产生式右部的首终结符字符的集合。求非终结符x的FIRST集合,需要找产生式的左部为x的。例如规则A->aBb,若a为终结符,则FIRST(A)={a};若a为非终结符,当a不为空串,则FIRST(A)=FIRST(a)-{ε},若a为空串,则将空串代入,规则变为A->Bb,循环上述过程,FIRST(A)=FIRST(B)。
图3-3 求FIRST集合方法
⑤求所有非终结符的FOLLOW集合:
FOLLOW集合即产生式右部的尾终结符字符的集合。求非终结符x的FOLLOW集合,需要找产生式的右部出现了x的。
规则1:begin->#...#,即FOLLOW(begin) = { #, ...}
规则2:A->aB(B后为空),则FOLLOW(B) = FOLLOW(A)
规则3:A->aBc(B后不为空)
规则3.1:c是终结符,则FOLLOW(B) = {a}
规则3.2:c是非终结符,且c不为空串,则FOLLOW(B)=FIRST(B)-{ε}
规则3.3:c是非终结符,但c为空串,则代入空串到原式,继续判断上述规则。
图3-4 求FOLLOW集合方法
⑥求所有产生式的SELECT集合:
SELECT集合即是输入符号集(即当前读取的符号)与该产生式的右侧符号串能产生的符号的交集。求产生式的SELECT集合有两种情况:
规则1:A->a,a不为空串ε,则SELECT(A->a)=FIRST(a)
规则2:A->a,a为空串ε,则SELECT(A->a)=FOLLOW(A)
图3-5 求FOLLOW集合方法
⑦求相同左部的所有产生式的SELECT集合的交集:
SELECT集合即是输入符号集(即当前读取的符号)与该产生式的右侧符号串能产生的符号的交集,求相同左部的所有产生式的SELECT集合的交集可以判断当前文法是否是LL(1)文法。若所有相同左部的产生式的SELECT集合的交集都为空,则是LL(1)文法,否则不是LL(1)文法。
⑧根据SELECT集合构造预测分析表:
LL(1)的预测分析表由行-终结符,列-非终结符构成,若非终结符x的SELECT集合中包括哪些终结符,则将该产生式的右部写入表格中。
(2)算符优先分析程序需求分析:
算符优先语法分析程序的分析步骤为:
①判断是否是有相邻的非终结符:
算符优先文法没有相邻的非终结符,拿到一个文法首先开始检查有无相邻的非终结符,若有则该文法不是算符优先文法,若无则可以继续第二步分析。
②求所有非终结符的FIRSTVT和LASTVT集合:
非终结符x的FIRSTVT集合即x从左到右,经过1步或n步能推导出的所有第一个终结符的集合,例如B->b...,B->Cb...。
非终结符x的FIRSTVT集合即从x从右到左,经过1步或n步能推导出的所有最后一个终结符的集合,例如B->...b,B->...bC。
③求算符优先关系:
算符共有三种关系,分别为算符等于,算符大于和算符小于,需要注意的是,所有算 符关系都不能调换顺序。
关系1:算符等于 a=b,即A->...ab... 或 A->aBb
关系2:算符大于 a>b,即A->...Bb...且B->...a或B->...aC,即a∈LASTVT(B)
关系3:算符大于 a<b,即A->...aB...且B->b...或B->Cb...,即b∈FIRSTVT(B)
④根据算符优先关系构造算符优先分析表:
算符优先分析表由行-终结符,列-终结符构成。
情况1:终结符a = 终结符b,则将 = 写入表格的a行b列中
情况2:终结符a > 终结符b,则将 > 写入表格的a行b列中
情况3:终结符a < 终结符b,则将 < 写入表格的a行b列中
2. 功能分析
1. LL(1)文法功能分析
系统的整体结构设计与需求分析中的一致,首先从grammer.txt中读取要分析的文法,接着程序通过main函数按顺序调用各种分析函数,处理后再提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。具体的各类主要函数功能如表3-2-1所示:
表3-2-1 LL(1)文法系统主要函数功能表
函数名称 | 形参含义 | 功能详解 |
int main() 主函数 | 无 | 程序入口,按分析顺序调用各个函数。调用函数对数据进行初始化,打开输入文件并调用函数进行分割与分析等。分析结束后提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。 |
init() 初始化函数 | 无 | 对程序所需要的各个集合,如非终结符VN和终结符VT进行初始化。 |
identifyVnVt (ArrayList<String> list) 识别终结符与非终结符 | 文法的产生式集合 | 1. 识别开始符号: 第一个产生式的左部 2. 识别非终结符: 左部与右部通过[->]进行分割获取,每个产生式的左部即为该文法的非终结符。 3. 识别终结符: 相同左部的不同产生式通过[|]进行分割获取,每一个产生式的每一个右部元素去除掉第二步中识别出的非终结符,即为该文法的终结符。 |
reformMap() 消除左递归和提取左公共因子 | 无 | 1. 产生式右部不为左递归: 保持左部不变,右部的最后面添加一个[左部+[']]的字符。 2. 产生式右部为左递归: 左递归标志设为true,左部替换为[左部+[']]的字符,右部保留除了第一个字符的剩下元素,再添加[左部+[']]的字符。 3. 左递归标志为true: 更新产生式的键值对map,添加[左部+[']]作为新的非终结符VN。右部不递归的产生式右部元素再添加一个空串ε。 4. 提取左公共因子: 左部保持不变,右部保留公共因子,在右部剩余部分删除,再添加为[左部+[']]。 添加新产生式左部为[左部+[']],右部为刚刚删除的非公共部分的其余部分。 |
getFirst() 求FIRST集合 | 无 | 1. 产生式第一个符号是终结符: 加入FIRST集合 2. 产生式第一个符号是非终结符: 递归查找该非终结符的 FIRST 集合,直到找到终结符或空串ϵ。 |
getFollow() 求FOLLOW集合 | 无 | 1. 开始符号start的FOLLOW集合: 直接加入[#] 2. 非终结符后直接跟一个终结符: 该终结符加入非终结符的FOLLOW集合。 3. ...非终结符1非终结符2...: 则FOLLOW(非终结符1) = FIRST(非终结符2) 4. 非终结符1->...非终结符2: 则FOLLOW(非终结符2) = FOLLOW(非终结符1) |
图3-2-1 LL(1)文法系统功能流程图
图3-2-2 识别VNVT函数功能流程图
图3-2-3 消除左递归函数功能流程图
图3-2-4 求FIRST集合函数功能流程图
2. 算符优先文法功能分析
系统的整体结构设计与需求分析中的一致,首先从grammer.txt中读取要分析的文法,接着程序通过main函数按顺序调用各种分析函数,处理后再提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。具体的各类主要函数功能如表3-2-1所示:
表3-2-1 算符优先文法系统主要函数功能表
函数名称 | 形参含义 | 功能详解 |
int main() 主函数 | 无 | 程序入口,按分析顺序调用各个函数。调用函数获取终结符与非终结符,分割产生式等。分析结束后提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。 |
getFirstVT(Character s, Set<Character> fvt) 计算FIRSTVT集合 | 当前要分析的左部文法符号 | 1. P->a...,产生式以终结符开头: 终结符加入FIRSTVT集合。 2. P->Qa...,即先以非终结符开头,紧跟终结符: 终结符加入FIRSTVT集合。 3. P->Q...,即以非终结符开头: 该非终结符的FIRSTVT集合加入P的FIRSTVT集合。 |
getLastVT(Character s, Set<Character> lvt) 计算LASTVT集合 | 当前要分析的左部文法符号 | 1. P->....aW,即先以非终结符结尾,倒数第二位是终结符: 终结符入LASTVT集合。 2. P->aQ |,即先以非终结符结尾,前面是终结符,后面有分隔符: 终结符入LASTVT集合,递归调用计算函数。 3. P->....Q,即以非终结符结尾: 该非终结符LASTVT集合加入P的LASTVT集合。 4. P->....Q |,即以非终结符结尾,后面有分隔符: 非终结符LASTVT集合加入P的LASTVT集合,递归调用计算函数。 |
constructMatrix() 构造算符优先矩阵 | 无 | 1. P->...ab...,即当前字符后面有一个终结符字符,并且不是竖线('|'): 将当前字符和后面的字符作为键,算符等于号('=')作为值,存入matrix中。 2. P->....aBc,即当前字符后面有两个字符,并且第二个字符也是终结符,并且不是竖线('|'): 将当前字符和第二个字符作为键,将算符等于号('=')作为值,存入matrix中。 3. P-> +T, 即当前字符后面有一个非终结符: 该字符的FIRSTVT集合中的每个元素与当前字符作为键,将小于号('<')作为值,存入matrix中。 4. P->E+,即当前字符前面为非终结符,且不是起始符号: 该字符的lastVt集合中的每个元素与当前字符作为键,将大于号('>')作为值,存入matrix中。 |
图3-2-5 算符优先文法系统功能流程图
图3-2-6 求FIRSTVT集合功能流程图
图3-2-7 求LASTVT集合功能流程图
3. 信创分析-主要针对能力提升中国产操作系统上开发内容。
1. 国产操作系统:
名称:麒麟操作系统
系统版本:20.03 (LTS-SP3)
内核: Linux 4.19.90
CPU:ARM 架构的 Kunpeng 920 处理器
桌面环境:UKUI
当前用户:root
2. 编译器:
名称:IntelliJ IDEA
版本:Community Edition 2018.3.6
图3-2-1 编译器配置
3. 开发步骤:
(1)安装idea
①在JetBrains官方网站下载IntelliJ IDEA,选择linux版本。
由于该操作系统的java版本太低,因此安装太高级的idea会无法使用,要安装较低版本的如2018.3.6。
②提取下载的压缩文件,输入命令:tar -zxvf ideaIC-2018.3.6.tar.gz
这里的[ideaIC-2018.3.6]应该换成自己下载的版本。
③给脚本执行权限,输入命令:
cd idea-IC-183.6156.11
sudo chmod a=+rx bin/idea.sh
bin/idea.sh
这里的[idea-IC-183.6156.11]依然应该换成自己下载的版本。
④安装idea,一直next就可以。
(2)打开idea
①打开解压出来的文件
②在该文件目录下打开终端
③在终端输入命令:bin/idea.sh
④成功打开idea
(3)在idea中运行程序
在国产操作系统上运行程序的结果和Windows上是一样的,这里就不演示了。
4. 开发中遇到的问题:
(1)无法安装vscode
原本开发消除注释的程序是用c++语言的写的,因此在国产操作系统上安装vscode。但由于该国产操作系统与其他操作系统的命令有部分不一样,例如apt-get命令不知为何使用不了,导致在诸如ubuntu操作系统上的安装流程,在麒麟操作系统上并不适用,无法成功安装。因此我将c++代码改为了java代码,并成功安装了idea进行编译。
(2)无法安装高版本的idea
一开始我直接安装了最高版本的idea,但在解压后安装idea时发现无法安装。
经查阅资料,发现是由于操作系统本身的java环境太低,与高版本的idea不适配,因为不同版本的idea有对应支持的java范围。因此我通过试验了几个版本的idea,找到2018版本的idea可以在操作系统上成功运行。
四、源代码
1. LL(1)文法代码
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;
public class TetsLab3LL1 {
// 文法定义文件
public static final String PATH = "grammer.txt";
// 从文件中读取得到的产生式集合
public static ArrayList<String> produceList;
// 文法开始符号
private static String START;
// 非终结符集合 终结符集合
private static HashSet<String> VN, VT;
// 产生式集合 key:产生式左边 value:产生式右边(含多条)
private static HashMap<String, ArrayList<ArrayList<String>>> MAP;
// "|" 分开的单条产生式对应的FIRST集合,用于构建LL1分析表
private static HashMap<String, String> oneLeftFirst;
// 总体的FIRST、FOLLOW集合
private static HashMap<String, HashSet<String>> FIRST, FOLLOW;
// LL1分析表的数组,用于输出
private static String[][] FOCAST_TABLE;
// LL1分析表的map,用于快速查找
private static HashMap<String, String> preMap;
public static void main(String[] args) {
// 1. 初始化
init();
// 2. 将文件中的符号分类,并以key-value形式存于MAP中
/* readFile(): 读取文件的内容,把文件的每一行存为一个字符串,放入ArrayList<String>中
类似[
"S -> A B | C D",
"A -> a | ε",
"B -> b"
]
*/
produceList = readFile(new File(PATH));
identifyVnVt(produceList);
// 3. 消除左递归
reformMap();
// 4. 求FIRST集合
getFirst();
// 5. 求FOLLOW集合
getFollow();
if (isLL1()) {
// 6. 构建预测分析表
preForm();
System.out.println("请输入要分析的单词串:");
Scanner in = new Scanner(System.in);
printAutoPre(in.nextLine());
in.close();
}
}
// 1. 初始化集合
private static void init() {
VN = new HashSet<>(); // 非终结符
VT = new HashSet<>(); // 终结符
MAP = new HashMap<>(); // 产生式集合,内含多条产生式
FIRST = new HashMap<>(); // FIRST集合
FOLLOW = new HashMap<>(); // FOLLOW集合
oneLeftFirst = new HashMap<>(); // 单条产生式的FIRST集合
preMap = new HashMap<>(); // 预测分析表的map
}
// 2. 将语法的字符进行分类,分为开始符号(非终结符)和终结符等
/*
该方法接受一个语法列表,识别出非终结符(VN)和终结符(VT),将它们分别存储在集合VN和VT中,同时构建产生式的映射MAP
*/
private static void identifyVnVt(ArrayList<String> list) { // 传入文法的产生式集合
START = String.valueOf(list.get(0).charAt(0)); // 文法的开始符号:文法中的第一个产生式的第一个字符
for (String oneline : list) { // 处理文法list中的每个产生式oneline
// 1. 分割该非终结符的产生式的左部和右部
// 用 "→" 分割产生式 vnvt[0]是文法的左边(非终结符) vnvt[1]是文法的右边(终结符)
String[] vnvt = oneline.split("→");
// 2. 处理产生式左部
String left = vnvt[0].trim(); // 产生式的左边 并消除左右空格
VN.add(left); // 将产生式的左边添加到非终结符集合
// 3. 处理产生式各个右部
// 每个左部对应一个mapValue
// 例如[
// [“A”,"a"], 代表A->Aa
// ["b"] 代表A->b
// ]
ArrayList<ArrayList<String>> mapValue = new ArrayList<>();
ArrayList<String> right = new ArrayList<>(); // 存储当前左部的其中一个右部,需要用[|]分割出来存入right
for (int j = 0; j < vnvt[1].length(); j++) { // 遍历产生式的右部的每一个字符
if (vnvt[1].charAt(j) == '|') { // 用 “|” 分割产生式的右部
// 把每个右部传入VT终结符集合,把某个左部对应的所有右部集合加入mapValue
// (这次移入会把终结符和非终结符一起移入,非终结符会在后续删掉)
addRightToVTAndMapValue(right, mapValue);
right = new ArrayList<>(); // 清空right
continue;
}
// 如果产生式某字符的左边含有中文或英文的单引号,则视为一个整体常量
// 如果当前字符后面紧跟一个单引号(' 或 ’),则将这两个字符视为一个整体,并将它们添加到 right 中。
// 如果不是,则只添加当前字符。
// if (j + 1 < vnvt[1].length() && (vnvt[1].charAt(j + 1) == '\'' || vnvt[1].charAt(j + 1) == '’')) {
// right.add(new StringBuilder().append(vnvt[1].charAt(j)).append(vnvt[1].charAt(j + 1)).toString());
// j++;
// }
else { // 如果没有遇到分割符[|]
right.add(String.valueOf(vnvt[1].charAt(j))); // 将字符添加到right
}
}
addRightToVTAndMapValue(right, mapValue); // 最后一次遍历不会遇到[|],再add一次
// 4. 把处理好的左部及对应的各个右部添加到MAP
// 以左部非终结符left作为键,以各个右部形成的列表作为值
// 例如A->Aa|b,最终map里就是key-A,value-[["A","a"],["b"]]
MAP.put(left, mapValue);
}
VT.removeAll(VN); // 循环结束后,找到所有的非终结符了,再从终结符集合中移除非终结符
// 打印Vn非终结符、Vt终结符
System.out.println("\n非终结符集:\t{" + String.join("、", VN) + "}");
System.out.println("终结符集:\t{" + String.join("、", VT) + "}\n");
}
// 2.1 从文法文件中读取语法,并将文法的每一行存放到list集合中
public static ArrayList<String> readFile(File file) {
System.out.println("分析的文法为:");
ArrayList<String> result = new ArrayList<>(); // 要返回的存储了文件每一行的string集合
try {
BufferedReader br = new BufferedReader(new FileReader(file)); // 保存文件内容到缓存区
String s; // 当前读取的一行
while ((s = br.readLine()) != null) { // 逐行读入文件每行
System.out.println("\t" + s);
result.add(s.trim()); // 把当前行消除空格并存入result
}
br.close();
}
catch (Exception e) {
System.out.println(e.getMessage());
}
return result;
}
// 2.2 添加 终结符VT 和 映射值mapValue
private static void addRightToVTAndMapValue(ArrayList<String> right, ArrayList<ArrayList<String>> mapValue) {
VT.addAll(right); // VT终结符集合 加入right右部集合中的每个元素(这次移入会把终结符和非终结符一起移入,非终结符会在后续删掉)
mapValue.add(new ArrayList<>(right)); // mapVlue列表加入right集合 并且这个集合是新new的 修改right集合不会影响mapVaue列表
}
// 3. 消除左递归
private static void reformMap() {
boolean isEditMap = false; // 标志MAP是否被修改
Set<String> keys = new HashSet<>(MAP.keySet()); // 保存MAP中所有去重的key【即非终结符】
Iterator<String> it = keys.iterator(); // keys的迭代器
ArrayList<String> nullRight = new ArrayList<>(); // 空串右部
nullRight.add("ε"); // ε是空串
while (it.hasNext()) { // 还有下一个非终结符
String left = it.next(); // 当前产生式的左部非终结符1
boolean isReflag = false; // 标志是否有左递归
ArrayList<ArrayList<String>> rightList = MAP.get(left); // 产生式原本的右部列表,例如[ ["S","a","c"], ["b"] ],用来遍历
ArrayList<String> noReRightElement = new ArrayList<>(); // 不递归的产生式的右部元素
ArrayList<ArrayList<String>> yesReRightList = new ArrayList<>(); // 递归的产生式的右部列表
// 消除直接左递归
for (ArrayList<String> strings : rightList) { // 遍历当前产生式的所有右部列表,strings例如["S","a","c","b"]
ArrayList<String> yesReRightElement = new ArrayList<>(); // 递归的产生式的右部元素
if (strings.get(0).equals(left)) { // 该右部是左递归的:右部的第一个字符是左部,例如["S","a","c"]中的"S"
isReflag = true; // 设置左递归标志为true
for (int j = 1; j < strings.size(); j++) { // 继续遍历剩下的右部
yesReRightElement.add(strings.get(j)); // 递归的产生式的右部元素:加入除了左部(第一个符)的所有右部,如["a","c"]
}
yesReRightElement.add(left + "'"); // 递归的产生式的右部元素:继续加入产生式左部的[']版。如["a","c","S'"]
yesReRightList.add(yesReRightElement); // 递归的产生式的右部列表:放入递归的产生式的新右部[["a","c","S'"]]
}
else { // 该右部不是左递归的
noReRightElement.addAll(strings); // 不递归的产生式的右部元素:加入原产生式的右部例如["b"]
noReRightElement.add(left + "'"); // 不递归的产生式的右部元素:继续加入左部的[']版,例如["b","S'"]
}
}
if (isReflag) { // 如果存在左递归,则更新MAP
isEditMap = true; // 更新MAP的标志为true
yesReRightList.add(nullRight); // 递归的产生式的右部列表:继续加入空串右部元素["ε"], 例如[["a","c","S'"],["ε"]]
MAP.put(left + "'", yesReRightList); // 加入递归的产生式,左部是原左部加['],右边是递归的产生式的右部列表 map如S'->acS'| ε
VN.add(left + "'"); // 加入新的非终结符到VN集合
VT.add("ε"); // 加入新的[ε]空串到VT集合
ArrayList<ArrayList<String>> newLeftOld = new ArrayList<>(); // 存放不递归的新产生式 如S->bS'
newLeftOld.add(noReRightElement); // 存放旧的产生式
MAP.put(left, newLeftOld); // 更新原产生式
}
}
// 如果文法被修改,则输出修改后的文法
if (isEditMap) {
System.out.println("消除左递归后的文法:");
Set<String> kSet = new HashSet<>(MAP.keySet()); // 找到文法的所有非终结符
for (String k : kSet) { // 遍历该文法每个非终结符
ArrayList<ArrayList<String>> rightList = MAP.get(k); // 找到该非终结符对应的右部列表 如[["a","c","S'"],["ε"]]
System.out.print("\t" + k + "→");
for (int i = 0; i < rightList.size(); i++) { // 遍历右部列表 如[["a","c","S'"],["ε"]]
System.out.print(String.join("", // 把该右部列表的第i个右部元素拼接为一个字符串 如["a","c","S'"]拼接为["acS'"]
rightList.get(i).toArray(new String[rightList.get(i).size()])));
if (i + 1 < rightList.size()) { // 该右部列表还有下一个右部元素
System.out.print("|"); // 用[|]分隔
}
}
System.out.println();
}
}
// MAP.toString(); // 输出新MAP
}
// 5. 求FIRST集合
/*
遇到终结符:加入FIRST集合
遇到非终结符:递归,求该非终结符的FIRST集合,即为该终结符的FIRST集合
*/
private static void getFirst() {
System.out.println("\n该文法的FIRST集合:");
for (String vn : VN) { // 遍历每个非终结符
HashSet<String> FIRST_List = new HashSet<>(); // 当前非终结符vn的FIRST集合,包括该vn的各个产生式各自的FIRST集合
ArrayList<ArrayList<String>> rightList = MAP.get(vn); // 当前非终结符vn对应的右部列表 如[["a","c","S'"], ["ε"]]
// System.out.println(key+":");
for (ArrayList<String> rightListElement : rightList) { // 遍历每个右部列表的每个右部元素(产生式) 如["a","c","S'"]
// 当前非终结符的当前产生式的FIRST集合 即组成当前非终结符vn的FIRST集合的一部分
HashSet<String> FIRST_List_One = new HashSet<>();
// 将右部列表中的单个元素拼接成字符串 如["a","c","S'"]拼接为"acS"
String oneRightElement = String.join("",
rightListElement.toArray(new String[rightListElement.size()]));
// System.out.println("oneLeft: "+oneLeft);
if (VT.contains(rightListElement.get(0))) { // 如果产生式第一个符号是终结符 则该符号是FIRST集合的符号
FIRST_List.add(rightListElement.get(0)); // 将该符号加入该产生式的FIRST集合
FIRST_List_One.add(rightListElement.get(0));
oneLeftFirst.put(vn + "$" + rightListElement.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
}
else { // 如果产生式第一个符号是非终结符
// 标记产生式中的符号是否为非终结符,是则检查下一个字符
boolean[] isVn = new boolean[rightListElement.size()];
isVn[0] = true; // 第一个为非终结符
int p = 0;
while (isVn[p]) { // 循环检查产生式中的每个符号 直到找到终结符或者空串(ε)。
if (VT.contains(rightListElement.get(p))) { // 1. 如果遇到终结符
FIRST_List.add(rightListElement.get(p)); // 则把该终结符加入当前非终结符的FIRST集合中
FIRST_List_One.add(rightListElement.get(p)); // 把该终结符加入当前产生式的FIRST集合中
oneLeftFirst.put(vn + "$" + rightListElement.get(p), vn + "→" + oneRightElement); // 添加到预测分析表中
break; // 找到终结符了,退出循环
}
// 2. 如果不是终结符
String vnGo = rightListElement.get(p); // 当前符号是非终结符,转换为找该非终结符的FIRST集合
Stack<String> stack = new Stack<>();
stack.push(vnGo); // 将当前非终结符放入栈中
while (!stack.isEmpty()) { // 如果栈不为空:是非终结符 转换为找该终结符的FIRST集合
ArrayList<ArrayList<String>> listGo = MAP.get(stack.pop()); // 获取该非终结符vnGo的右部
for (ArrayList<String> listGoCell : listGo) { // 遍历该非终结符vnGo的右部的每个元素
if (VT.contains(listGoCell.get(0))) { // 如果第一个字符是终结符 可能1:空 可能2:终结符
if (listGoCell.get(0).equals("ε")) { // 可能1:空 可以查询下一个字符
if (!vn.equals(START)) { // 如果当前的终结符不是开始符号(开始符号不能推出空)
FIRST_List.add(listGoCell.get(0)); // 把空串加入FIRST集合
FIRST_List_One.add(listGoCell.get(0));
oneLeftFirst.put(vn + "$" + listGoCell.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
}
if (p + 1 < isVn.length) { // 继续处理下一个字符
isVn[p + 1] = true;
}
}
else { // 可能2:终结符 加入对应的FIRST集合
FIRST_List.add(listGoCell.get(0));
FIRST_List_One.add(listGoCell.get(0));
oneLeftFirst.put(vn + "$" + listGoCell.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
}
}
else { // 不是终结符号,入栈,再转换为找该非终结符的FIRST集合 以此递归
stack.push(listGoCell.get(0));
}
}
}
p++;
if (p > isVn.length - 1)
break;
}
}
// 保存当前非终结符vn的产生式oneRightElement的FIRST元素到总FIRST集合中
// key为当前产生式 如A->acS value为当前产生式的FIRST集合
FIRST.put(vn + "→" + oneRightElement, FIRST_List_One);
}
FIRST.put(vn, FIRST_List); // 添加到总的FIRST集合中
// 输出key的FIRST集合
System.out.println(
"\tFIRST(" + vn + ")={" + String.join("、",
FIRST_List.toArray(new String[FIRST_List.size()])) + "}");
}
}
// 求FOLLOW集合
private static void getFollow() {
System.out.println("\n该文法的FOLLOW集合:");
// 遍历非终结符集合的迭代器
Iterator<String> it = VN.iterator();
// 创建一个HashMap keyFollow 用于存储每个非终结符的FOLLOW集合
HashMap<String, HashSet<String>> keyFollow = new HashMap<>();
// 存放特殊组合 如A->...B 或者 A->...Bε(结尾是非终结符/结尾是空串)
ArrayList<HashMap<String, String>> vn_VnList = new ArrayList<>();
// 存放vn_VnList的左边和右边
HashSet<String> vn_VnListLeft = new HashSet<>();
HashSet<String> vn_VnListRight = new HashSet<>();
// 开始符号加入#,初始化keyFollow
keyFollow.put(START, new HashSet() {
private static final long serialVersionUID = 1L;
{
add("#");
}
});
// 遍历非终结符集合
while (it.hasNext()) { // 还有非终结符
String currentVN = it.next(); // key为当前的非终结符
ArrayList<ArrayList<String>> rightList = MAP.get(currentVN); // rightList为当前非终结符的右部 如[["a","c","S'"], ["ε"]]
ArrayList<String> rightListOne; // rightListOne为当前非终结符的右部的其中一个右部元素 如["a","c","S'"]
// 如果key的FOLLOW集合不存在,则初始化
if (!keyFollow.containsKey(currentVN)) {
keyFollow.put(currentVN, new HashSet<>());
}
// keyFollow.toString();
// 遍历该非终结符的所有产生式
for (ArrayList<String> strings : rightList) {
rightListOne = strings; // rightListOne为当前非终结符的右部的其中一个右部元素 如["a","c","S'"]
// (1) 如果非终结符号的后面找到了一个终结符
for (int j = 1; j < rightListOne.size(); j++) {
HashSet<String> set = new HashSet<>(); // 当前产生式的FOLLOW集合
if (VT.contains(rightListOne.get(j))) { // 如果找到一个终结符
set.add(rightListOne.get(j)); // 把该终结符加入到当前产生式的FOLLOW集合
if (keyFollow.containsKey(rightListOne.get(j - 1)))
set.addAll(keyFollow.get(rightListOne.get(j - 1))); // 把上一个非终结符的FOLLOW集合加入当前的FOLLOW集合
keyFollow.put(rightListOne.get(j - 1), set); // 把当前产生式的FOLLOW集合加入到前一个非终结符的FOLLOW集合
}
}
// (2) 找...非终结符1非终结符2...组合
// 则FOLLOW(非终结符1) = FIRST(非终结符2)
for (int j = 0; j < rightListOne.size() - 1; j++) {
HashSet<String> set = new HashSet<>();
if (VN.contains(rightListOne.get(j)) && VN.contains(rightListOne.get(j + 1))) {
set.addAll(FIRST.get(rightListOne.get(j + 1)));
set.remove("ε");
if (keyFollow.containsKey(rightListOne.get(j)))
set.addAll(keyFollow.get(rightListOne.get(j)));
keyFollow.put(rightListOne.get(j), set);
}
}
// (3) A->...B 或者 A->...Bε(可以有n个ε)的组合存起来
for (int j = 0; j < rightListOne.size(); j++) {
HashMap<String, String> vn_Vn;
if (VN.contains(rightListOne.get(j)) && !rightListOne.get(j).equals(currentVN)) {// 是VN且A不等于B
boolean isAllNull = false;// 标记VN后是否为空
if (j + 1 < rightListOne.size())// 即A->...Bε(可以有n个ε)
for (int k = j + 1; k < rightListOne.size(); k++) {
if ((FIRST.containsKey(rightListOne.get(k)) && FIRST.get(rightListOne.get(k)).contains("ε"))) {// 如果其后面的都是VN且其FIRST中包含ε
isAllNull = true;
} else {
isAllNull = false;
break;
}
}
// 如果是最后一个为VN,即A->...B
if (j == rightListOne.size() - 1) {
isAllNull = true;
}
if (isAllNull) {
vn_VnListLeft.add(currentVN);
vn_VnListRight.add(rightListOne.get(j));
// 往vn_VnList中添加,分存在和不存在两种情况
boolean isHaveAdd = false;
for (int x = 0; x < vn_VnList.size(); x++) {
HashMap<String, String> vn_VnListCell = vn_VnList.get(x);
if (!vn_VnListCell.containsKey(currentVN)) {
vn_VnListCell.put(currentVN, rightListOne.get(j));
vn_VnList.set(x, vn_VnListCell);
isHaveAdd = true;
break;
} else {
// 去重
if (vn_VnListCell.get(currentVN).equals(rightListOne.get(j))) {
isHaveAdd = true;
break;
}
continue;
}
}
if (!isHaveAdd) {// 如果没有添加,表示是新的组合
vn_Vn = new HashMap<>();
vn_Vn.put(currentVN, rightListOne.get(j));
vn_VnList.add(vn_Vn);
}
}
}
}
}
}
keyFollow.toString();
// (4) vn_VnListLeft减去vn_VnListRight,剩下的就是入口产生式,
vn_VnListLeft.removeAll(vn_VnListRight);
// 用栈或者队列都行
Queue<String> keyQueue = new LinkedList<>(vn_VnListLeft);
// 处理 vn_VnListLeft 的每个元素
while (!keyQueue.isEmpty()) {
String keyLeft = keyQueue.poll();
for (int t = 0; t < vn_VnList.size(); t++) {
HashMap<String, String> vn_VnListCell = vn_VnList.get(t);
if (vn_VnListCell.containsKey(keyLeft)) {
HashSet<String> set = new HashSet<>();
// 原来的FOLLOW加上左边的FOLLOW
if (keyFollow.containsKey(keyLeft))
set.addAll(keyFollow.get(keyLeft));
if (keyFollow.containsKey(vn_VnListCell.get(keyLeft)))
set.addAll(keyFollow.get(vn_VnListCell.get(keyLeft)));
keyFollow.put(vn_VnListCell.get(keyLeft), set);
keyQueue.add(vn_VnListCell.get(keyLeft));
// 移除已处理的组合
vn_VnListCell.remove(keyLeft);
vn_VnList.set(t, vn_VnListCell);
}
}
}
// 此时 keyFollow 为完整的FOLLOW集
FOLLOW = keyFollow;
// 打印FOLLOW集合
for (String key : keyFollow.keySet()) {
HashSet<String> f = keyFollow.get(key);
System.out.println("\tFOLLOW(" + key + ")={" + String.join("、", f.toArray(new String[f.size()])) + "}");
}
}
/*
判断是不是LL(1)文法
1. 无左递归:文法不能直接或间接地包含左递归。
2. SELECT集合的交集为空:对于某个非终结符 A,如果 A 有两个产生式 A -> α 和 A -> β,那么必须满足:
(1)如果 α 和 β 都能推导出空串(即包含 ε),则 FIRST(α) 与 FOLLOW(A) 必须没有交集。
(2)如果 α 和 β 不都能推导出空串,则 FIRST(α) 和 FIRST(β) 必须没有交集。
*/
private static boolean isLL1() {
System.out.println("\n判断是否是LL(1)文法:");
boolean flag = true; // 是否是LL(1)文法的标志
for (String key : VN) { // 遍历非终结符
ArrayList<ArrayList<String>> list = MAP.get(key);// 对于每一个非终结符 key,获取其产生式列表 list
if (list.size() > 1) { // 如果该终结符的产生式有两个以上,则判断SELECT集合的交集
for (int i = 0; i < list.size(); i++) {
String a = String.join("", list.get(i));
for (int j = i + 1; j < list.size(); j++) {
String b = String.join("", list.get(j));
// 对于每一对产生式,如果其中一个是空产生式(ε),则要求该非终结符对应的 FIRST 集合和 FOLLOW 集合的交集为空
if (a.equals("ε") || b.equals("ε")) {
// (1)若a=ε,则判断FOLLOW(A) ∩ FIRST(b) = φ 相当于FOLLOW(Key) ∩ FIRST(b) = φ
// (2)若b=ε, 则判断FIRST(a) ∩ FOLLOW(B)=φ 相当于FIRST(a) ∩ FOLLOW(Key) = φ
Set<String> retainSet = new HashSet<>(FIRST.get(key));
if (FOLLOW.get(key) != null) {
retainSet.retainAll(FOLLOW.get(key)); // 求A的FOLLOW集合与FIRST集合的交集
}
if (!retainSet.isEmpty()) { // 如果交集不为空,说明存在冲突
flag = false;// 不是LL(1)文法,输出FIRST(a)FOLLOW(a)的交集
printIntersectionSet("\tFIRST(" + key + ") ∩ FOLLOW(" + key + ")", retainSet);
break;
} else {
System.out.println("\tFIRST(" + key + ") ∩ FOLLOW(" + key + ") = φ");
}//如果交集不为空,说明存在冲突,输出交集并将标志 flag 设置为 false
}
else { // (2)如果a和b都不为空 则要FIRST(a) ∩ FIRST(b) = φ
Set<String> retainSet = new HashSet<>(FIRST.get(key + "→" + a));
retainSet.retainAll(FIRST.get(key + "→" + b));
if (!retainSet.isEmpty()) { // 1. 如果FIRST(a)和FIRST(b)的交集不为空
flag = false;// 不是LL(1)文法,输出FIRST(a)FIRST(b)的交集
printIntersectionSet("\tFIRST(" + a + ") ∩ FIRST(" + b + ")", retainSet);
break;
}
else { // 2. 如果FIRST(a)和FIRST(b)的交集为空
System.out.println("\tFIRST(" + a + ") ∩ FIRST(" + b + ") = φ");
}
}
}
}
}
}
if (flag)
System.out.println("\t是LL(1)文法,继续分析!");
else
System.out.println("\t不是LL(1)文法,退出分析!");
return flag;
}
/*
输出
*/
private static void printIntersectionSet(String prefix, Set<String> set) {
System.out.println(prefix + " = {" + String.join("、", set) + "}");
}
/*
构建预测分析表FORM
遍历非终结符和终结符集合,利用 oneLeftFirst 和 FOLLOW 集合填充预测分析表
*/
private static void preForm() {
HashSet<String> setVT = new HashSet<>(VT); // 终结符集setVT
setVT.remove("ε"); // 去除空串 "ε"
FOCAST_TABLE = new String[VN.size() + 1][setVT.size() + 2]; // 预测分析表:行是非终结符,列是终结符的二维数组
Iterator<String> itVn = VN.iterator(); // 非终结符集VN的迭代器
Iterator<String> itVt = setVT.iterator(); // 终结符集VT的迭代器
// (1)第一个循环遍历 FORM,根据迭代器填充第一行和第一列
// 同时填充不是空串的,即FIRST集合
for (int i = 0; i < FOCAST_TABLE.length; i++) {
for (int j = 0; j < FOCAST_TABLE[0].length; j++) {
if (i == 0 && j > 0) { // 第一行 填充终结符VT
if (itVt.hasNext()) {
FOCAST_TABLE[i][j] = itVt.next();
}
if (j == FOCAST_TABLE[0].length - 1) // 最后一列 加入 [#]
FOCAST_TABLE[i][j] = "#";
}
if (j == 0 && i > 0) { // 第一列 填充非终结符VN
if (itVn.hasNext())
FOCAST_TABLE[i][j] = itVn.next();
}
if (i > 0 && j > 0) { // 不是第一行和第一列,且不是空串(则填充FIRST集合)
String oneLeftKey = FOCAST_TABLE[i][0] + "$" + FOCAST_TABLE[0][j];// 作为key查找其First集合
FOCAST_TABLE[i][j] = oneLeftFirst.get(oneLeftKey);
}
}
}
// (2)第二个循环 处理空串的情况 填充 FOLLOW 集合
for (int i = 1; i < FOCAST_TABLE.length; i++) {
String oneLeftKey = FOCAST_TABLE[i][0] + "$ε"; // 非终结符+"$ε"
if (oneLeftFirst.containsKey(oneLeftKey)) { // 如果有空产生式 则填充FOLLOW集合
HashSet<String> follows = FOLLOW.get(FOCAST_TABLE[i][0]); // 获取空产生式的FOLLOW集合
for (String vt : follows) { // 遍历FOLLOW集合中的元素(终结符)
for (int j = 1; j < FOCAST_TABLE.length; j++)
for (int k = 1; k < FOCAST_TABLE[0].length; k++) {
if (FOCAST_TABLE[j][0].equals(FOCAST_TABLE[i][0]) && FOCAST_TABLE[0][k].equals(vt)) { // 找到要填充的行、列
FOCAST_TABLE[j][k] = oneLeftFirst.get(oneLeftKey); // 对应位置填写当前key的产生式
}
}
}
}
}
// 打印预测表
printForm();
//将预测分析表的数据存储到Map中,以便于快速查找
buildPreMap();
}
// 打印预测表
private static void printForm() {
System.out.println("\n该文法的预测分析表为:");
for (String[] line : FOCAST_TABLE) {
for (String column : line) {
System.out.print((column != null ? column : " ") + "\t"); // 若不为空则输出当前行所在的列的内容
}
System.out.println();
}
System.out.println();
}
// 根据FROM建立Map
private static void buildPreMap() {
for (int i = 1; i < FOCAST_TABLE.length; i++) {
for (int j = 1; j < FOCAST_TABLE[0].length; j++) {
if (FOCAST_TABLE[i][j] != null) {
String[] tmp = FOCAST_TABLE[i][j].split("→");
preMap.put(FOCAST_TABLE[i][0] + FOCAST_TABLE[0][j], tmp[1]);
}
}
}
}
// 根据LL(1)分析表来分析输入串
public static void printAutoPre(String str) {
System.out.println(str + "的分析过程:");
Deque<String> queue = new LinkedList<>(); // 队列queue 用于存储单词串的分割
for (int i = 0; i < str.length(); i++) {
StringBuilder t = new StringBuilder().append(str.charAt(i));
if (i + 1 < str.length() && (str.charAt(i + 1) == '\'' || str.charAt(i + 1) == '’')) {
t.append(str.charAt(i + 1)); // 将输入的单词串按照一定规则分割成单个符号,并存储在队列中,同时在符号后面的字符是 ' 或 ’ 时,将其合并。
i++;
}
queue.offer(t.toString());
}
queue.offer("#"); // 将结束符 # 加入队列末尾。
// 分析栈
Deque<String> stack = new LinkedList<>();
stack.push("#"); // "#"开始
stack.push(START); // 初态为开始符号
boolean isSuccess = false; // 分析成功的标志
int step = 1; // 步骤数初始为1
while (!stack.isEmpty()) { // 进行循环分析,直到分析栈为空
String left = stack.peek();
String right = queue.peek(); // 检查分析栈栈顶 left 和队列头部 right
// (1)分析成功
if (left.equals(right) && right.equals("#")) { // 栈和输入串相等,且输入串为#
isSuccess = true;
System.out.println((step++) + "\t#\t#\t" + "分析成功");
break; // 结束循环
}
// (2)规约
if (left.equals(right)) { // 栈顶和当前符号匹配,均为终结符
System.out.println((step++) + "\t" + toString(stack) + "\t" + toString(queue) + "\t\"" + left + "\"\t匹配");
stack.pop(); // 出栈
queue.poll(); // 出队列
continue; // 继续
}
// (3)产生式推导
if (preMap.containsKey(left + right)) {
System.out.println((step++) + "\t" + toString(stack) + "\t" + toString(queue) + "\t产生式" + left + "→"
+ preMap.get(left + right));
stack.pop();
String tmp = preMap.get(left + right); // 从预测分析表 preMap 中查找对应产生式,输出相应信息,将产生式逆序入栈
for (int i = tmp.length() - 1; i >= 0; i--) { // 逆序进栈
StringBuilder t = new StringBuilder();
if (tmp.charAt(i) == '\'' || tmp.charAt(i) == '’') {
t.append(tmp.charAt(i-1)).append(tmp.charAt(i));
i--;
}
else {
t.append(tmp.charAt(i));
}
if (!t.toString().equals("ε")) { // 不为空串则入栈
stack.push(t.toString());
}
}
continue;
}
//(4)其他情况:不规约,无推导,不成功,则直接退出
break;
}
if (!isSuccess) { // 如果没有成功就中途退出
System.out.println((step++) + "\t#\t#\t" + "分析失败");
}
}
// 将集合的转化为字符串
private static String toString(Collection<String> collection) {
return String.join("", collection.toArray(new String[0]));
}
}
2. 算符优先文法
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;
import java.util.stream.Collectors;
public class TestLab3SFYX {
//FIRSTVT集合
private static Map<Character, Set<Character>> firstVt = new HashMap<>();
//LASTVT集合
private static Map<Character, Set<Character>> lastVt = new HashMap<>();
//输入的文法
private static List<String> input = new ArrayList<>();
//终结符
private static Set<Character> End = new LinkedHashSet<>();
//非终结符
private static Set<Character> NoEnd = new LinkedHashSet<>();
//算符矩阵
private static Map<String, Character> matrix = new HashMap<>();
//输入流
private static Scanner in = new Scanner(System.in);
//文法的左右分割一一对应
private static Map<Character, List<String>> produce = new HashMap<>();
// TODO 主方法
public static void main(String[] args) {
String path = "grammer2.txt";
int flag = 1;
while (flag != 0) {
input = readFile(new File(path));
if (isOperator()) {
System.out.println("此文法是算符文法!");
flag = 0;
} else {
System.out.println("此文法不是算符文法!请重新输入:");
input.clear();
}
}
//获取终结符
getVT();
//获取非终结符
getVN();
//分离文法
getProduce();
//计算并显示FirstVT、LastVT集合
DisplayFirstVT_LastVT();
//构造算符优先矩阵并打印
constructMatrix();
//分析句子过程
analysisProcess();
}
//TODO 读取文法文件
public static ArrayList<String> readFile(File file) {
System.out.println("从文件读入的文法为:");// 输出读取的文法内容
ArrayList<String> result = new ArrayList<>();
try {
BufferedReader br = new BufferedReader(new FileReader(file));
String s;
while ((s = br.readLine()) != null) {
System.out.println("\t" + s);
result.add(s.trim());
}
br.close();
} catch (Exception e) {
System.out.println(e.getMessage());// 如果发生异常,输出错误信息
}
return result;
}
// TODO 判断是否是算符文法
/**
* 判断其是不是算符文法
* 遍历每个产生式,检查是否有连续的非终结符相连,如果有,则不是算符文法
* 如果没有连个连续非终结符号相连的就是算符优先文法,返回判断结果
*/
private static boolean isOperator() {
int i;
for (i = 0; i < input.size(); i++) {
for (int j = 0; j < input.get(i).length() - 1; j++) {
String str = input.get(i);
if (str.charAt(j) >= 65 && str.charAt(j) <= 90) {
if ((str.charAt(j + 1) >= 65 && str.charAt(j + 1) <= 90)) {
return false;
}
}
}
}
return true;
}
// TODO 求终结符
/**
* 获取文法中的终结符
* 遍历每个产生式,将终结符添加到End集合中
*/
private static void getVT() {
for (String temp : input) {
for (int j = 2; j < temp.length(); j++) {
if (temp.charAt(j) < 65 || temp.charAt(j) > 90 && temp.charAt(j) != '|') {
End.add(temp.charAt(j));
}
}
}
End.add('#');
}
// TODO 求非终结符
/**
* 获取文法中的非终结符
* 遍历每个产生式,将非终结符添加到NoEnd集合中
*/
private static void getVN() {
for (String temp : input) {
for (int j = 2; j < temp.length(); j++) {
if (temp.charAt(j) >= 65 && temp.charAt(j) <= 90) {
NoEnd.add(temp.charAt(j));
}
}
}
}
//TODO 分离文法
/**
* 分离文法,将每一行的文法分离成左右两部分
* 将左部分作为键,右部分作为值存入produce集合中
* 将每一行的文法分离,如E->E+T|T分离成E和E+T,T
*/
private static void getProduce() {
for (String s : input) {
List<String> list = new ArrayList<>();
String str = s;
StringBuffer a = new StringBuffer();
for (int j = 2; j < str.length(); j++) {
if (str.charAt(j) != '|') {
a.append(str.charAt(j));
} else {
list.add(a.toString());
a.delete(0, a.length());//清空a
}
}
list.add(a.toString());
produce.put(str.charAt(0), list);
}
}
// TODO 计算firstVt集合
/**
* 遍历文法,根据文法规则计算FirstVT集合,结果存储在fvt参数中
* 获得firstVt集合
*/
private static void getFirstVT(Character s, Set<Character> fvt) {
String str = null;
int i = 0;
for (i = 0; i < input.size(); i++) {
if (input.get(i).charAt(0) == s) {
str = input.get(i);
}
}
if (str == null)
return;
//logger.info("求firstVt集合:" + str);
boolean flag = true;
for (i = 2; i < str.length(); i++){
if (str.charAt(i) < 65 || str.charAt(i) > 90){
//P->a.....,即以终结符开头,该终结符入Firstvt
if ((String.valueOf(str.charAt(i - 1)).equals("→")) || str.charAt(i - 1) == '|') {
fvt.add(str.charAt(i));
flag = false;
}
}
if (str.charAt(i) == '|')
flag = true;
//P->Qa....,即先以非终结符开头,紧跟终结符,则终结符入Firstvt
if (str.charAt(i) >= 65 && str.charAt(i) <= 90){
int index = i + 1;
if (index < str.length()){
if ((str.charAt(index) < 65 || str.charAt(index) > 90)
&& End.contains(str.charAt(index)) && flag){
fvt.add(str.charAt(index));
}
}
}
//若有P->Q.....,即以非终结符开头,该非终结符的Firstvt加入P的Firstvt
if ((str.charAt(i - 1) == '|' || String.valueOf(str.charAt(i-1)).equals("→"))
&& str.charAt(i) >= 65 && str.charAt(i) <= 90) {
//E->E
if (str.charAt(i) == str.charAt(0)) {
continue;
}
//递归
//E->P
//logger.info("firstVt集合进入递归");
getFirstVT(str.charAt(i), fvt);
}
}
}
// TODO 计算lastVt集合
/**
* 遍历文法,根据文法规则计算LastVT集合,结果存储在lvt参数中
* 获得lastVt集合
*/
private static void getLastVT(Character s, Set<Character> lvt) {
String str = null;
int i;
for (i = 0; i < input.size(); i++) {
if (input.get(i).charAt(0) == s) {
str = input.get(i);
}
}
if (str == null)
return;
for (i = 2; i < str.length(); i++) {
if (str.charAt(i) < 65 || str.charAt(i) > 90) {
//P->....aW,即先以非终结符结尾,前面是终结符,则终结符入Lastvt,Q处于产生式最后一位的情况
if (i == str.length() - 1 ||
(i == str.length() - 2 &&
str.charAt(i + 1) >= 65 && str.charAt(i + 1) <= 90 &&
str.charAt(i) != '|' && !String.valueOf(str.charAt(i)).equals("→")
)
) {
lvt.add(str.charAt(i));
}
if (i < str.length() - 2) {
//P->aQ | ,即先以非终结符结尾,前面是终结符,则终结符入Lastvt
if (str.charAt(i + 1) == '|' ||
(str.charAt(i + 2) == '|' && str.charAt(i + 1) >= 65 && str.charAt(i + 1) <= 90)
) {
lvt.add(str.charAt(i));
}
}
} else {
//P->....Q,即以非终结符结尾,该非终结符的Lastvt入P的Lastvt
if (i == str.length() - 1) {
if (str.charAt(i) == str.charAt(0)) {
continue;
}
getLastVT(str.charAt(i), lvt);
} else if (str.charAt(i + 1) == '|') {
if (str.charAt(i) == str.charAt(0)) {
continue;
}
//P->....Q,即以非终结符结尾,该非终结符的Lastvt入P的Lastvt
getLastVT(str.charAt(i), lvt);
}
}
}
}
/**
* 显示FirstVT集合和LastVT集合
* 遍历文法,计算并显示每个非终结符的FirstVT和LastVT集合
*/
//TODO 显示firstVt集合和lastVt集合----一次优化
private static void DisplayFirstVT_LastVT() {
for (String value : input) {
Set<Character> fvt = new HashSet<>();
getFirstVT(value.charAt(0), fvt);
firstVt.put(value.charAt(0), fvt);
Set<Character> lvt = new HashSet<>();
getLastVT(value.charAt(0), lvt);
lastVt.put(value.charAt(0), lvt);
}
displaySet("firstVt", firstVt);
displaySet("lastVt", lastVt);
}
//输出
private static void displaySet(String setName, Map<Character, Set<Character>> set) {
System.out.printf("%s集合如下:\n", setName);
for (Map.Entry<Character, Set<Character>> entry : set.entrySet()) {
String valueString = String.join(",", entry.getValue().stream().map(String::valueOf).collect(Collectors.toList()));
System.out.printf("%s(%s): {%s}\n", setName, entry.getKey(), valueString);
}
}
/**
* 错误
*/
public static void partError() {
matrix.put(")(", 'b');
matrix.put("((", 'b');
matrix.put("(#", 'a');
}
//TODO 构造算符优先矩阵并打印
/**
* 构造算符优先矩阵并打印
* 用Map<String,Character>存,String中存优先变得行值和列值,
* Character表示String中所存行列的大小关系如"++"表示行为'+',列为'+'的时候,关系为Character中的值
* 遍历文法,根据文法规则构造算符优先矩阵
* 结果存储在matrix集合中
*/
private static void constructMatrix() {
for (int i = 0; i < input.size(); i++) {
String str = input.get(i);
for (int j = 2; j < input.get(i).length(); j++) {
//每个产生式的字符,判断是否为终结符并且不是竖线('|')
if ((str.charAt(j) < 65 || str.charAt(j) > 90) && (str.charAt(j) != '|')) {
//当前字符后面有一个字符,并且该字符也是终结符,并且不是竖线('|'),将当前字符和后面的字符作为键,
// 将等于号('=')作为值,存入matrix中。P->...ab....
if (j < str.length() - 1
&& (str.charAt(j + 1) < 65 || str.charAt(j + 1) > 90)
&& (str.charAt(j + 1) != '|')
) {
String temp = str.charAt(j) + "" + str.charAt(j + 1);
matrix.put(temp, '=');
} else {
//当前字符后面有两个字符,并且第二个字符也是非终结符,并且不是竖线('|'),将当前字符和第二个字符作为键,
//将等于号('=')作为值,存入matrix中。
//如 P->....aBc
if (j < str.length() - 2
&& (str.charAt(j + 2) < 65 || str.charAt(j + 2) > 90)
&& (str.charAt(j + 2) != '|')
) {
matrix.put(str.charAt(j) + "" + str.charAt(j + 2), '=');
}
}
//当前字符后面有一个字符,并且该字符是非终结符,将该字符的firstVt集合中的每个元素与当前字符作为键,
// 将小于号('<')作为值,存入matrix中。 如 +T 则有 + < FirstV(T)
if (j < str.length() - 1 && str.charAt(j + 1) >= 65 && str.charAt(j + 1) <= 90) {
Set<Character> coll = firstVt.get(str.charAt(j + 1));
for (Character value : coll) {
//logger.info("创建矩阵测试2:"+str.charAt(j+1) + ":" + value);
matrix.put(str.charAt(j) + "" + value, '<');
}
}
//当前字符前面不是产生式的起始符号,并且当前字符前面的字符是非终结符,将该字符的lastVt集合中的每个元素与当前字符作为键,
// 将大于号('>')作为值,存入matrix中。 如 E+ 则有 LastV(E) > +
if (j - 1 != 1 && str.charAt(j - 1) >= 65 && str.charAt(j - 1) <= 90) {
Set<Character> coll = lastVt.get(str.charAt(j - 1));
for (Character value : coll) {
matrix.put(value + "" + str.charAt(j), '>');
}
}
}
}
}
//起始符号的firstVt集合中的每个元素与'#'作为键,将小于号('<')作为值,存入matrix中。
Set<Character> coll = firstVt.get(input.get(0).charAt(0));
for (Character value : coll) {
matrix.put('#' + "" + value, '<');
}
//起始符号的lastVt集合中的每个元素与'#'作为键,将大于号('>')作为值,存入matrix中。
Set<Character> coll1 = lastVt.get(input.get(0).charAt(0));
for (Character value : coll1) {
matrix.put(value + "" + '#', '>');
}
//处理部分错误情况
partError();
//遍历所有的终结符,并在matrix中找到对应的优先关系。
//如果没有找到对应的关系,则将键值对存入matrix中,其中值为' '。
for (Character value : End) {
for (Character value1 : End) {
matrix.putIfAbsent(value + "" + value1, ' ');
}
}
//将"##"作为键,将等于号('=')作为值,存入matrix中。
matrix.put("##", '=');
getVT();
//打印构造的算符优先矩阵
printMatrix();
}
/**
* 打印算符优先矩阵
* 遍历终结符,输出构造的算符优先矩阵
*/
private static void printMatrix(){
System.out.println("\n构造的算符优先关系表如下:");
int kong = 0;
for (Character value : End) {
if (kong == 0) {
System.out.print(" ");
}
kong++;
System.out.print(value);
if (kong != End.size()) {
System.out.print(" ");
}
}
System.out.println();
for (Character value : End) {
System.out.print(value);
for (Character value1 : End) {
Character ch = matrix.get(value + "" + value1);
if (ch != null) {
System.out.print(" " + ch);
} else {
System.out.print(" " + " ");
}
}
System.out.println();
}
}
/**
* 判断其是不是终结符
*/
private static boolean isEnd(Character ch) {
for (Character value : End) {
if (value.equals(ch)) {
return true;
}
}
return false;
}
/**
* 判断其是不是非终结符
*/
private static boolean isNoEnd(Character ch) {
for (Character value : NoEnd) {
if (value.equals(ch)) {
return true;
}
}
return false;
}
/**
* 根据产生式右部分返回左边
*/
private static char retLeft(String str) {
char ch;
// 遍历文法的产生式
for (Map.Entry<Character, List<String>> map : produce.entrySet()) {
ch = map.getKey();
// 遍历每个产生式的右部
for (String value : map.getValue()) {
// 检查右部的长度是否与给定的字符串相同
if (value.length() != str.length()) {
continue;
}
int i;
for (i = 0; i < str.length(); i++) {// 比较每个字符
if (str.charAt(i) >= 65 && str.charAt(i) <= 90) {// 如果是非终结符
if (value.charAt(i) >= 65 && value.charAt(i) <= 90) {// 都是非终结符,继续比较
} else {
break;// 产生式与给定字符串不匹配,跳出循环
}
} else {
// 如果是终结符,直接比较字符
if (value.charAt(i) != str.charAt(i)) {
break; // 产生式与给定字符串不匹配,跳出循环
}
}
}
if (i == str.length()) {
return ch;// 如果遍历完成,说明匹配成功,返回左部非终结符
}
}
}
return 0;// 没有匹配的产生式,返回0
}
//TODO 将字符数组转换成字符串
/**
* 将字符数组转换成字符串
* @param list 字符列表
* @return 对应的字符串
*/
public static String replaceToString(List<Character> list) {
StringBuffer a = new StringBuffer();
for (Character value : list) {
if (value != ',' && value != '[' && value != ']') {
a.append(value);
}
}
return a.toString();
}
//TODO 分析过程
/**
* 算符优先分析过程
* 使用一个符号栈,用它寄存终结符和非终结符,k代表符号栈的深度
* 在正常情况下,算法工作完毕时,符号栈S应呈现:#N
*/
public static void analysisProcess() {
int status = 0;// 操作类型标志,0表示移进,1表示规约
int count = 0;// 计数器,记录分析步骤
int k = 0;//符号栈深度的指针,用于标记在符号栈中的当前位置
int j = 0;// 回溯指针,用于在规约过程中找到前一个终结符
int step = 0;//计步器,用于记录当前处理到句子的哪个字符
String gui = null;//字符串,用于存储规约时的显示信息
System.out.println("请输入要分析的句子");
//输入的句子,需要被分析的字符串
String analyze;
analyze = in.nextLine();
if (analyze.charAt(analyze.length() - 1) != '#') {
analyze = analyze + "#";
}
/* 符号栈的初始化,符号栈用于存储待处理的符号,初始时栈底为'#' */
//符号栈,用于存储待处理的符号。它是一个字符的列表,可以存储输入字符串中的字符和非终结符。
List<Character> listStack = new ArrayList<>();
//格式化字符串"%-8s%-20s%-8s%-10s%-8s\n"中的每个%-Ns表示一个左对齐的字符串,其中N是该字符串的最小宽度
System.out.printf("%-8s%-20s%-8s%-10s%-8s\n", "步骤", "栈", "a读入", "剩余串", "操作");
//栈底 #
listStack.add('#');
//当前正在处理的字符
char a = analyze.charAt(step++);
/* 分析过程主体 */
do {
if (status == 0) {
if (count != 0) {
System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", "移进", count,
replaceToString(listStack), a, analyze.substring(step)
);
} else {
System.out.printf("%-8d %-20s %-8c %-10s", count,
replaceToString(listStack), a, analyze.substring(step)
);
}
} else {
System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", gui, count,
replaceToString(listStack), a, analyze.substring(step)
);
}
char ch = listStack.get(k);
if (isEnd(ch)) {
j = k;
} else if (j >= 1) {
j = k - 1;
}
char temp;
if (matrix.get(listStack.get(j) + "" + a) != null) {
//规约
//检查符号栈顶的字符和当前输入字符之间的优先关系。如果栈顶的符号优先级高,那么执行规约操作
while (matrix.get(listStack.get(j) + "" + a).equals('>')) {
if (listStack.size() == 2 && a == '#') {
//#E这种情况
break;
}
StringBuffer judge = new StringBuffer();
do {
temp = listStack.get(j);
if (isEnd(listStack.get(j - 1))) {
j = j - 1;
} else {
j = j - 2;
}
} while (!matrix.get(listStack.get(j) + "" + temp).equals('<'));
for (int i = j + 1; i < listStack.size(); i++) {
judge.append(listStack.get(i));
}
int te = listStack.size();
for (int t = j + 1; t < te; t++) {
listStack.remove(j + 1);
}
//将符号栈中的一些字符替换为一个非终结符
char res = retLeft(judge.toString());
if (res != 0) {
count++;
k = j + 1;
listStack.add(res);
status = 1;
gui = "用" + res + "->" + judge + "规约";
if (status == 0) {
System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", "移进", count,
replaceToString(listStack), a, analyze.substring(step)
);
} else {
System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", gui, count,
replaceToString(listStack), a, analyze.substring(step)
);
}
}
}
}
//移进---将当前输入字符'a'添加到符号栈中。
if (matrix.get(listStack.get(j) + "" + a).equals('<')
|| matrix.get(listStack.get(j) + "" + a).equals('=')
) {
count++;
k++;
status = 0;
listStack.add(a);
}else{
//输入字符'a'和符号栈顶的字符不能形成有效的优先关系,那么输出错误信息并退出程序。
switch (matrix.get(listStack.get(j) + "" + a)){
case 'a':
System.out.print("非法左括号! ");
return;
case 'b':
System.out.print("缺少运算符! ");
return;
case 'c':
System.out.print("缺少表达式! ");
return;
default:
break;
}
}
if (listStack.size() == 2 && a == '#') {
break;
}
if (step < analyze.length()) {
a = analyze.charAt(step++);
} else {
break;
}
} while (listStack.size() != 2 || a != '#');
System.out.printf("%-8s\n", "分析成功");
}
}
五、测试结果
1. LL(1)文法:
(1)输入文件:
图5-1-1 LL(1)文法的输入文件
(2)分析结果:
图5-1-2 LL(1)文法的分析结果
2. 算符优先文法:
(1)输入文件:
图5-1-4 算符优先文法的输入文件
(2)分析结果:
图5-1-5 算符优先文法的分析结果