[编译原理]算符优先文法分析与实现
Posted 童凌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[编译原理]算符优先文法分析与实现相关的知识,希望对你有一定的参考价值。
在算术表达式中,运算的优先顺序主要采用四则运算的口诀:先乘除后加减,从左算到右,这个口诀确定了乘除运算优先于加减运算,同级运算一律从左到右,即代数中的“左结合”法则。如果计算的每一步做一个运算,那么四则运算的每一步都是唯一的。
算符优先分析法实质上就是仿效上述计算过程而设计的一种方法,它规定:用算符之间的运算优先关系来确定语句的合法性。并不是所有上下文无关文法都可以建立这样的关系,只有所谓的算符优先文法,才适合这种方法。
一个文法G,如果它不含空产生式,并且任何产生式的右部都不含两个相连的非终结符,及即不含如下形式的产生式:P-->....QR....,其中P,Q,R,是非终结符。称该文法G为算符文法(operator grammar),也称OG文法。
(....)代表由终结符和非终结符组成的任意序列,包括空字。
2.实验步骤
花一周时间写出表达式的文法,优先符号表等理论准备。设计好程序结构,画出模块结构图,写出模块接口和调用关系。描述每个模块的功能。上机编制子模块代码,并测试。业余继续完成代码设计。第二次上机进行调试、修改,对照测试数据比对结果。第三次上机要求全部通过。有余力的同学可编制解释执行程序,对表达式进行求值(此时可不考虑赋值语句)。
算符优先分析算法
算符优先分析算法是以最左素短语可作为归约串的自下而上分析方法。下面首先引入最左素短语的概念,然后介绍算符优先分析算法。
素短语的概念:它是一个递归的定义,至少含有一个终结符,并且除它自身之外不再含任何更小的素短语,所谓最左素短语就是处于句型最左边的素短语的短语。而一个算符优先文法G的任何句型的最左素短语是满足以下条件的最左子串NaNb…NcNdN(N是非终结符,a,b,c,d是终结符)
n素短语(Prime Phrase)
nSÞ* αAβ and AÞ+γ
nγ至少含一个终结符
n且不含更小的含终结符的短语,则称γ是句型αγβ的相对于变量A的素短语
实例:句型T+T*F+id,求出其语法树,可知,T*F是最左素短语,id也是素短语,但不是最左的。
最左素短语是指在具有多个素短语的句型中处于最左边的那个素短语。例如,文法G1的一个句型
i+F*i的语法图如图所示,其中,i1,F3和i2是直接短语,i1是句柄,i1,F3,i2,F3*i2是短语,i1和i2是素短语,i1是最左素短语。
现在考虑算符优先文法G,把它句型一般形式写成
#N1a1N2a2…NnanNn+1#
其中前一个#是栈底符号,最后的#是句末符,此外的文法符号可能有一部分在栈里,另有一部分是尚未入栈的符号串。一开始,只有前一个#入栈,其余的是整个尚未入栈的输入符号串,他们应当全是终结符。上述句型的特点是,每个a1均是终结符,每两个终结符之间至多只有一个非终结符,这是根据算符文法的定义可知的。
图 6.9算符优先分析时语法树的框架
特别要注意在算符优先分析过程中,因去掉了单非终结符之间的归约,非终结符的名字没有任何意义。所以在归约过程中所有的非终结符都用同一个名字。
(3)算符优先分析归约过程的算法
自底向上的算符优先分析法,也为自左向右归约,我们已经知道它不是规范归约。规范归约的关键问题是如何寻找当前句型的句柄,而算符优先分析归约的关键是如何找最左素短语。最左素短语 NiaiNi+1ai+1… ajNj+1 应满足:
ai-1ai
aiai+1…aj
ajaj+1
在文法的产生式中存在右部符号串的符号个数与该素短语的符号个数相等,非终结符号对应 Nk,(k=i,…,j+1)不管其符号名是什么。终结符对应ai,…,aj,其符号名要与ai,…,aj的实际名相一致,相应位置也一致,才有可能形成素短语。由此,我们在分析过程中可以设置一个符号栈S,用以寄存归约或待形成最左素短语的符号串,用一个工作单元a存放当前读入的终结符号,归约成功的标志是当读到句子结束符#时,S栈中只剩#N,即只剩句子最左括号'#'和一非终结符N。下面给出分析过程的示意图如图6.10。
在归约时要检查是否有对应产生式的右部与S[j+1]…S[k]相符,若有才可归约,否则出错。在这个分析过程中把'#'也放在终结符集中。
S[j+1]…S[k]为S栈中符号串,S[k]为栈顶符号。所谓检查是否有对应产生式的右部与S[j+1]…S[k]相符,是指符号个数是否相等,对应的终结符名是否相同。不管非终结符名字。
规范归约时句柄为某一产生式的右部,归约结果为从符号栈中去掉与句柄相同的产生式右部符号串,并把左部非终结符放入栈中,代替归约前的句柄。
图 6.10算符优先分析归约过程框图
算符优先分析法是一种自底向上分析方法,也称移进-归约分析法,粗略地说它的实现思想是对输入符号串自左向右进行扫描,并将输入符逐个移入一个后进先出栈中,边移入边分析,一旦栈顶符号串形成某个句型的句柄时,(该句柄对应某产生式的右部),就用该产生式的左部非终结符代替相应右部的文法符号串,这称为一步归约。重复这一过程直到归约到栈中只剩文法的开始符号时则为分析成功,也就确认输入串是文法的句子。本章将在介绍自底向上分析思想基础上,着重介绍算符优先分析法。
栈顶符号串是指该符号串包含栈顶符号在内,并由栈中某个符号为该符号串的头,栈顶符号为该符号串的尾,也可以只有一个栈顶符号。
算符优先分析的基本思想是只规定算符(广义为终结符)之间的优先关系,也就是只考虑终结符之间的优先关系,不考虑非终结符之间的优先关系。在归约过程中只要找到可归约串就归约,并不考虑归约到那个非终结符名,算符优先分析的可归约串不一定是规范句型的句柄,所以算符优先归约不是规范归约。算符优先分析的可归约串是当前符号栈中的符号和剩余的输入符号构成句型的最左素短语。
例如:若有文法G为: (1) E→E+E (2) E→E*E (3) E→i
对输入串i1+i2*i3的归约过程可表示为表6.3。
表6.3 对输入串i1+i2*i3的归约过程 f6-2-2.swf
在分析到第6步时,栈顶的符号串为E+E,若只从移进-归约的角度讲,栈顶已出现了产生式(1)的右部,可以进行归约,但从通常四则运算的习惯来看应先乘后加,所以应移进,这就提出了算符优先的问题。 本节所给例子是二义性文法,对二义性文法不能构造确定的分析过程,但是在本例中,人为地规定了算符之间的优先关系,仍然可构造出确定的分析过程。对于一个算符优先文法,只要能够造出它的算符优先表,就可以利用算符优先分析方法,分析一个句子是否符合这个文法的定义。
那么定义FirstVT(P)=a|P->a、、、或P->Qa、、、,a属于终结字符集,而Q属于非终结字符集
LastVT(P)=a|P->...a或P->...aQ,a属于终结字符集,而Q属于非终结字符集
由以下两条规则来构造FirstVT集:
(1) 若有产生式P-〉a…、或P->Qa…,则a属于FirstVT(P);
(2) 若有a属于FirstVT(Q),且有产生式P-〉Q…,则a属于FirstVT(P);
类似的有构造LastVT集的规则:
(1) 若有产生式P->…a或P->…aQ,则a属于LastVT集。
(2) 若a属于LastVT(Q),且有产生式P-〉…Q,则a属于LastVT集。
假定G是一个不含空字符产生式的算符文法。对于任何一对终结符a,b,
(1)a=b,当且仅当G中含有形如P->…ab…或P->…aQb…的产生式;
(2)a<b,当且仅当G中含有形如P->…aR…的产生式,而R-〉b…或R->Qb…;
(3)a>b,当且仅当G中含有形如P->…Rb…的产生式,而R->…a或R->…aQ;
这样再结合上次的FirststVT和LastVT集的概念便可以由文法自动构造出算符优先表。
表6.14算符优先关系矩阵表
| ; | ( | ) | a | + | # |
|
|
|
|
|
|
|
语法分析是编译程序的核心部分。语法分析的作用是识别由词法分析给出的单词符号序列是否是给定文法的正确句子(程序),目前语法分析常用的方法有自顶向下(自上而下)分析(见第5章)和自底向上(自下而上)分析两大类。而自底向上分析又可分为算符优先分析和LR分析(见第7章),它们的分析都是移进-归约过程,是自顶向下最右推导的逆过程,但LR分析是规范归约 ,算符优先分析不是规范归约。它们的区别在于识别可归约串的原则不同。LR分析是按规范句型的句柄为可归约串,算符优先分析是以句型的最左素短语为可归约串,这样算符优先分析去掉了单非终结符的归约(即一个非终结符到另一个非终结符的归约),因此,算符优先分析法比LR分析(规范归约)法的归约速度快。在LR分析一章的语法分析器自动生成工具Yacc中,对算数表达式的归约往往会用到算符优先关系的概念。
算符优先分析的缺点是对文法有一定的限制,在实际应用中往往只用于算数表达式的归约。由于算符优先分析不是规范归约,所以可能把不是文法的句子错误的归约成功。 算符优先分析是移进-归约过程,移进就是将一个终结符推进栈 ,归约就是将0个或多个符号(某个产生式的右部)从栈中弹出,将相应产生式左部的非终结符压入栈中。对一个输入符号串,不断地进行移进-归约过程,若能归约到文法的开始符号则为归约成功。在归约工作的每一步,都是从当前栈顶符号串中寻找一个子串,看它是否能归约到文法的某个非终结符号,该子串称为"可归约串"。因此, 如何识别"可归约串"是算符优先分析的关键问题。
3. 实验内容
#include <stdio.h>
#include <malloc.h>
void push(char pch); //符号的入栈函数
char pop(); //符号的出栈函数
void guiyue(); //规约函数
int getint(char ch); //得到算符优先值函数
void scanner(); //扫描函数
void show(); //显示函数
//优先关系表
//用1表示大于。-1表示小于,0表示等于,-2表示没有关系
const int table[8][8]=
1,1,-1,-1,-1,1,-1,1,
1,1,-1,-1,-1,1,-1,1,
1,1,1,1,-1,1,-1,1,
1,1,1,1,-1,1,-1,1,
-1,-1,-1,-1,-1,0,-1,-2,
1,1,1,1,-2,1,-2,1,
1,1,1,1,-2,1,-2,1,
-1,-1,-1,-1,-1,-2,-1,0
;
//算符
const char suanfu[8]='-','*','/','(',')','i','#','\\0';
//定义全局变量
char curch; //当前字符
char curcmp; //当前比较字符
int right; //判断是否出错,出错则为0
int k; //比较字符在栈的位置
int i,j;
//定义一个链表
typedef struct node
char lch;
struct node *next;
node,Lchar,Lch;
//结点
node *base,*top,*p,*q,*temp;
//显示函数
void show()
printf("***************************************************\\n");
printf("| + | - | * | / | ( | ) | i | # |\\n");
printf("***************************************************\\n");
printf("+");
for(i=0;i<8;i++)
for(j=0;j<8;j++)
if(table[i][j]==1)
printf("| > ");
else if(table[i][j]==-1)
printf("| < ");
else if(table[i][j]==0)
printf("| = ");
else
printf("| null");
printf("|\\n%c",suanfu[i]);
printf("***************************************************\\n");
//扫描字符串
void scanner()
k=1;
for(;;)
curch=q->lch;
temp=top;
if(temp->lch=='E')
break;
else
for(;;)
if(temp->lch=='E'||temp->lch=='T'||temp->lch=='F')
temp=temp->next;
k++;
else
curcmp=temp->lch;
break;
i=getint(curcmp);
j=getint(curch);
printf("\\n%d\\t%d\\t",table[i][j],k);
temp=top;
for(;;) //打印栈
printf("%c",temp->lch);
if(temp->lch=='#')
break;
else
temp=temp->next;
printf("\\t\\t");
temp=q;
for(;;) //打印待比较的字符
printf("%c",temp->lch);
if(temp->lch=='#')
break;
else
temp=temp->next;
//i=getint(curcmp);
//j=getint(curch);
if(table[i][j]==-2) //算符优先值为空
printf("\\n%d\\t%d\\t%c\\t\\t%c\\n该字符串不是算符优先法",table[i][j],k,curcmp,curch);
right=0;
break;
else //算法优先值不为空
if(table[i][j]==-1||table[i][j]==0)
if(curch=='#')
if(k==2) //当前比较字符在栈的位置为两个元素
break;
else
printf("\\n%d\\t%d\\t%c\\t\\t%c\\n该字符串不是算符优先法",table[i][j],k,curcmp,curch);
right=0;
break;
push(curch);
k=1;
curcmp=curch;
q=q->next;
else //算符优先值为1时,发生归约
guiyue();
//得到算符优先值函数
int getint(char ch)
int a;
switch(ch)
case '+':a=0;break;
case '-':a=1;break;
case '*':a=2;break;
case '/':a=3;break;
case '(':a=4;break;
case ')':a=5;break;
case 'i':a=6;break;
case '#':a=7;break;
return a;
//符号的入栈函数
void push(char pch)
temp=(Lch *)malloc(sizeof(Lchar));
temp->lch=pch;
temp->next=top;
top=temp;
//符号的出栈函数
char pop()
char ch;
ch=top->lch;
if(top->lch!='#')
top=top->next;
return ch;
//规约函数
void guiyue()
char ch1;
char ch2;
char ch3;
char popch;
ch1=pop();
ch2=pop();
if((ch1>='a'&&ch1<='z')||(ch1>='0'&&ch1<='9'))
if(ch2=='#')
push('F');
k=1;
else
switch(ch2)
case '+':popch='T';break;
case '-':popch='T';break;
case '*':popch='F';break;
case '/':popch='F';break;
push(ch2);
push(popch);
k=1;
else
if(ch1=='F')
ch3=pop();
if(ch2=='*'&&ch3=='T')
push('T');
else if(ch2=='/'&&ch3=='T')
push('T');
else
right=0;
else if(ch1=='T')
ch3=pop();
if(ch2=='+'&&ch3=='E')
push('E');
else if(ch2=='-'&&ch3=='E')
push('E');
else
right=0;
//主函数
void main()
char ch;
right=1;
base=(Lch *)malloc(sizeof(Lchar));
base->next=NULL;
base->lch='#';
top=base;
q=(Lch *)malloc(sizeof(Lchar));
q->next=NULL;
p=q;
printf("本文法为算符优先分析法\\n");
printf("E->E+T|E-T|T\\n");
printf("T->T*F|T/E|F\\n");
printf("F->(E)|i\\n");
show();
printf("Please input a string(End with#)\\n");
printf("[eg]i+i*i#\\n");
do //输入待比较字符串以#结束
ch=getchar();
putchar(ch);
if((ch>='a'&&ch<='z')||(ch>='0'&&ch<='9')||
ch=='+'||ch=='*'||ch=='-'||ch=='/'||ch=='('||ch==')'||ch=='#')
temp=(Lch *)malloc(sizeof(Lchar));
temp->next=NULL;
temp->lch=ch;
q->next=temp;
q=q->next;
else
temp=p->next;
printf("\\nInput a wrong char!Please InputAgain:\\n");
for(;;)
if(temp!=NULL)
printf("%c",temp->lch);
else
break;
temp=temp->next;
while(ch!='#');
p=p->next;
q=p;
scanner();
if(right)
printf("\\n该字符串归约成功!\\n");
else
printf("\\n该字符串归约失败!\\n");
getchar();
getchar();
以上是关于[编译原理]算符优先文法分析与实现的主要内容,如果未能解决你的问题,请参考以下文章