语法分析器的任务在于,对词法分析器识别出的单词符号串进行识别分析,如程序中的表达式、说明语句等各类型语句,对于语法分析主要采取的是上下文无关文法进行描述的,且主要分为自上而下的语法分析以及自下而上的语法分析,而在自上而下的语法分析中存在回溯现象,为了避免回溯,在自上而下的语法分析方法中,主要采用LL(1)语法分析方法,LL(1)分析方法的主要特点为:文法不含左递归,文法中每一个非终结符的各个产生式的候选首符集两两不相交,对输入的字符,都能精确的选取候选式进行工作,进而避免回溯。
LL(1)预测分析器由分析表,符号栈,和总控程序组成。为了方便使用分析器,本程序通过从程序的主界面选择txt文本读取文法的形式,自动生成分析表,在分析表的界面,可以输入句子,判断句子符不符合该文法,并出现提示。使用者需要把文法写入到某个txt文本中,且要注意格式,格式不对会提示。本程序不提供LL(1)文法判断功能,所以使用者要自己判断所选择的文法是不是LL(1)文法,另外对文法格式也有要求,一个非终结符号的产生式,只能写在一行,如E->+TE|-TE,而不能分成E->+TE,E->-TE,且非终结符号只能为一个字符,即不能出现诸如E'的形式,该形式为两个字符。
预测分析器用Java语言编写,界面分为文件选择界面,分析表界面。为了构造分析表,加入了求解文法的first集合和follow集合的模块。除了图形界面,在控制台,程序还可以打印文法follow集合和first集合,以及分析表的预测分析步骤
软件:应用eclipse集成开发工具,用Java语言编写预测分析器。
硬件:Intel CPU 2950m
操作系统:WIN10
3.2.1 Java编程语言
Java 是面向对象程序设计语言。Java 语言具有以下特性:简单性高效、面向对象、平台无关性、交互式特性、多线程机制、动态内存管理机制等多种特性。这些特性为程序员编写高质量的程序提供保障,也是 Java 在大数据时代和移动互联网时代能处于主流地位的基础。
3.2.2 Swing开发工具包
Swing是一个用于开发java应用程序用户界面的开发工具包,通过Swing工具包能够快速设计、编写java应用程序的用户界面,利用该开发工具包来设计、开发G文法的递归下降分析器的用户界面。
根据文法规则,生成每个非终结符的Follow集,Firs集。通过两个集合构造预测分析表。总控程序,根据分析表,输入串的当前符号,符号栈栈顶符号决定执行的动作。
求FIRST集的算法思想:
如果产生式右部第一个字符为终结符,则将其计入左部first集,如果产生式右部第一个字符为非终结符,求该非终结符的first集,将该非终结符的非εfirst集计入左部的first集,若存在ε,则将指向产生式的指针右移,若不存在ε,则停止遍历该产生式,进入下一个产生式,若已经到达产生式的最右部的非终结符,则将ε加入左部的first集,处理数组中重复的first集中的终结符。
求FOLLOW集的算法思想:
对于文法G的每个终结符A构造Follow(A)的办法是,连续使用下面的规则,直至每个Follow不在增大为止。
(1)对于文法的开始符号S,置#于Follow(S)中
(2)若 A->aBb是一个产生式,则把First(b)\{ε}加至Follow(B)中
(3)若A->aB是一个产生式,或A->aBb是一个产生式而b→ε,则把Follow(A)加至Follow(B)中。
构造分析表M的算法是:
(1) 对文法G的每个产生式A->a执行第二步和第三步;
(2) 对每个终结符a∈FIRST(a),把A->a加至M[A,a]中;
(3) 若ε∈FIRST(a),则把任何b∈FOLLOW(A)把A->a加至M[A,b]中;
(4) 把所有无定义的M[A,a]标上出错标志。
图 1 模块结构图
程序打开时的界面,功能主要是导入存放文法的txt文件,并将文件构造成文法,调用其他的类生成其First集,Follow集,再根据这两个集合构造分析表。把表传入分析表界面将其显示出来。
操作过程如下图:
图 2 主界面
图 3 选择文件界面
图 4 显示打开的内容
图 5 分析表界面
图 6 输入句子
图 7控制台显示的两个集合
图 8 控制台显示的分析步骤
分析表构造,首先要求出文法的First集和Follow集。由于Follow集需要求解First集,故而设计时Follow类中带有First集合。因此,生成表时只需传入Follow集。
下面给出求解文法First集的流程图。
图 9 求解文法First集的流程图
以下是句子分析功能实现的详细代码:
public String analyseSentence(String sentence){
ConsoleTable t = new ConsoleTable(4, true);
t.appendRow();
t.appendColum("------步 骤 ------").appendColum("--符号栈--------").appendColum("输入---------").appendColum("所用产生式");
sentence+="#";
TableCell ts=null;
Stack stack=new Stack();
stack.push('#');
int index=0;
stack.push(rules.getStart());
Character temp=null;
boolean flag=true;
int count=0;
//输出信息
StringBuffer stackString=new StringBuffer();
for(int i=0;i=0;i--){
stack.push(ts.getRuleRight().charAt(i));
}
}
//输出信息
stackString=new StringBuffer();
for(int i=0;i"+ts.getRuleRight());
// System.out.println(count+" "+stackString+" "+sentence.substring(index)+ts.getRuleLeft()+"->"+ts.getRuleRight());
}else{
return "ERROR";
}
}
System.out.println(t.toString());
return "SUCCESS";
}
其算法如下
对于栈顶符号X和当前的输入符号,总控程序每次都执行下述三种可能的动作。
(1)若X=a=‘#’,则宣布分析成功,停止分析。
(2)X=a!=‘#’,则把X弹出栈顶,让a指向下一个符号。
(3)若X是一个非终结符,则查看分析表M。若M[A,a]中存放着关于X的一个产生式,那么,首先把X弹出栈顶,然后把产生式右部反序压入栈中,若右部符号为ε,则不用推什么东西进栈。把产生式的右部符号压入栈中时,同时做这个产生式相应的语义动作。M[A,a]存放出错标志,则报错,停止分析。
对于文法G,产生规则如下,使用设计表里的用例测试,结果如下图。
B->*FB|ε
T->FB
E->TA
F->(E)|i
A->+TA|ε
表1 用例设计表
类别 |
用例 |
合法性 |
i+i+i |
(i+i)*(i+i) |
|
i |
|
非法性 |
i+*i |
i*(i |
|
7 |
图 10 输入i+i+i,分析
图 11 输入(i+i)*(i+i),分析
图 12 输入i,分析
图 13 输入i+*i,分析
图 2 输入i*(i,分析
图 3 输入7,分析
整个程序参考了在网上找到的一个程序的源码。其中First集是自己做的,Follow集、构造分析表和分析句子模块参考了别人的代码。求解Follow集时重要的是消去递归中的死循环,构造分析表时要考虑填入哪个产生式。