编译原理之文法推导

 

    已知文法G:
            <表达式> ::= <项> | <表达式>+<项>
    		<项> ::= <因子> | <项>*<因子>
    		<因子> ::= (<表达式>) | i
    试给出下列表达式的推导。
    (1) i				(2) (i)			(3) i*i			(4) i*i+i
    (5) i+(i+i)			(6) i+i*i		(7) (i+i)*i		(8) i+i
    (9) i*i+i*i			(10)i*i*i

 

3. 算法思想
描述的是大概的过程,具体实现的细节见代码注释。

(1)定义输入字符串E,保存需要推导的句子;定义输出字符串out=”E”和kout=”E”,out用于输出主推导(不是括号中表达式的推导 ) 时的推导过程,kout用于输出括号推导时的推导过程;定义栈S,用于保存括号中待推导的表达式。
(2)定义推导阶段标志kflag=0,kflag=0表示为主推导,kflag≠0表示为从右向左第kflag个括号推导。定义输出函数print(),根据kflag的值选择性输出推导过程(具体实现可见源代码),(3)中输出均为调用print()函数。
(3)推导方法如下: 
① 扫描字符串E,计算E中除去括号中的符号外“+”的个数,记录每个“()”的位置(位置相对每个“()”为一个整体来看)保存到数组pos,同时将“()”中的符号串压入栈S。
② E中除去括号中的符号外有多少个“+”,就在E的末尾添加几次“+I”(E→E+I),最后将E用I替换(E+I…→I+I…),同时输出。如果没有“+”,用I替换E(E→I),同时输出。
③ 倒序(从右向左)扫描E,以“+”为分割点分成若干区间,从右向左依次扫描每个区间,计算区间内除去括号中的符号外有几个“*”,从右向左扫描out(不重复扫描,每次接着上次的位置继续向左扫描),记录“I”的位置Ipos,有几个“*”就在out中Ipos位置的‘I’的后面插入“*T”(I→I*T),同时输出。 
④ 倒序扫描E,将位置i=pos[](pos数组中任意某个值)的字符“I”或“T”替换成“(E)”(I→T,T→(E)),同时输出。 
⑤当栈S不空,令E=S.top(),S栈顶出栈,推导阶段标志kflag++,重复②~⑤过程。直到S栈空,结束。

4. 不足之处
① 对于文法并没有用相应的数据结构去存储,推导过程仅仅只是依据输入的表达式中的算符来判断,并且只针对2.中给定的文法推导。
②输入检测不够严谨,仅仅只对输入能够接受的字符进行了限制,并没有检查句子合法性。
③推导过于依赖“+*()”符号,所以整体不是最左推导也不是最右推导;并且对应存在嵌套括号(括号中还有括号)的表达式不能推导,能够推导大部分的多级括号(式中有多个括号,但每个括号中无括号),但也有少部分不能推导。
 

    #include<iostream>
    #include<string>
    #include<stack>
    using namespace std;

    int kflag=0;//推导阶段标志,0为主推导,非0表示从右向左第几个括号推导
    string out,kout;//保存括号推导过程,保存全部推导过程

    void shift()//存储的字母输出为汉字
    {
    	cout<<"	→";
    	if(kflag==0)//主推导直接输出out
    	{
    		cout<<out;
    		for(int j=0;j<30-out.length();j++)
    			cout<<" ";
    		cout<<"|	→";
    		for(int i=0;i<out.length();i++)
    		{
    			if(out[i]=='E')
    				cout<<"<表达式>";
    			else if(out[i]=='I')
    				cout<<"<项>";
    			else if(out[i]=='T')
    				cout<<"<因子>";
    			else
    				cout<<out[i];
    		}
    		cout<<endl;
    	}
    	else//括号推导输出kout
    	{
    		cout<<kout;
    		for(int j=0;j<30-kout.length();j++)
    			cout<<" ";
    		cout<<"|	→";
    		for(int i=0;i<kout.length();i++)
    		{
    			if(kout[i]=='E')
    				cout<<"<表达式>";
    			else if(kout[i]=='I')
    				cout<<"<项>";
    			else if(kout[i]=='T')
    				cout<<"<因子>";
    			else
    				cout<<kout[i];
    		}
    		cout<<endl;
    	}
    }

    void print()//输出函数,输出推导过程
    {
    	int kk,knum,kpos1,kpos2;
    	if(kflag==0)//主推导
    		shift();//输出
    	else//括号推导
    	{
    		knum=0;//第几个括号
    		for(kk=kout.length()-1;kk>=0;kk--)
    		{
    			if(kout[kk]==')')
    			{
    				knum++;//遇到')加1
    				if(knum==kflag)//当遇到第kflag个)时
    				{
    					kpos2=kk;//记录右括号位置
    					while(kout[kk]!='(')
    						kk--;
    					kpos1=kk;//记录左括号位置
    				}
    			}
    		}
    		kout.replace(kpos1+1,kpos2-kpos1-1,out);//将括号中字符替换为当前的括号推导
    		shift();//输出
    	}
    }

    int main()
    {
    	string P[3][3];
    	P[0][0]="E";
    	P[0][1]="I";
    	P[0][2]="E+I";
    	P[1][0]="I";
    	P[1][1]="T";
    	P[1][2]="I*T";
    	P[2][0]="T";
    	P[2][1]="(E)";
    	P[2][2]="i";

    	cout<<endl<<"文法如下:"<<endl;//输出文法
    	for(int i=0;i<3;i++)
    	{
    		cout<<"	"<<P[i][0]<<" → "<<P[i][1]<<"|"<<P[i][2]<<"	|	";
    		for(int j=0;j<3;j++)
    		{
    			for(int m=0;m<P[i][j].length();m++)
    			if(P[i][j][m]=='E')
    				cout<<"<表达式>";
    			else if(P[i][j][m]=='I')
    				cout<<"<项>";
    			else if(P[i][j][m]=='T')
    				cout<<"<因子>";
    			else
    				cout<<P[i][j][m];
    			if(j==0)
    				cout<<" →";
    			if(j==1)
    				cout<<" | ";
    		}
    		cout<<endl;
    	}

    	stack<string> S; //保存括号中的句子,栈顶到栈底依次保存的是E中从右向左每对括号中的内容
    	int n[20];//括号内串长度,下标默认为第几个括号
    	int pos[20];//记录表达式中所有(位置
    	string E;//保存输入的文法
    	kout="E";//输出总的推导过程
    	string choose="1";
    	while(choose=="1") //选择是否继续的循环
    	{
    		cout<<endl<<"★说明:无法推导括号嵌套“(())”或“(()())”的句子,只能推导多个单级括号“()()()”的句子!"<<endl;
    		int error=1;
    		while(error==1)//输入句子,简单判错
    		{
    			cout<<"请输入该文法的句子:"<<endl<<">>";
    			cin>>E;
    			for(int i=0;i<E.length();i++)
    			{
    				if(E[i]!='i' && E[i]!='+' && E[i]!='*' && E[i]!='(' && E[i]!=')')
    				{
    					cout<<"输入有误请重新输入!表达式应只含有“i,+,*,(,)”等字符!"<<endl;
    					error=1;
    					break;
    				}
    				else
    					error=0;
    			}
    		}

    		cout<<endl<<"句子  "<<E<<"  的推导过程如下:"<<endl;
    		cout<<endl<<"	E";
    		for(int j=0;j<30-1;j++)
    				cout<<" ";
    		cout<<"  |	<表达式>"<<endl;//输出开始符
    	K:
    		int k=0;//访问pos数组
    		int add=0;//括号外加号个数
    		out="E";//保存括号中内容推导过程
    		for(int i=0;i<E.length();i++)
    		{
    			if(E[i]=='(')//未考虑括号嵌套情况
    			{
    				if(k!=0)//保存(的相对位置
    				{
    					pos[k]=i;//一对()看成一个位置
    					for(int j=0;j<k;j++)
    						pos[k]-=n[j];
    					pos[k]-=k;
    				}
    				else
    					pos[k]=i;
    				k++;//查找下一个(
    				i++;//跳过(
    				n[k-1]=0;//计算括号内字符长度
    				while(E[i]!=')')//跳过括号中的字符,并计算长度
    				{
    					n[k-1]++;
    					i++;
    				}
    				S.push(E.substr(i-n[k-1],n[k-1]));//括号中符号串内容入栈,如果括号很多栈底为靠左括号内容
    			}
    			if(E[i]=='+') add++;//计数+个数
    		}
    		for(int i=0;i<add;i++)
    		{
    			out.append("+I");//几个+就输出几个+I
    			print();
    		}
    		if(add==0)//无+时
    		{
    			out="I";
    			print();
    		}
    		else//有+时
    		{
    			out[0]='I';//E用I替换
    			print();
    		}

    		int mul;//两个+之间的括号之外乘号个数
    		int pos1=E.length()-1,pos2; //两个+的位置
    		int Ipos;//I的位置
    		char str[2]={'*','T'};//“*T”
    		str[2]='\0';
    		int i=E.length()-1;
    		int m=out.length()-1;
    		while(i!=-1)//倒序遍历输入的句子E
    		{
    			for(;i>=0;i--)//未考虑括号嵌套情况
    			{
    				if(E[i]==')')//遇到)
    				{
    					i--;//跳过)
    					while(E[i]!='(')//跳过括号中的字符
    						i--;
    				}
    				if(E[i]=='+') break;
    			}
    			pos2=pos1;//记录区间(以+为分界点)末端位置 ,是上一个区间的开始位置
    			pos1=i;//记录区间开始位置
    			if(i!=-1)//保证循环能够结束
    				i--;
    			mul=0;
    			for(int j=pos1+1;j<pos2;j++)//扫描此区间
    			{
    				if(E[j]=='(')//遇到(
    				{
    					j++;//跳过(
    					while(E[j]!=')')//跳过括号中的字符
    						j++;
    				}
    				if(E[j]=='*') mul++; //记录*个数
    			}
    			for(;m>=0;m--)//倒序遍历out字符
    			{
    				if(out[m]=='I')
    				{
    					Ipos=m;//记录I的位置
    					m=m-1;//下次从下一个位置开始遍历
    					break;
    				}
    			}
    			if(mul!=0)//有乘号
    			{
    				for(int j=0;j<mul;j++)
    				{
    					out=out.insert(Ipos+1,str);//I位置之后插入"*T"
    					print();
    				}
    			}
    	    }

    	    for(i=out.length()-1;i>=0;i--)//倒序遍历out字符串
    	    {
    	    	for(int j=0;j<k;j++)//查询pos数组,即括号(位置
    	    	{
    	    		if(i==pos[j])//(位置与i相等时
    	    		{
    	    			if(out[i]=='I')//推导路线为I->T->(E)
    	    			{
    	    				out[i]='T';
    	    				print();
    					}
    					if(out[i]=='T')
    					{
    						out.replace(i,1,"(E)");
    						print();
    					}
    				}
    			}
    		}

       		for(i=out.length()-1;i>=0;i--)//倒序遍历out
    	    {
    	    	if(out[i]=='I')//将I,T推导至i
    	    	{
    	    		out[i]='T';
    	    		print();
    			}
    	    	if(out[i]=='T')
    	    	{
    	    		out[i]='i';
    	    		print();
    			}
    		}
    		if(kflag==0)//如果是主推导
    			kout=out;//保存总输出
    		if(!S.empty())//当S栈不空,说明句子中有括号
    		{
    			E=S.top();//栈顶为最右侧括号
    			S.pop();//出栈
    			kflag++;//表示当前正在推导第几个括号中的内容
    			goto K;
    		}
    		else
    			cout<<endl<<"推导完毕!"<<endl<<endl;
    		cout<<"输入 1 继续推导其他句子,输入其他退出。"<<endl;
    		cin>>choose;
    		kflag=0;//kflag清零,继续推导其他句子
    	}
    	return 0;
    }

1.i

2.(i)

3.i*i

4.i*i+i

5.i+(i+i)

6.i+i*i

  • 10
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值