【编译原理】Python语法分析LL(1)、LR(1)

目录

一、实验目的

二、实验任务

三、实验原理

1  LL(1)文法

2  LR文法

四、实验过程

1  LL(1)文法

2  LR文法

五、实验结果

1  LL(1)文法

2  LR(0)文法

3  LR(1)文法

参考资料

附录

1 LL(1)代码

2 LR(0)文法

3  LR(1)代码


一、实验目的

1、掌握非递归下降的预测分析;

2、了解如何使用Yacc、bision等工具完成语法分析

二、实验任务

1、针对书上第三章中的表达式文法,采用LL(1)、LR(1)进行分析(必做),不需要做词法分析,输入为记号流。可以手工构造预测分析表,但鼓励自动生成。

2、使用Yacc或bision等工具,实现对表达式文法的语法分析析(附加)。

三、实验原理

1  LL(1)文法

任何两个产生式A ®a | b 都满足下列条件:

(1)FIRST(a ) Ç FIRST(b ) = Æ;

(2)若b Þ* e ,那么FIRST(a) Ç FOLLOW(A) = Æ。

把满足这两个条件的文法称为LL(1)文法。先定义两个和文法有关的函数FIRST(a ) = { a | a Þ* a…, a Î VT},特别是,a Þ* e时,规定e Î FIRST(a ) ;FOLLOW(A) = { a | S Þ* …Aa…,aÎVT},如果A是某个句型的最右符号,那么$属于FOLLOW(A)。

  1. 查找非终结符的First

Step 1:按产生式顺序来,从开始符找起;

Step 2:如果右部的串首为终结符,则直接将该终结符填入左部非终结符的First集中;

Step 3:如果右部的串首为非终结符,则左部非终结符的First集等价于串首非终结符的First集。因而,需要利用Step 2和Step 3继续寻找串首非终结符的First集。

  1. 查找非终结符的Follow

Step 1:按产生式顺序来,从开始符找起(开始符的Follow集必定包含$);

Step 2:从所有产生式右部寻找目标非终结符,若其后紧跟终结符,则将终结符填入目标非终结符的Follow集。特别地,若其后紧跟$,则目标非终结符的Follow集等价于产生式左部非终结符的Follow集。

Step 3:从所有产生式右部寻找目标非终结符,若其后紧跟非终结符,则将该非终结符的First集元素填入目标非终结符的Follow集。特别地,若该非终结符的First集元素中包含e,则需针对e情况时做特殊处理,即目标非终结符的Follow集等价于产生式左部非终结符的Follow集。

LL(1)文法有一些明显的性质:没有公共左因子;不是二义的;不含左递归。

  1. 构造预测分析表

(1)对文法的每个产生式A ® a ,执行(2)和(3);

(2)对FIRST(a)的每个终结符a,把A ® a 加入M[A, a];

(3)如果e在FIRST(a)中,对FOLLOW(A)的每个终结符b(包括$),把A ® a加入M[A, b];

(4)M中其它没有定义的条目都是error。

2  LR文法

LR分析器的模型如图所示,它包括输入、输出、栈、驱动程序和含动作和转移两部分的分析表。驱动程序对所有的LR分析方法都一样,不同的分析方法构造的分析表不同。分析程序每次从输入缓冲区读一个符号,它使用栈存储形式为s0X1s1X2s2⋯Xmsm 的串,sm 在栈顶。Xi 是文法符号,si是叫做状态的符号,状态符号概括了栈中它下面部分所含的信息。栈顶的状态符号和当前的输入符号用来检索分析表,以决定移进—归约分析的动作。

图 3.1:LR分析器的模型

2.1  LR(0)文法

文法G的LR(0)项目(简称项目)是在右部的某个地方加点的产生式。如产生式A→XYZ对应有四个项目:

A→·XYZ

A→X·YZ

A→XY·Z

A→XYZ·

产生式A→ε只有一个项目A→·和它对应。直观地讲,项目表示在分析过程的某一点,已经看见了产生式的多大部分(点的左边部分)和下面希望看见的部分。

2.2  LR(1)文法

让状态含有更多的信息,使之能够剔除无效归约是完全可能的。定义项目,使之包含一个终结符作为第二个成分,这样就把更多的信息并入了状态。项目的一般形式也就成了[A→α·β,a],其中A→αβ是产生式,a是终结符号或$,这种项目叫做LR(1)项目,1是第二个成分的长度,这个成分叫做项目的搜索符。搜索符对β非空的项目[A→α·β,a]是不起作用的,但对形式为[A→α·,a]的项目,它表示只有在下一个输入符号是a时,才能要求按A→α归约。这样,分析器只有在输入符号是a时才按A→α归约,其中[A→α·,a]在栈顶状态的LR(1)项目集中。这样的a的集合是FOLLOW(A)的子集,完全可能是真子集。我们说LR(1)项目[A→α·β,a]对活前缀γ是有效的,如果存在着推导S=>*rmδAw=>rmδαβw,其中:

(1)  γ=δα

(2) a是w的第一个符号,或者w 是ε且a是$。

在LR(1)文法中,栈中的文法符号总是形成一个活前缀,分析表的转移函数本质上是识别活前缀的DFA,栈顶的状态符号包含了确定句柄所需要的一切信息,是已知的最一般的无回溯的移进归约方法,能及时发现语法错误,但是,手工构造分析表的工作量太大。

LR分析法的规约过程是规范推倒的逆过程,所以LR分析过程是一种规范规约的逆过程,L表示从左到右扫描输入串, R表示最左规约(即最右推导的逆过程),括号中的1表示向右查看输入符号数为1。LR(1)项目可以看成两个部分组成,一部分和LR(0)项目相同,这部分成为心,另一部分为向前搜索符集合。所以只有当面临的输入符属于向前搜索符的集合,才做规约动作,其他情况均出错。LR(1)方法恰好解决LR(0)(1)方法在某些情况下存在的无效规约问题。

四、实验过程

1  LL(1)文法

LL(1)文法的分析模型如图4.1所示,课本上要分析的文法(3.8)如下:

E→TE′

E′→+TE′|ε

T→FT′

T′→*FT′|ε

F→(E)|id

图4.1:LL(1)文法的分析模型

(1)构造LL(1)预测分析表,按照实验原理中描述的流程,最终分析表如下表所示。

表4.1:LL(1)预测分析表

在实际编程中,用字典进行存储,其中P代表E′,p代表T′,e代表e,如下所示:

#Epie=P;Tpie=p;yimuxitong=e

dicM={

    "E":{"i":"TP","C":"TP"},

    "P":{"+":"+TP",")":"e","$":"e"},

    "T":{"i":"Fp","(":"Fp"},

    "p":{"+":"e","*":"*Fp",")":"e","$":"e"},

    "F":{"i":"i","(":"(E)"}

}

(2)按照LL(1)文法分析的伪代码进行编程,具体代码详见附录1.

图4.2:LL(1)文法的伪代码

2  LR文法

LR(1)文法的分析模型如图3.1所示.

2.1  LR(0)文法

利用LR(0)课本上要分析的文法(3.11)如下:

E->E+T

E->T

T->T*F

T->F

F->(E)

F->i

(1)构造LR(0)预测分析表,按照实验原理中描述的流程,最终分析表如下表所示。

表4.2:LR(0)预测分析表

在实际编程中,用字典进行存储,如下所示:

action={0: {'i': 's5', '(': 's4'},

 1: {'+': 's6', '$': 'acc'},

 2: {'+': 'r2', '*': 's7', ')': 'r2', '$': 'r2'},

 3: {'+': 'r4', '*': 'r4', ')': 'r4', '$': 'r4'},

 4: {'i': 's5', '(': 's4'},

 5: {'+': 'r6', '*': 'r6', ')': 'r6', '$': 'r6'},

 6: {'i': 's5', '(': 's4'},

 7: {'i': 's5', '(': 's4'},

 8: {'+': 's6', ')': 's11'},

 9: {'+': 'r1', '*': 's7', ')': 'r1', '$': 'r1'},

 10: {'+': 'r3', '*': 'r3', ')': 'r3', '$': 'r3'},

 11: {'+': 'r5', '*': 'r5', ')': 'r5', '$': 'r5'}}

goto={0: {'E': 1, 'T': 2, 'F': 3},

 4: {'E': 8, 'T': 2, 'F': 3},

 6: {'T': 9, 'F': 3},

 7: {'F': 10}}

(2)按照LR(0)文法分析的伪代码进行编程,具体代码详见附录2.

图4.3:LR(0)文法的伪代码

2.2  LR(1)文法

课本上要分析的文法(3.13)如下:

对下列文法,用LR(1)分析法对任意输入的符号串进行分析:

E->S

S->BB

B->bB|a

程序的流程图如图所示,其中输入以#结束的符号串(包括a、b、#),如:abb#。此文法采用C++进行编程,对应文法分析表Action的存储结构为字符型二维数组,Goto的存储结构为整数型二维数组,如下所示:

图4.4: LR(1)分析流程图

图4.5:LR(1)分析表的存储结构示意图

五、实验结果

1  LL(1)文法

程序识别出文法的非终结符VN集合为:['E', 'P', 'T', 'p', 'F'];终结符VT集合为['(', 'C', '+', ')', '*', 'i', '$', 'e'],其中P代表E′,p代表T′,e代表e。输入符号串i*i+i,分析结果如下图所示,经过17步分析,最终判断该句子属于该文法,分析结束。当然,如果输入i*+i,不符合该文法的句子,结果如图5.2所示,最终判断该句子不属于该文法。说明程序编写正确。

图5.1: 句子“i*i+i”的LL(1)分析过程

图5.2: 句子“i*+i”的LL(1)分析过程

2  LR(0)文法

输入句子“i*i+i$”,采用LR(0)文法进行分析的结果如下,经过14步分析,最终判断该句子属于该文法,分析结束;当然,如果输入i*+i,不符合该文法的句子,结果如图5.4所示,最终判断该句子不属于该文法。程序编写正确。

图5.3: 句子“i*i+i”的LR(0)分析过程

图5.4: 句子“i*+i”的LR(0)分析过程

3  LR(1)文法

输入句子“abb#”,采用LR(1)文法进行分析的结果如下,经过8步分析,最终判断该句子属于该文法,分析结束;输入句子“abbab#”,采用LR(1)文法进行分析的结果如下,经过6步分析,最终判断该句子不属于该文法,分析结束。

图5.5:LR(1)文法的分析过程

参考资料

  1. 自上而下语法分析器python:https://blog.csdn.net/zhengjue3343/article/details/80042782
  2. 基于LR(1)的语法检查器(一):https://blog.csdn.net/Maxners/article/details/78974766
  3. LR(1)文法实现:https://blog.csdn.net/int_Brosea/article/details/106022936
  4. LR(1)文法:https://wenku.baidu.com/view/556116976bec0975f465e223.html

附录

1 LL(1)代码

# -*- coding: utf-8 -*-

"""

Created on Thu Apr 16 10:41:01 2020

 

@author: 郭百海

"""

 

#分析表M

#dicM={

#    "S":{"(":"A", ")":"A"},

#    "A":{"(":"CB", ")":"CB" },

#    "B":{"i":"iCB","*":"e", "$":"e"},

#    "C":{"(":"ED", ")":"ED" },

#    "D":{"i":"e","+":"+ED","*":"e", "$":"e"},

#    "E":{"(":"(", ")":")A*" }

#}

 

#Epie=P;Tpie=p;yimuxitong=e

dicM={

    "E":{"i":"TP","C":"TP"},

    "P":{"+":"+TP",")":"e","$":"e"},

    "T":{"i":"Fp","(":"Fp"},

    "p":{"+":"e","*":"*Fp",")":"e","$":"e"},

    "F":{"i":"i","(":"(E)"}

}

#非终结符

VN=[]

for item in dicM.keys():

    VN.append(item);

print("非终结符VN集合为:{0}".format(VN))

 

#终结符

VT=[]

# print(len(dicM))

for item in dicM.values():

    for it in item:#遍历key值

        VT.append(it)

VT=list(set(VT))

VT.append("e")

print("终结符VT集合为{0}:".format(VT))

 

#翻转字符串,返回字符串

def convertStr(arg):

    conarg=arg[::-1]

    return conarg

 

#找产生式,返回产生式右部

def findCSS(argS,argstr):

    if argS in dicM.keys() :

        temp_value=dicM[argS]

        if argstr in temp_value.keys():

            temp=temp_value[argstr]

            return temp

    return ""

#截取列表 除去最后一个元素

def substr(arg):

    # print(arg,"fdsfs")

    Listarg=list(arg)

    le=len(Listarg)

    args=Listarg[0:le-1]

    return args

 

def addS(arg,args):

    Listargs=list(args)

    for i in arg:

        Listargs.append(i)

    return  Listargs

 

#strString=input("请输入要分析的符号串:")#输入串

strString="i*i+i"

str=list(strString)

str.append("$")

 

COUNT=0#步骤

 

#符号栈

S=["$"]

S.append(VN[0])

# print(S)

CSRight=""

print("步骤\t符号栈S[i]\t\t\t输入串str[j]\t\t\t产生式")

while len(S)!=0:

    COUNT += 1

    ch = str[0]

    CSRight0 = findCSS(S[-1], str[0])

    if CSRight0!="" and S[-1] not in VT:

        print("%s\t%s\t\t\t\t%s\t\t\t\t%s" % ( COUNT, "".join(S).center(10), "".join(str).center(10),S[-1] + "->" +CSRight0))

    elif  CSRight0 in VT or S[-1]  in VT:

        print("%s\t%s\t\t\t\t%s" % ( COUNT, "".join(S).center(10), "".join(str).center(10)))

    elif CSRight0=="":

        print("\033[0;31m%s\033[0m" % "该句子不是该文法!")

        break

    CHS = S.pop()

    CSRight = findCSS(CHS, str[0])

    if CHS not in VT  :#如果栈顶元素不是终结字符

        if CSRight!="":

            if CSRight[0] in VN :

                temp = convertStr(CSRight)

                S = addS(temp,S)

            elif CSRight[0] in VT and CSRight[0]!="e":

                temp = convertStr(CSRight)

                S = addS(temp, S)

            elif CSRight[0]=="e" and CSRight[0] in VT:

                pass

        elif CSRight=="":

            pass

    elif CHS in VT:

        if ch==CHS:

            if CHS=="$":

                # print("该句子属于该文法!")

                print("\033[1;31;40m该句子属于该文法!\033[0m")

            elif CHS!="$":

                str = str[1:]

        elif ch!=CHS:

            if CHS=="$":

                print("\033[0;31m%s\033[0m" % "该句子不是该文法!")

                pass

2 LR(0)文法

2.1 LR(0)代码

#使用已经建立好的分析表

 

action={0: {'i': 's5', '(': 's4'},

 1: {'+': 's6', '$': 'acc'},

 2: {'+': 'r2', '*': 's7', ')': 'r2', '$': 'r2'},

 3: {'+': 'r4', '*': 'r4', ')': 'r4', '$': 'r4'},

 4: {'i': 's5', '(': 's4'},

 5: {'+': 'r6', '*': 'r6', ')': 'r6', '$': 'r6'},

 6: {'i': 's5', '(': 's4'},

 7: {'i': 's5', '(': 's4'},

 8: {'+': 's6', ')': 's11'},

 9: {'+': 'r1', '*': 's7', ')': 'r1', '$': 'r1'},

 10: {'+': 'r3', '*': 'r3', ')': 'r3', '$': 'r3'},

 11: {'+': 'r5', '*': 'r5', ')': 'r5', '$': 'r5'}}

goto={0: {'E': 1, 'T': 2, 'F': 3},

 4: {'E': 8, 'T': 2, 'F': 3},

 6: {'T': 9, 'F': 3},

 7: {'F': 10}}

 

gramma=open('wx.txt').readlines()

i=0

while i<len(gramma): #去掉'->'符号

    gramma[i]=gramma[i][0:1]+gramma[i][3:len(gramma[i])-1]

    i+=1

 

#sen='i*i+i$'

sen='i*+i$'

 

 

 

ip=0#sen的指针

stack=['$',0]

a=sen[ip]

while True:

    print(stack, end="")

    print("\t",end="")

    s=stack[len(stack)-1] #s为栈顶状态

    if  a in action[s]:#存在action[s][a]

        if action[s][a]=='acc': #若为acc 则成功,并结束

           

            print("\033[1;31;40mAccept,该句子属于该文法!\033[0m")

            break

        elif action[s][a][0]=='s':#若为 si 则把a和i依次入栈 a指向下一个

            print('移进'+action[s][a])

            t=int(action[s][a][1])

            stack.append(a)

            stack.append(t)

            ip+=1

            a=sen[ip]

        elif action[s][a][0]=='r':#若为ri i为第i个文法(gramma[i-1]) 则栈回退2*gramma[i-1]表达式表达式右端的长度

            print('规约'+gramma[int(action[s][a][1])-1][0:1]+'->'+gramma[int(action[s][a][1])-1][1:])

            size=len(gramma[int(action[s][a][1])-1])-1 #rj 对应的第j个产生式右端的长度

            j=0

            while j<2*size:

                stack.pop()

                j+=1

            t=stack[len(stack)-1]#t为现在栈的状态

            stack.append(gramma[int(action[s][a][1])-1][0])#表达式左端入栈

            stack.append(goto[t][gramma[int(action[s][a][1])-1][0]])#goto[t,表达式左端]入栈

    else:

            #调用错误恢复例程

            print('error')

            break;

2.2 wx.txt附件

*注:最后一行有换行。

E->E+T

E->T

T->T*F

T->F

F->(E)

F->i

 

3  LR(1)代码

#include<stdio.h>

#include<string.h>

char *action[10][3]= {"S3#","S4#",NULL,           /*ACTION表*/

                      NULL,NULL,"acc",

                      "S6#","S7#",NULL,

                      "S3#","S4#",NULL,

                      "r3#","r3#",NULL,

                      NULL,NULL,"r1#",

                      "S6#","S7#",NULL,

                      NULL,NULL,"r3#",

                      "r2#","r2#",NULL,

                      NULL,NULL,"r2#"

                     };

int goto1[10][2]= {1,2,                                               /*QOTO表*/

                   0,0,

                   0,5,

                   0,8,

                   0,0,

                   0,0,

                   0,9,

                   0,0,

                   0,0,

                   0,0

                  };

char vt[3]= {'a','b','#'};                       /*存放非终结符*/

char vn[2]= {'S','B'};                           /*存放终结符*/

char *LR[4]= {"E->S#","S->BB#","B->bB#","B->a#"};/*存放产生式*/

int a[10];

char b[10],c[10],c1;

int top1,top2,top3,top,m,n;

int main()

{

       int g,h,i,j,k,l,p,y,z,count;

       char x,copy[10],copy1[10];

       top1=0;

       top2=0;

       top3=0;

       top=0;

       a[0]=0;

       y=a[0];

       b[0]='#';

       count=0;

       z=0;

       printf("请输入表达式\n");//abb#

       do {

              scanf("%c",&c1);

              c[top3]=c1;

              top3=top3+1;

       } while(c1!='#');

       printf("步骤\t状态栈\t\t符号栈\t\t输入串\t\tACTION\tGOTO\n");

       do {

              y=z;

              m=0;

              n=0;                      /*y,z指向状态栈栈顶*/

              g=top;

              j=0;

              k=0;

              x=c[top];

              count++;

              printf("%d\t",count);

              while(m<=top1) {

                     /*输出状态栈*/

                     printf("%d",a[m]);

                     m=m+1;

              }

              printf("\t\t");

              while(n<=top2) {

                     /*输出符号栈*/

                     printf("%c",b[n]);

                     n=n+1;

              }

              printf("\t\t");

              while(g<=top3) {

                     /*输出输入串*/

                     printf("%c",c[g]);

                     g=g+1;

              }

              printf("\t\t");

              while(x!=vt[j]&&j<=2) j++;

              if(j==2&&x!=vt[j]) {

                     printf("error\n");

                     return 0;

              }

 

              if(action[y][j]==NULL) {

                     printf("error\n");

                     return 0;

              }   else    strcpy(copy,action[y][j]);

              if(copy[0]=='S') {

                     /*处理移进*/    z=copy[1]-'0';

                     top1=top1+1;

                     top2=top2+1;

                     a[top1]=z;

                     b[top2]=x;

                     top=top+1;

                     i=0;

                     while(copy[i]!='#') {

                            printf("%c",copy[i]);

                            i++;

                     }

                     printf("\n");

              }

              if(copy[0]=='r') {

                     /*处理归约*/    i=0;

                     while(copy[i]!='#') {

                            printf("%c",copy[i]);

                            i++;

                     }

                     h=copy[1]-'0';

                     strcpy(copy1,LR[h]);

                     while(copy1[0]!=vn[k]) k++;

 

                     l=strlen(LR[h])-4;

                     top1=top1-l+1;

                     top2=top2-l+1;

                     y=a[top1-1];

                     p=goto1[y][k];

                     a[top1]=p;

                     b[top2]=copy1[0];

                     z=p;

                     printf("\t");

                     printf("%d\n",p);

              }

       } while(action[y][j]!="acc");

       printf("恭喜你,分析成功!\n");

       return 0;

}

猜你喜欢

转载自blog.csdn.net/weixin_43442778/article/details/114971767