编译原理实验一(上下文无关文法的表示与存储)

上下文无关文法的表示与存储(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张图片
【主函数】
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完成实验内容。

由于本人水平有限,数据结构设计及算法实现不可避免会有漏洞或不足之处。欢迎大家指出思路和代码中的不足,也期待大佬分享更简洁精巧的代码实现。

欢迎大家转载!(注明出处 嘻嘻)

你可能感兴趣的:(编译原理,编译原理,实验,Java,上下文无关文法)