C语言的语法分析器——java实现

编译原理语法分析的实验已经是上上周的事了,可是今天才得以更新博客,其原因必然是一直木有做完,唉想想就伤感。。我这两周的大部分青春都献给了编译原理,虽然花了好长时间,可说实话写这篇博文心里还是十分没底,因为到最后还是没有一个令自己满意的结果,特别愧疚,实验报告交的都是最基础的课件上的例子。但是已经拖了两周了,第三次实验又浩浩荡荡的袭来,唉水平有限,力不从心,老了啊。。没办法分数还得说的过去,昨天找老师检查了一下,先做到这个程度吧,后面需要学习的地方还有很多,可能能顿悟呢也说不定哦~有时间再回头修改吧~

上次实验用的是C,所以必然要通过读文件的方式来获取上次的结果,先把过程缕一下哈~


上次实验是词法分析,使用C程序将单词分开,忽略空格和注释,还有头文件(因为语法分析不想处理啦~),然后输出该单词,以及token(种别码,入口地址),当然有些没有入口地址,常量和变量的符号表也保存在文件中,并给以编号(即入口地址)。重复的常量会剔除,而重复的变量并没有(因为可能作用域不同,符号表的内容也不一定一样)。根据词法分析的结果,可以进行语法分析,即通过读入token串(相当于在词法分析识别了单词的含义),根据给定的文法进行产生式的推导。为了方便读入,我将之前的token文件只保留了种别码,并在末尾手动加上了结束符号100(种别码在词法分析的头文件里宏定义了,没有100,固选择100,之所以是数字因为是终结符,语法分析的程序通过判断是不是数字来判断是不是终结符)。我采用了自顶向下的分析方法,对文法的要求是LL1文法,要求同一非终结符的产生式的select集互不相交,这样才不会产生歧义导致不知道选择哪个产生式,其实单纯根据select集也可以分析出来,只不过通过预测分析表效率更高些,预测分析表也是通过Select集来的。所以求select集是很关键的,求出来就能分析了,有错的话可能推导不下去,错误处理待会儿再说。


求select集的过程中需要用到follow集和first集,算法如下:







有两点需要注意:

1、注意分清first(X)和first(α)的区别。X表示一个符号,而α表示一串符号,求Select集的时候用到的是求一串符号的first集,而中间会用到求一个符号的first集,两种算法分别如下:

/**
	 * 求一个符号的First集
	 * @param X
	 * @param formulas
	 * @return 
	 */
	public static HashSet<Integer> getFirst(String X, ArrayList<Formula> formulas)
	{
//		System.out.println("getFirst:"+X);
		HashSet<Integer> first = new HashSet<Integer>();
		if(isNumeric(X))//如果是终结符
		{
			first.add(Integer.parseInt(X));
			return first;
		}
		String tmp;
		for(Formula f: formulas)
		{
			int i=0;
			if(X.equals(f.getLeft()))
			{
				if(i==f.getRight().size())
				{
					return first;
				}
				tmp = f.getRight().get(i);
				if(isNumeric(tmp))
				{
					first.add(Integer.parseInt(tmp));
					//不能return
				}
				else if(!tmp.equals("@"))
				{
					while(i<f.getRight().size())
					{
						if(!f.getRight().get(i).equals(X))//避免死循环
						{
							first.addAll(getFirst(f.getRight().get(i), formulas));//递归调用getFirst
						}
						if(!canNull(f.getRight().get(i++), formulas))
						{
							break;
						}
					}
				}
			}
		}
		return first;
	}

	/**
	 * 求一串的first集
	 * @param right
	 * @param formulas
	 * @return
	 */
	public static HashSet<Integer> getFirsts(ArrayList<String> right, ArrayList<Formula> formulas)
	{
		HashSet<Integer> first = new HashSet<Integer>();
		first.addAll(getFirst(right.get(0), formulas));
		int k=0;
		while(canNull(right.get(k), formulas) && k<right.size()-1)
		{
			first.addAll(getFirst(right.get(k+1), formulas));
			k++;
		}
		return first;
	}

2、如果采用递归求follow集的话可能出现死循环。先说一层的,比如A-->B A,那么求A的follow集会求左部(还是A)的follow集,这样产生死循环,在此我们可以加上判断,当二者不相等的时候在求,算法如下(但仍面临问题):

/**
	 * 获得Follow集  使用了递归
	 * @param X
	 * @param formulas
	 * @return
	 */
	public static HashSet<Integer> getFollow(String X, ArrayList<Formula> formulas)
	{
//		System.out.println("getFollow:"+X);
		HashSet<Integer> follow = new HashSet<Integer>();	
		ArrayList<String> right = null;
		if(X.equals(LL1.start))//开始符号也可能死循环
		{
			follow.add(Integer.parseInt(LL1.end));
		}
		for(Formula f: formulas)
		{
			right = f.getRight();
			for(int i=0; i<right.size(); i++)
			{
				if(X.equals(right.get(i)))
				{
					//后面没有符号串
					if(i==right.size()-1)
					{
						if(!f.getLeft().equals(X))
						{
							follow.addAll(getFollow(f.getLeft(), formulas));
							return follow;//右部后面没有符号,直接return
						}
					}
					else
					{
						if(isNumeric(right.get(i+1)))
						{
							follow.add(Integer.parseInt(right.get(i+1)));
							continue;
						}
						follow.addAll(getFirst(right.get(i+1), formulas));
						int j;
//						判断后面的符号串是否都能推出空
						for(j=i+1; j<right.size() && canNull(right.get(j), formulas); j++);
						if(j == right.size())
						{
							follow.addAll(getFollow(f.getLeft(), formulas));
						}
					}
				}
			}
		}
		return follow;
	}


但是这只是一层,文法我们很难用肉眼看出来,如果出现求A的follow集需要求B的follow集,而求B的follow集又要求A的follow集,这样的循环恐怕递归不好判断吧。没有想到合适的方法,固在主程序中增加了follow集的集合,由于没有给终结符和非终结符编号,采用HashMap实现,注意follow集要先初始化,代码简单就不贴了,算法如下:

/**
	 * 非递归求follow集
	 */
	static void setFollows()
	{
		boolean changes;
		boolean flag;
		int sizeBefore;
		int sizeAfter;
		ArrayList<String> beta;
		HashSet<Integer> tmp = new HashSet<Integer>();
		do
		{
			changes = false;
			for(Formula f: formulas)
			{
				beta = (ArrayList<String>) f.getRight().clone();//beta不能修改原来的right
				for(String right: f.getRight())
				{
					beta.remove(0);
					if(nonTerminal.contains(right))
					{
//						System.out.println("right+"+right);
	
						if(follows.get(right).isEmpty())
						{
							sizeBefore = 0;
						}
						else
						{
							sizeBefore = follows.get(right).size();
						}
						if(beta.size()!=0)
						{
							tmp = Util.getFirsts(beta, formulas);
//							flag = tmp.remove("@");
							int x;
							for(x=0; x<beta.size() && Util.canNull(beta.get(x), formulas); x++);
							if(x==beta.size())
							{
								flag = true;
							}
							else
							{
								flag = false;
							}
							follows.get(right).addAll(tmp);
						}
						else
						{
							flag = true;
						}

						if(flag && !right.equals(f.getLeft()))
						{
							follows.get(right).addAll(follows.get(f.getLeft()));
						}
						sizeAfter = follows.get(right).size();
						if(sizeBefore!=sizeAfter)
						{
							changes = true;
						}
					}
				}
			}
		}while(changes);
	}

接下来就可以求select集了,我的程序还有一个Formula的产生式的类,数据结构如下:

String left;
	ArrayList<String> right = new ArrayList<String>();
	HashSet<Integer> select = new HashSet<Integer>();

保存左部,右部和select集,另外有一个格式化输出函数和判断和另一个产生式的右部是否相同(作用稍后提到)。
之前根据递归的求follow集的方法设置select集,有死循环出现,所以改用内存中保存的follow集的集合来设置,Select集的算法如下:

/**根据保存的follow集来设置select的值
	 * 
	 */
	static void setSelectFromFollow()
	{
		HashSet<Integer> select = new HashSet<Integer>();
		int i;
		for(Formula f: formulas)
		{
			if(f.getRight().get(0).equals("@"))
			{
				select.addAll(follows.get(f.getLeft()));
			}
			else
			{
				select.addAll(Util.getFirsts(f.getRight(), formulas));
				for(i=0; i<f.getRight().size() && Util.canNull(f.getRight().get(i), formulas); i++);
				if(i==f.getRight().size())
				{
					select.addAll(follows.get(f.getLeft()));
				}
			}
			f.setSelect((HashSet<Integer>) select.clone());
			select.clear();
		}
	}
然后将select集填入预测分析表,空的部分采用一些启发式规则选择填写error或者同步记号,本程序将follow集作为同步记号,置为-2,error空项置为-1,有产生式的表中内容为产生式的编号。算法如下:

/**
	 * 建立预测分析表,不能基于左部都在一起,因为文法变换后顺序不定
	 */
	static void fillPredictMap()
	{
		Iterator<Integer> terIter = terminal.iterator();
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
		Formula f;
		Integer terInt;
		for(int i=0; i<formulas.size(); i++)
		{
			f = formulas.get(i);
			while(terIter.hasNext())
			{
				terInt = terIter.next();
				if(f.getSelect().contains(terInt))
				{
					map.put(terInt, i);
					if(predictMap.get(f.getLeft())==null)//没有该终非结符的表,建立
					{
						predictMap.put(f.getLeft(), (HashMap<Integer, Integer>) map.clone());
					}
					else//有则直接get再添加map
					{
						predictMap.get(f.getLeft()).put(terInt, i);
					}
					map.clear();
				}
			}
			terIter = terminal.iterator();//迭代器复位
		}
		//填synch和error项
		Iterator<String> nonIter = nonTerminal.iterator();
		String nonStr;
		while(nonIter.hasNext())
		{
			terIter = terminal.iterator();
			nonStr = nonIter.next();
			while(terIter.hasNext())
			{
				terInt = terIter.next();
				if(predictMap.get(nonStr)==null)
				{
				System.out.println("nonStr="+nonStr);
				}
				if(predictMap.get(nonStr).get(terInt)==null)
				{
					if(follows.get(nonStr).contains(terInt))
					{
						predictMap.get(nonStr).put(terInt, -2);
					}
					else
					{
						predictMap.get(nonStr).put(terInt, -1);
					}
				}
			}
		}
	}

之后就可以根据预测分析表进行推导了,输出推导的过程,即采用的不同产生式,推导过程如下(附算法):


/**
	 * 根据预测分析表推导,输出栈里的内容
	 */
	static void deriveFromTable()
	{
		int result;
		Formula f;
		while(!stack.isEmpty())
		{
			
//			System.out.println("stack peek: "+stack.peek()+"\t"+"input peek:"+input.peek());
			if(predictMap==null)
			{
				System.out.println("预测分析表为空");
			}
			else if(predictMap.get(stack.peek())==null)
			{
				System.out.println("表中"+stack.peek()+"为空");
			}
			else if(predictMap.get(stack.peek()).get(input.peek())==null)
			{
				System.out.println("表中表"+stack.peek()+"\t"+input.peek()+"为空");
			}
			result = predictMap.get(stack.peek()).get(input.peek());
			switch(result)
			{
			case -1:
				System.out.println(stack.peek()+" "+input.peek()+" error项,弹出输入符号:"+input.poll());
				break;
			case -2:
				System.out.println(stack.peek()+" "+input.peek()+" synch项,弹出栈顶元素:"+stack.pop());
				break;
				default:
					f = formulas.get(result);
					System.out.println(f.output());
					//将产生式左部出队列
					stack.pop();
					if(!f.getRight().get(0).equals("@"))
					{
						//将产生式右部入队列
						for(int i=(f.getRight().size()-1); i>=0; i--)
						{
							stack.push(f.getRight().get(i));
						}
					}
					while(!stack.isEmpty() && Util.isNumeric(stack.peek()))//不是if是while,加上非空判断
					{
						if( input.peek()==Integer.parseInt(stack.peek()))//不能是t,因为出栈后可能变化,是input.peek()
						{
							input.poll();
							stack.pop();
						}
					}
			}
		}
	}

大体思路就是这样,运行课件上的例子倒是简单,但是运行C语言的程序就稍微困难一些了。关键是C语言的文法比较复杂,之前写好了有各种左递归的回溯(下附左递归,回溯的消除方法),直接的消除还好,关键是有许多间接的左递归和回溯不容易看出来,给人工消除造成了很大的麻烦。其实两个星期以前就开始纠结于文法不符合LL1的要求,后来一直是在处理文法的问题。人工解决不行我们还有程序嘛~所以就写了消除直接左递归和直接回溯的算法,由于有间接左递归和回溯,固消除之后将右部第一个非终结符进行替换,在进行消除,知道产生式不再变化为止,之前写的基于相同的左部在一起的算法,但是由于产生式不断变化,左部相同的不一定在一起,后来写了新的算法,还有新加的产生式不能直接在左部后面加"'"之类的符号,这样会出现歧义。比如两个相同的E'所表达的含义其实不一定是一样的,所以用了int的静态变量给新的产生式编号,由于文法确定后会扫描文法根据是否是数字判断是不是终结符,所以该新非终结符左右各加入尖括号,变成这样“<数字>”,优化后的算法如下(还是先伪代码后代码实现):



	 * 消除直接左递归,左部相同的不一定在一起
	 */
	static void removeLeftRecursion()
	{
//		System.out.println("removeLeft called...");
		Formula fi;
		Formula fj;
		String left;
		boolean flag;
		ArrayList<String>  forNull = new ArrayList<String>();
		ArrayList<String> tmp = new ArrayList<String>();
		forNull.add("@");
		for(int i=formulas.size()-1; i>=0; i--)
		{
			fi = formulas.get(i);
			if(fi.getLeft().equals(fi.getRight().get(0)))
			{
				flag = true;
//				System.out.println(f.output());
				formulaChange = true;
				System.out.println("removeLeftRecursion true");
				left = fi.getLeft();
				fi.getRight().remove(0);
				fi.getRight().add("<"+number+">");
				fi.setLeft("<"+number+">");
				for(int j=formulas.size()-1; j>=0; j--)
				{
					fj = formulas.get(j);
					if(left.equals(fj.getLeft()))
					{
						if(left.equals(fj.getRight().get(0)))//左递归
						{
							fj.setLeft("<"+number+">");//加尖括号避免看成终结符
							fj.getRight().remove(0);
							fj.getRight().add("<"+number+">");
						}
						else
						{
							flag = false;//看是否都是左递归,进入此处表示有不是左递归的产生式
							if(fj.getRight().get(0).equals("@"))
							{
								fj.getRight().remove(0);
							}
							fj.getRight().add("<"+number+">");
						}
					}
				}
				formulas.add(new Formula("<"+number+">", (ArrayList<String>) forNull.clone()));
				if(flag)
				{
					tmp.add("<"+number+">");
					formulas.add(new Formula(left, (ArrayList<String>) tmp.clone()));
					tmp.clear();
				}
				number++;
			}
		}
	}
/**
	 * 消除直接回溯,提取左因子
	 */
	static void removeBack()
	{
//		System.out.println("removeBack called...");
		int k;
		boolean flag = false;
		ArrayList<String> right = new ArrayList<String>();
		do
		{
			flag = false;
			for(int i=formulas.size()-1; i>=0; i--)
			{
				Formula fi = formulas.get(i);
				for(int j=i-1; j>=0; j--)
				{
					Formula fj = formulas.get(j);
					if(fj.getRight().get(0).equals("@"))//空产生式略过
					{
						continue;
					}
					if(fj.getLeft().equals(fi.getLeft()) && Util.isNumeric(fi.getRight().get(0)))
					{
						for(k=0; k<fi.getRight().size()&&k<fj.getRight().size()&&
						fi.getRight().get(k).equals(fj.getRight().get(k)); k++);
						if(k>0)
						{
//							System.out.println("i="+i+":"+fi.output()+"   |||||    "+"j="+j+":"+fj.output());
							formulaChange = true;
//							System.out.println("removeBack true");
							for(int a=0; a<k; a++)
							{
								right.add(fi.getRight().get(0));
								fi.getRight().remove(0);
								fj.getRight().remove(0);
							}
							right.add("<"+number+">");
							formulas.add(new Formula(fi.getLeft(), (ArrayList<String>) right.clone()));
							right.clear();
							if(fi.getRight().isEmpty())
							{
								fi.getRight().add("@");
							}
							if(fj.getRight().isEmpty())
							{
								fj.getRight().add("@");
							}
							fi.setLeft("<"+number+">");
							fj.setLeft("<"+number+">");
							number++;
							flag = true;
							break;
						}
					}
				}
			}
		}while(flag);
	}

/**
	 * 将非终结符换位终结符代入,注意考虑空,此时不能有左递归,否则死循环,
	 * 新的产生式加在后面,逆向遍历
	 */
	static void substituteFormula()
	{
		Formula fi;
		Formula fj;
		ArrayList<String> right = new ArrayList<String>();
		ArrayList<String> beta = new ArrayList<String>();
		for(int i=formulas.size()-1; i>=0; i--)
		{
			fi = formulas.get(i);
			if(!Util.isNumeric(fi.getRight().get(0))&& !fi.getRight().get(0).equals("@")
					&& Util.isBeginWithTerminal(fi.getRight().get(0), formulas))
			{
//				System.out.println("substitute true..");
//				System.out.println(formulas.get(i).output());
				formulaChange = true;
				for(int j=formulas.size()-1; j>=0; j--)
				{
					fj = formulas.get(j);
					if(fj.getLeft().equals(fi.getRight().get(0)))
					{
						beta = (ArrayList<String>) fi.getRight().clone();
						beta.remove(0);
						right = (ArrayList<String>) fj.getRight().clone();
						right.addAll(beta);
						formulas.add(new Formula(fi.getLeft(), (ArrayList<String>) right.clone()));
					}
				}
				formulas.remove(i);
			}
		}
	}


这样实现下来还可能遇到意义相同但长得不一样的非终结符,即推出的产生式都是完全一样的。这样的非终结符可以合并,以免产生式太多不好debug。所以产生式的类里有一个判断右部是否相同的方法,如下:

	public boolean isRightSame(Formula f)
	{
		int k;
		if(f.getRight().size()==right.size())
		{
			for(k=0; k<right.size() && right.get(k).equals(f.getRight().get(k)); k++);
			if(k==right.size())
			{
				return true;
			}
		}
		return false;
	}


这样如果两个非终结符推出的产生式都完全一样,且个数相同(真子集也不能说明两个非终结符一样),就以一个为基础,将另一个的产生式删除,并且替换删掉的非终结符为基础的那个非终结符,算法如下:

/**
	 * 合并相同的产生式,即合并实质一样的非终结符
	 */
	static void mergeFormulas()
	{
		Formula f1;
		Formula f2;
		String f2Left;
		ArrayList<String> right;
		for(int i=0; i<formulas.size(); i++)
		{
			f1 = formulas.get(i);
			for(int j=i+1; j<formulas.size(); j++)
			{ 
				if(!f1.getLeft().equals(formulas.get(j).getLeft()))
				{
					f2 = formulas.get(j);
					if(Util.isNonTerSame(f1.getLeft(), f2.getLeft(),formulas))
					{
						f2Left = f2.getLeft();
						//遍历一遍删掉该非终结符推出的产生式,并将右部有该非终结符的替换为i的
						for(int k=0; k<formulas.size(); k++)
						{
							if(formulas.get(k).getLeft().equals(f2Left))
							{
								formulas.remove(k);
							}
							else
							{
								right = formulas.get(k).getRight();
								for(int m=0; m<right.size(); m++)
								{
									if(right.get(m).equals(f2Left))
									{
										right.set(m, f1.getLeft());
									}
								}
							}
						}
					}
				}
			}
		}
	}

这样我们新的文法就产生啦~!庆祝一下~!但是理论上是这样,事实上总是有差距的。。可能我程序有bug,还求大家帮忙看看~总之有的文法还是不能分析,或者一直死循环一直会有改变,所以我最后没办法才修改了文法,简化了许多许多许多。。多到我都不好意思贴出来了。。没有考虑运算符号的优先级,也没有考虑括号神马的。。唉。。时间就是生命啊。。不过话说回来,并不是所有文法都能转变为LL1的,这也可能是死循环的原因?额。。我不是故意推卸责任说自己算法木有问题的。。但是到底哪些能哪些不能呢?我还不太清楚额。。总之。。还是写了个算法判断是不是LL1文法,当然你开始判断一下是的话就万事大吉啦~什么都不用消除咯~算法如下:

/**
	 * 判断是否是LL1文法
	 * @return
	 */
	static boolean isLL1()
	{
		HashSet<Integer> tmp = new HashSet<Integer>();
		for(int i=0; i<formulas.size(); i++)
		{
			for(int j=i+1; j<formulas.size(); j++)
			{
				tmp = (HashSet<Integer>) formulas.get(i).getSelect().clone();
				tmp.retainAll(formulas.get(j).getSelect());
				if(formulas.get(i).getLeft().equals(formulas.get(j).getLeft())
						&& tmp.size()!=0 && !(formulas.get(i).getRight().get(0).equals("@")
								&&formulas.get(j).getRight().get(0).equals("@")))//可能产生相同的空产生式
				{
					System.out.println("not LL1:"+i+"\t"+j);
					return false;
				}
			}
		}
		return true;
	}

有了上面这些做基础,就有了主要的程序来调用他们~得让他们有用武之地嘛~我写了好多测试函数,来测试文法,follow集,first集,select集,预测分析表等等,测试代码就不贴啦~主程序(main函数中)的代码如下:

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		initFormulas(formulas);
//		System.out.println("从文件读入的初始文法如下:");
//		System.out.println("\n-------------------------------------------------------------------------------");
//		System.out.println("消除左递归的文法如下:");
//		System.out.println("\n-------------------------------------------------------------------------------");
//		System.out.println("右部无非终结符的文法如下:");
//		System.out.println("\n-------------------------------------------------------------------------------");
//		System.out.println("消除回溯的文法如下:");
//		removeLeftRecursion();
//		testFormulas();
		while(true)
		{
			formulaChange = false;
			removeBack();
			removeLeftRecursion();
			substituteFormula();
			mergeFormulas();
			if(!formulaChange)
			{
				break;
			}
			//不能在这判断是不是LL1文法,因为select集还没有确定
		}
		System.out.println("处理后的文法如下:");
		testFormulas();
		System.out.println("\n-------------------------------------------------------------------------------");
		fillSymbols();//要在文法确定之后再添加
		initFollows();
		setFollows();
		setSelectFromFollow();
		testSelect();
		System.out.println("预测分析表如下:");
		fillPredictMap();
		testPredictMap();
		//结束符号和开始符号入栈
		stack.push(end);
		stack.push(start);
		try {
			BufferedReader in = new BufferedReader(new FileReader(tokenName));
			String line;
			int i=0;
			while((line=in.readLine())!=null )
			{
				//输入进队列
				input.offer(Integer.parseInt(line));
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("推导过程如下:");
		deriveFromTable();
	}


最后遇到了好多问题,java也好久没用了。。(上次说C好久没有用不熟。。这次又说java。。请问您天天到底有木有编程啊!唉。。不吐槽了。。)我错了。。低级错误还是有的。。毕竟java还是我大学生涯的最低分啧啧。。还好谨记任大神语录——java都是指针好吗!其实不编理解还是不深刻啊。。那总结一下吧~

1、arraylist的赋值不能直接赋值,要clone或者构造函数中传参。虽然java都是指针,但是之前的函数跳出之后,内存会被回收。不用clone的话,即使是a = b;然后a.remove(0)之类的操作也把b给毁了。。本程序里b一般都是产生式。。亲~你把辛辛苦苦优化为LL1的产生式都毁了还编个毛毛呢~

2、类似这样的产生式E2->400T E2 会死循环,需要先判断,避免都是求E2的follow集而重复,如果递归时仍然是求E2的follow集则无需再求。这个其实只是判断了一层,归根到底应该还是不能用递归的,上文有说哦~看我这篇是上下文有关文法O(∩_∩)O哈哈~~开个玩笑。。

3、集合循环使用并给其他赋值,每次使用前要清空!因为每次都是调用add函数,不然后面的集合将包含之前的集合。

4、之前对求first集理解不深刻!再求select集的时候,注意first是对一串求,不是对一个非终结符求。而之前写的first算法是对某一个非终结符求first集,这个上面也说啦~!

5、终结符可能连续出栈,比如连着两个都是输入缓冲和栈顶匹配。不能用if判断,需要用While判断,并且需要用peek函数来判断。之前设置了变量保存栈顶和输入缓冲的队列头,但运行中不断变化,直接peek调用更加可靠。

6、考虑间接左递归和间接回溯!这是文法中最不容易看出来的地方,也是最最最最纠结的地方。。最终用程序实现了消除左递归。

7、程序修改后注意调用顺序,比如编程实现左递归之后,主程序还是先调用setSelect了,则不是对消除左递归之后的文法设置Select集,故分析出错。还有终结符,非终结符的集合填写也要在文法确定之后哦~改动太多也要注意函数的调用顺序呀~

8、左递归右部是空,不能当做β,要去掉。因为程序中用“@”表示空,也是字符串,判断时终结符是数字,而空面临当做非终结符处理的可能,需额外考虑。

9、对集合遍历时迭代器要归位啊,否则只能对第一次的遍历。

10、正则表达式没有检测-1,结束符也要是正数,设为100(种别码中没有,规定即可)附上正则表达式判断数字的函数吧~

/**
	 * 判断字符串是否是数字
	 * @param str
	 * @return
	 */
	public static boolean isNumeric(String str){ 
		   Pattern pattern = Pattern.compile("[0-9]*"); 
		   Matcher isNum = pattern.matcher(str);
		   if( !isNum.matches() ){
		       return false; 
		   } 
		   return true; 
		}

11、对于结束符号的理解没有到位。刚开始写文法按照报告的要求分开写的,这样非常清晰。先不说左递归和回溯问题,关于结束符号理解为表达式的是“;”,函数的是“}”这样,所以程序只能分析单独的一类文法。而最终分析的程序又好多语句构成,需要把上面单独的文法合为整体,所以对token串的最后增加自己设定的结束符号(当做终结符,本程序中设为100)。而语句的右部需要增加“;”,其他表达式,函数调用等各种语句后面就不需要“;”了。

12、StringTokenizer为“->”时读取出错,读不到非终结符最右端的“>”,改为“#”即可。

13、开始符号也可能出现死循环,理由同问题2.

14、文法中可以直接考虑优先级的,虽然我最终还是木有考虑。。比如课件中的例子:


15、如果全是左递归,要添加E->E'的式子,否则出现永远不会在左部出现的非终结符,那推到那里就悲剧了哦~之前算法中没有考虑到。

16、正则表达式没有检测-1,结束符刚开始设为-1了额。。


最后梳理一下整个流程,然后输出课件上的例子的结果,不然结果太多了不容易看,尤其是种别码我自己规定的,数字意义不够明确额。。

主图:



流程图:


运行结果:


附上我写的C语言的文法,实在是简单的可以额。。。

PROGRAM#STATS
STATS#@
STATS#STAT STATS
STAT#ARITHME 405
STAT#FUNC_CALL
STAT#FUNC_DEFINE
STAT#LOOP
STAT#IF_ELSE
STAT#RETURN
STAT#ASSIGN
STAT#DECLARES
STAT#SELF_CALC 405
STAT#405
SELF_CALC#300 421
SELF_CALC#300 422
SELF_CALC#421 300
SELF_CALC#422 300
FUNC_DEFINE#TYPE P 300 410 PARAMS_DEFINE 409 407 STATS 408
TYPE#288
TYPE#273
TYPE#260
TYPE#269
TYPE#265
TYPE#277
TYPE#274
P#402
P#@
PARAMS_DEFINE#TYPE P 300 ARRAY PARAMS_DEFINE_TAIL
PARAMS_DEFINE#@
PARAMS_DEFINE_TAIL#406 TYPE P 300 ARRAY PARAMS_DEFINE_TAIL
PARAMS_DEFINE_TAIL#@
RETURN#276 VALUE 405
RETURN#276 ARITHME 405
FUNC_CALL#300 410 PARAMS 409 405
PARAMS#VALUE PARAMS_TAIL
PARAMS#@
PARAMS_TAIL#406 VALUE PARAMS_TAIL
PARAMS_TAIL#@
DECLARES#TYPE P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405
DECLARE_ASSIGN#413 VALUE
DECLARE_ASSIGN#@
ARRAY#414 301 415 ARRAY
ARRAY#@
DECLARE_TAIL#406 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL
DECLARE_TAIL#@
ASSIGN#300 ARRAY 413 VALUE 405
ASSIGN#300 413 ARITHME 405
ASSIGN#300 413 FUNC_CALL
VALUE#CONST
VALUE#300
CONST#301
CONST#302
CONST#303
CONST#304
LOGIC#VALUE EQU VALUE LOGIC_TAIL
LOGIC_TAIL#@
LOGIC_TAIL#431 LOGIC
LOGIC_TAIL#430 LOGIC
EQU#423
EQU#429
EQU#411
EQU#412
EQU#427
EQU#428
ARITHME#VALUE OP VALUE ARITHME_TAIL
ARITHME_TAIL#@
ARITHME_TAIL#OP VALUE ARITHME_TAIL
OP#400
OP#401
OP#402
OP#403
OP#404
FOR_ASSIGN#405
FOR_ASSIGN#300 413 VALUE 405
LOOP#270 410 FOR_ASSIGN LOGIC 405 SELF_CALC 409 407 STATS 408
LOOP#287 410 LOGIC 409 407 STATS 408
LOOP#264 407 STATS 408 287 410 LOGIC 409 405
IF_ELSE#272 410 LOGIC 409 407 STATS 408 ELSE_IF ELSE
ELSE_IF#266 272 410 LOGIC 409 407 STATS 408
ELSE#266 407 STATS 408
ELSE#@


下面是主程序处理之后的文法:

0 PROGRAM-->STATS 
1 STATS-->@ 
2 STAT-->405 
3 <2>-->421 
4 <2>-->422 
5 SELF_CALC-->421 300 
6 SELF_CALC-->422 300 
7 TYPE-->288 
8 TYPE-->273 
9 TYPE-->260 
10 TYPE-->269 
11 TYPE-->265 
12 TYPE-->277 
13 TYPE-->274 
14 P-->402 
15 P-->@ 
16 PARAMS_DEFINE-->@ 
17 PARAMS_DEFINE_TAIL-->406 TYPE P 300 ARRAY PARAMS_DEFINE_TAIL 
18 PARAMS_DEFINE_TAIL-->@ 
19 FUNC_CALL-->300 410 PARAMS 409 405 
20 PARAMS-->@ 
21 PARAMS_TAIL-->406 VALUE PARAMS_TAIL 
22 PARAMS_TAIL-->@ 
23 DECLARE_ASSIGN-->413 VALUE 
24 DECLARE_ASSIGN-->@ 
25 ARRAY-->414 301 415 ARRAY 
26 ARRAY-->@ 
27 DECLARE_TAIL-->406 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 
28 DECLARE_TAIL-->@ 
29 <3>-->ARRAY 413 VALUE 405 
30 VALUE-->300 
31 CONST-->301 
32 CONST-->302 
33 CONST-->303 
34 CONST-->304 
35 LOGIC_TAIL-->@ 
36 LOGIC_TAIL-->431 LOGIC 
37 LOGIC_TAIL-->430 LOGIC 
38 EQU-->423 
39 EQU-->429 
40 EQU-->411 
41 EQU-->412 
42 EQU-->427 
43 EQU-->428 
44 ARITHME_TAIL-->@ 
45 OP-->400 
46 OP-->401 
47 OP-->402 
48 OP-->403 
49 OP-->404 
50 FOR_ASSIGN-->405 
51 FOR_ASSIGN-->300 413 VALUE 405 
52 LOOP-->270 410 FOR_ASSIGN LOGIC 405 SELF_CALC 409 407 STATS 408 
53 LOOP-->287 410 LOGIC 409 407 STATS 408 
54 LOOP-->264 407 STATS 408 287 410 LOGIC 409 405 
55 IF_ELSE-->272 410 LOGIC 409 407 STATS 408 ELSE_IF ELSE 
56 ELSE_IF-->266 272 410 LOGIC 409 407 STATS 408 
57 ELSE-->266 407 STATS 408 
58 ELSE-->@ 
59 <3>-->413 <0> 
60 RETURN-->276 <1> 
61 SELF_CALC-->300 <2> 
62 ASSIGN-->300 <3> 
63 ARITHME_TAIL-->404 VALUE ARITHME_TAIL 
64 ARITHME_TAIL-->403 VALUE ARITHME_TAIL 
65 ARITHME_TAIL-->402 VALUE ARITHME_TAIL 
66 ARITHME_TAIL-->401 VALUE ARITHME_TAIL 
67 ARITHME_TAIL-->400 VALUE ARITHME_TAIL 
68 VALUE-->304 
69 VALUE-->303 
70 VALUE-->302 
71 VALUE-->301 
72 <20>-->410 PARAMS 409 405 
73 DECLARES-->274 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
74 DECLARES-->277 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
75 DECLARES-->265 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
76 DECLARES-->269 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
77 DECLARES-->260 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
78 DECLARES-->273 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
79 DECLARES-->288 P 300 ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
80 PARAMS-->301 PARAMS_TAIL 
81 PARAMS-->302 PARAMS_TAIL 
82 PARAMS-->303 PARAMS_TAIL 
83 PARAMS-->304 PARAMS_TAIL 
84 PARAMS-->300 PARAMS_TAIL 
85 <15>-->405 
86 PARAMS_DEFINE-->274 P 300 ARRAY PARAMS_DEFINE_TAIL 
87 PARAMS_DEFINE-->277 P 300 ARRAY PARAMS_DEFINE_TAIL 
88 PARAMS_DEFINE-->265 P 300 ARRAY PARAMS_DEFINE_TAIL 
89 PARAMS_DEFINE-->269 P 300 ARRAY PARAMS_DEFINE_TAIL 
90 PARAMS_DEFINE-->260 P 300 ARRAY PARAMS_DEFINE_TAIL 
91 PARAMS_DEFINE-->273 P 300 ARRAY PARAMS_DEFINE_TAIL 
92 PARAMS_DEFINE-->288 P 300 ARRAY PARAMS_DEFINE_TAIL 
93 FUNC_DEFINE-->274 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
94 FUNC_DEFINE-->277 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
95 FUNC_DEFINE-->265 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
96 FUNC_DEFINE-->269 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
97 FUNC_DEFINE-->260 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
98 FUNC_DEFINE-->273 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
99 FUNC_DEFINE-->288 P 300 410 PARAMS_DEFINE 409 407 STATS 408 
100 STAT-->422 300 405 
101 STAT-->421 300 405 
102 <7>-->ARRAY DECLARE_ASSIGN DECLARE_TAIL 405 
103 <4>--><3> 
104 STAT-->276 <1> 
105 STAT-->272 410 LOGIC 409 407 STATS 408 ELSE_IF ELSE 
106 STAT-->264 407 STATS 408 287 410 LOGIC 409 405 
107 STAT-->287 410 LOGIC 409 407 STATS 408 
108 STAT-->270 410 FOR_ASSIGN LOGIC 405 SELF_CALC 409 407 STATS 408 
109 <7>-->410 PARAMS_DEFINE 409 407 STATS 408 
110 <4>-->410 PARAMS 409 405 
111 <12>--><4> 
112 STAT-->274 P 300 <7> 
113 STAT-->277 P 300 <7> 
114 STAT-->265 P 300 <7> 
115 STAT-->269 P 300 <7> 
116 STAT-->260 P 300 <7> 
117 STAT-->273 P 300 <7> 
118 STAT-->288 P 300 <7> 
119 <14>--><12> 
120 <12>-->422 405 
121 <12>-->421 405 
122 ARITHME-->301 OP VALUE ARITHME_TAIL 
123 ARITHME-->302 OP VALUE ARITHME_TAIL 
124 ARITHME-->303 OP VALUE ARITHME_TAIL 
125 ARITHME-->304 OP VALUE ARITHME_TAIL 
126 ARITHME-->300 OP VALUE ARITHME_TAIL 
127 LOGIC-->301 EQU VALUE LOGIC_TAIL 
128 LOGIC-->302 EQU VALUE LOGIC_TAIL 
129 LOGIC-->303 EQU VALUE LOGIC_TAIL 
130 LOGIC-->304 EQU VALUE LOGIC_TAIL 
131 LOGIC-->300 EQU VALUE LOGIC_TAIL 
132 <0>-->304 OP VALUE ARITHME_TAIL 405 
133 <0>-->303 OP VALUE ARITHME_TAIL 405 
134 <0>-->302 OP VALUE ARITHME_TAIL 405 
135 <0>-->301 OP VALUE ARITHME_TAIL 405 
136 STAT-->304 OP VALUE ARITHME_TAIL 405 
137 STAT-->303 OP VALUE ARITHME_TAIL 405 
138 STAT-->302 OP VALUE ARITHME_TAIL 405 
139 STAT-->301 OP VALUE ARITHME_TAIL 405 
140 STATS-->301 OP VALUE ARITHME_TAIL 405 STATS 
141 STATS-->302 OP VALUE ARITHME_TAIL 405 STATS 
142 STATS-->303 OP VALUE ARITHME_TAIL 405 STATS 
143 STATS-->304 OP VALUE ARITHME_TAIL 405 STATS 
144 <13>--><12> STATS 
145 STATS-->288 P 300 <7> STATS 
146 STATS-->273 P 300 <7> STATS 
147 STATS-->260 P 300 <7> STATS 
148 STATS-->269 P 300 <7> STATS 
149 STATS-->265 P 300 <7> STATS 
150 STATS-->277 P 300 <7> STATS 
151 STATS-->274 P 300 <7> STATS 
152 STATS-->270 410 FOR_ASSIGN LOGIC 405 SELF_CALC 409 407 STATS 408 STATS 
153 STATS-->287 410 LOGIC 409 407 STATS 408 STATS 
154 STATS-->264 407 STATS 408 287 410 LOGIC 409 405 STATS 
155 STATS-->272 410 LOGIC 409 407 STATS 408 ELSE_IF ELSE STATS 
156 STATS-->276 <1> STATS 
157 STATS-->421 300 405 STATS 
158 STATS-->422 300 405 STATS 
159 STATS-->405 STATS 
160 STATS-->300 <13> 
161 STAT-->300 <14> 
162 <1>-->301 <15> 
163 <1>-->302 <15> 
164 <1>-->303 <15> 
165 <1>-->304 <15> 
166 <1>-->300 <15> 
167 <0>-->300 <20> 
168 <13>-->404 VALUE ARITHME_TAIL 405 STATS 
169 <13>-->403 VALUE ARITHME_TAIL 405 STATS 
170 <13>-->402 VALUE ARITHME_TAIL 405 STATS 
171 <13>-->401 VALUE ARITHME_TAIL 405 STATS 
172 <13>-->400 VALUE ARITHME_TAIL 405 STATS 
173 <14>-->404 VALUE ARITHME_TAIL 405 
174 <14>-->403 VALUE ARITHME_TAIL 405 
175 <14>-->402 VALUE ARITHME_TAIL 405 
176 <14>-->401 VALUE ARITHME_TAIL 405 
177 <14>-->400 VALUE ARITHME_TAIL 405 
178 <15>-->404 VALUE ARITHME_TAIL 405 
179 <15>-->403 VALUE ARITHME_TAIL 405 
180 <15>-->402 VALUE ARITHME_TAIL 405 
181 <15>-->401 VALUE ARITHME_TAIL 405 
182 <15>-->400 VALUE ARITHME_TAIL 405 
183 <16>-->403 VALUE ARITHME_TAIL 405 
184 <16>-->401 VALUE ARITHME_TAIL 405 
185 <17>-->401 VALUE ARITHME_TAIL 405 
186 <20>-->404 VALUE ARITHME_TAIL 405 
187 <20>-->403 VALUE ARITHME_TAIL 405 
188 <20>-->402 VALUE ARITHME_TAIL 405 
189 <20>-->401 VALUE ARITHME_TAIL 405 
190 <20>-->400 VALUE ARITHME_TAIL 405 



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值