上下文无关文法的表示与存储(Java描述)
【问题描述】
把输入的文法存储在计算机内。
【基本要求】
1、输入上下文无关文法的一组产生式。
2、将文法按顺序或链式结构存储在计算机内。
3、输出文法的四要素:终极符集合、非终极符集合、规则式集合和开始符。
4、开始符在输入时指明,否则将所输入第一条规则式的左部符号视为开始符。
【数据结构】
1、构建文法类
文法四要素:{ 终极符集合、非终极符集合、规则式集合和开始符 }
public class WenFa {
char BeginSymbol; //文法的开始符
StringBuffer terminator; //终结符
StringBuffer nonterminator; //非终结符
ArrayList<startNode> production; //文法产生式存储入口
//构造方法 //初始化
WenFa(){
terminator = new StringBuffer("");
nonterminator = new StringBuffer("");
}
}
2、构建文法存储数据结构:数组 + 链表
在这里事先定义两个类,表示存储结构中数组中的节点和链表中的节点。
(1)存储文法每个产生式左边非终结符的节点startNode
class startNode{
char val;
Node next;
//构造函数
startNode(char c){
val = c;
next = null;
}
//重写toString()方法
public String toString(){
return val+"";
}
}
(2)存储文法产生式右边字符的节点Node
class Node{
char val;
Node next1; //此产生式的本方案的右侧符号
Node next2; //此产生式的下一替换方案的首符号节点
Node(char c){
val = c;
next1 = null;
next2 = null;
}
//重写toString()方法
public String toString(){
return val+"";
}
}
例如现有产生式:E—>E+T|T,“—>”左端的E存储在一个startNode节点s中,s节点的next指向一个Node节点node,此时node.val=‘E’,就是“—>”右边的第一个符号。
E—>E+T|T 可以看作E—>E+T和E—>T两个方案
而node有两个成员变量next1和next2,node.next1指向本方案E—>E+T的右侧符号,即就是存储‘+’的节点,而node.next2指向下一替换方案E—>T的首符号节点,但是此时本方案还未结束,所以用node.next2=null来表示。
依此我们可以得到存储"E—>E+T"中‘E’和‘+’的节点的next2均为null,当第一个方案E—>E+T结束时,会遇到‘|’,此时令存储‘T’的节点的next1为null,令此节点的next2指向下一方案E—>T的首节点‘T’。
E—>E+T|T 具体存储结构如下图:
【主函数】
1、新建文法对象;建立ArrayList以存储产生式首字符
2、逐行读取输入的文法产生式,以“-1”结束
3、在读入第一行产生式时定义文法的开始符
4、读取一行产生式后将“—>”左边的字符加入文法的非终结符中;将“—>”右边的字符加入文法的终结符中
5、对产生式按行存储,构建数据结构
6、文法产生式读入完毕后,对终结符字符串进行处理:包括去重和去除非终结符
7、输出文法内容
import java.util.*;
public class first {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
WenFa wenfa = new WenFa(); //新建一个文法对象
ArrayList<startNode> arr = new ArrayList<>(); //存储非终结符的列表
wenfa.production = arr; //定义文法产生式
int p=0;
while(!scanner.hasNext("-1")){
String inRule = scanner.nextLine();
char[] rule = inRule.toCharArray();
if(p==0){
wenfa.BeginSymbol = rule[0]; //在读入第一行产生式时定义开始符
p=1;
}
wenfa.nonterminator.append(rule[0]); //文法中的非终结符
startNode start = new startNode(rule[0]);
arr.add(start); //向数组中加入非终结符
//构建数据结构
Node node = new Node(rule[3]); //箭头右边的第一个字符
start.next = node;
int jud=0,I=4; //从产生式第四个字符开始
while(I<rule.length){
if(rule[I]!='|' && jud==0 && I<rule.length){
node.next1 = new Node(rule[I]);
node = node.next1;
I++;
}
if(I<rule.length){ //避免数组越界异常
if(rule[I]=='|'){ //检测到另一种规则开始
jud=1;
I++;
}
}
if(jud==1 && I<rule.length){ //另一种规则,使用next2
node.next2 = new Node(rule[I]);
node = node.next2;
jud=0;
I++;
}
}
//数据结构构建完毕
//构造文法
int jud1=1;
for(int i=3;i<rule.length;i++){
char[] ch = wenfa.nonterminator.toString().toCharArray();
if(rule[i] != '|'){
for(int j=0;j<ch.length;j++){
if(ch[j] == rule[i])
jud1=0;
}
if(jud1==1)
wenfa.terminator.append(rule[i]); //文法中的终结符
jud1=1;
}
}
} //while结束
//字符串去重
//(对存储文法中终结符的字符串去重),利用集合元素的不重复性
wenfa.terminator = methods.StringNoRepeat(wenfa.terminator);
//生成最终准确的终结符集合,从终结符集合中移除非终结符
methods.RemoveAFromB(wenfa.terminator, wenfa.nonterminator);
//打印文法内容
methods.printWenFa(wenfa);
} //主函数结束
}
【相关方法】
程序对主函数中用到的特殊方法进行了封装,方法构造过程中数据结构较简单,思路比较简陋,欢迎大家对错误不足之处提出指正,也欢迎大家提供更精巧的方法。
import java.util.HashSet;
public class methods {
//打印节点数据方法(访问数组链表数据结构)
public static void printOut(Node node){
Node far = node;
while(node!=null){
System.out.print(node);
far = node;
node = node.next1;
}
if(far.next2!=null){
System.out.print('|');
printOut(far.next2); //递归调用
}
}
//打印出文法相关内容
public static void printWenFa(WenFa wenfa){
System.out.println("\n\n{ 终结符:"+wenfa.terminator+"\n非终结符:"+wenfa.nonterminator+"\n产生式:");
int tab = 0; //控制产生式输出时首行换行
for(startNode ter : wenfa.production){
if(tab == 1)
System.out.println();
tab=1;
System.out.print(ter+" -> ");
if(ter.next!=null){
methods.printOut(ter.next); //调用输出方法
}
}
System.out.println("\n开始符:"+wenfa.BeginSymbol+" }");
}
//对字符串(StringBuffer)去重,返回字符串(StringBuffer)
//利用集合元素的不可重复性
public static StringBuffer StringNoRepeat(StringBuffer sb){
char[] c = sb.toString().toCharArray();
int delnumm = 0; //调整字符串删除字符后的参数,确保移除字符正确
HashSet hash = new HashSet();
for(int i=0;i<c.length;i++){
if(!hash.add(c[i])){
sb.deleteCharAt(i-delnumm);
delnumm++;
}
}
return sb;
}
//移除字符串A中与字符串B中含有的相同的字符
public static void RemoveAFromB(StringBuffer A,StringBuffer B){
char[] a = A.toString().toCharArray();
char[] b = B.toString().toCharArray();
int delnum=0; //调整字符串删除字符后的参数,确保移除字符正确
for(int i=0;i<a.length;i++){
for(int j=0;j<b.length;j++){
if(a[i]==b[j]){
A.deleteCharAt(i-delnum);
delnum++;
}
}
}
} //end
}
【程序演示】
由控制台输入文法产生式:
/*
*
E->E+T|T
T->T*F|F
F->(E)|i
*
*/
控制台输出文法四要素{ 终极符集合、非终极符集合、规则式集合和开始符 }:
{ 终结符:+*()i
非终结符:ETF
产生式:
E -> E+T|T
T -> T*F|F
F -> (E)|i
开始符:E }
写在最后:
在学习编译原理课程并完成实验过程中,由于缺少Java代码的参考,便尝试用Java完成实验内容。
由于本人水平有限,数据结构设计及算法实现不可避免会有漏洞或不足之处。欢迎大家指出思路和代码中的不足,也期待大佬分享更简洁精巧的代码实现。
欢迎大家转载!(注明出处 嘻嘻)