python 递归下降分析法的设计与实验原理 编译原理

本文介绍了如何使用Python实现对LL(1)文法的递归下降分析,包括消除左递归和提取左因子的文法转换,以及词法分析和解析表达式的程序设计。程序通过读取文本文件中的表达式,进行词法分析和递归下降解析,判断表达式是否符合文法规则,并对错误给出反馈。
摘要由CSDN通过智能技术生成

本文内容:

本文章实现的文法:

E->T|E+T;
T->F|T*F;
F->i|(E);

利用上一篇文章:python 预备实验2 LL(1)文法构造转化后的输出:

E->TE';
T->FT';
F->i|(E);
E'->+TE'|;
T'->*FT'|;

手工测试,是LL(1)文法下面第二列是首符集,第三列是后继符集

# 经过处理后的文法如下:
# E->TE';       i,(     ),;
# T->FT';       i,(     +,),;
# F->i|(E);     i,(     *,+,),;
# E'->+TE'|;    +,e     ),;
# T'->*FT'|;    *,e     +,),;
# 由于函数名不能有‘所以里面的’由1代替

1)手工将测试的表达式写入文本文件,每个表达式写一行,用“;”表示结束;

2)读入文本文件中的表达式;

3)调用实验一中的词法分析程序搜索单词;

4)把单词送入递归下降分析程序,判断表达式是否正确,若错误,应给出错误信息;

样例:

相关程序:       

        python 预备实验2 LL(1)文法构造                # 将文法消除左递归,提取左因子

        python 词法分析程序的设计 编译原理        # 从文件中提取各个元素,代码中scan部分

 代码如下:(直接从107行开始看,嵌满的是词法分析部分)

# scan 部分=============================================================================
# =====================================================================================
# 输出结果
def output(str, a, b, type):
    global program
    program.append([type, str[a:b + 1]])


# 判断字符串一部分是否属于关键字
# 是返回1不是返回2
def iskeywords(str, a, b):
    # 关键字
    keywords = {"if", "int", "for", "while", "do", "return", "break", "continue"}
    s = str[a:b]  # 拷贝字符
    if s in keywords:  # 判断是否存在,存在返回1,否则返回2
        return 1
    else:
        return 2


# 判断字符是否属于运算符或分隔符的一部分。
# 不是返回0,是返回1,是且后面能跟=号返回2
def belong_to(str, type):
    if type == 4:  # 选择运算符
        library = "+-*/=><!"  # 运算符
    else:  # 选择分隔符
        library = ",;{}()"  # 分隔符
    if str in library:  # 存在
        # 是可能后面跟=号的几个符号
        if type == 4 and library.index(str) >= 4:
            return 2
        else:
            return 1
    return 0


# 递归的词法分析函数,读入一行str字符串,初始位置 n = 0
# 分离+判断,打印输出类型
# 由之前的c语言版本改写而成
def scan(str, n):
    # 7 种类型(最后输出1 - 5)
    # -1
    # 0: 初始
    # 1: 关键字, 在keywords中
    # 2: 标识符
    # 3: 常数(无符号整型)
    # 4: 运算符和界符:+ - * / = > < >= <= !=
    # 5: 分隔符:, ; {}()
    i = n
    type = 0
    while i < len(str):
        if type == 0:  # 初始态
            if str[i] == ' ':  # 空格跳过
                n += 1
                i += 1
                continue
            elif str[i] == '\0' or str[i] == '\n':  # 是结束
                return
            elif ('a' <= str[i] <= 'z') or ('A' <= str[i] <= 'Z'):
                type = 1  # 是字母,
            elif '0' <= str[i] <= '9':
                type = 3  # 是数字,常数
            else:
                type = belong_to(str[i], 4)
                if type > 0:  # 是运算符
                    # 是能跟=号的运算符,后面是=号
                    if type == 2 and str[i + 1] == '=':
                        i = i + 1  # 结束位置后移
                    output(str, n, i, 4)  # 输出 + 递归 + 结束
                    scan(str, i + 1)
                    return
                elif belong_to(str[i], 5):  # 是分隔符
                    output(str, n, i, 5)  # 输出 + 递归 + 结束
                    scan(str, i + 1)
                    return
                else:
                    print("失败:", str[i])
                    return
        elif type == 1:  # 关键字或标识符
            if not (('a' <= str[i] <= 'z') or ('A' <= str[i] <= 'Z')):  # 不是字母了
                if '0' <= str[i] <= '9':  # 是数字,只能是标识符
                    type = 2
                else:  # 非字母数字
                    type = iskeywords(str, n, i)
                    output(str, n, i - 1, type)  # 输出 + 递归 + 结束
                    scan(str, i)
                    return
        elif type == 2:  # 标识符
            if not (('a' <= str[i] <= 'z') or ('A' <= str[i] <= 'Z')):
                # 不是字母了
                if not ('0' <= str[i] <= '9'):
                    # 不是数字
                    output(str, n, i - 1, type)  # 输出 + 递归 + 结束
                    scan(str, i)
                    return
        elif type == 3:
            if not ('0' <= str[i] <= '9'):
                # 不是数字
                output(str, n, i - 1, type)  # 输出 + 递归 + 结束
                scan(str, i)
                return
        else:
            print("%d失败" % type)
        i += 1


# 递归下降分析程序部分=====================================================================
# =====================================================================================
# 经过处理后的文法如下:
# E->TE';       i,(     ),;
# T->FT';       i,(     +,),;
# F->i|(E);     i,(     *,+,),;
# E'->+TE'|;    +,e     ),;
# T'->*FT'|;    *,e     +,),;
# 由于函数名不能有‘所以里面的’由1代替
def Parse():
    def ParseE():  # E的分析子程序 E->TE';    i,(     ),;
        global lookahead, parseerror
        if parseerror:
            return
        elif lookahead[0] == 2 or lookahead[0] == 3 or lookahead[1] == '(':
            ParseT()
            ParseE1()
        else:
            print("E 错误")
            parseerror = 1
            # exit(0)

    def ParseT():  # T的分析子程序 T->FT';    i,(     +,),;
        global lookahead, parseerror
        if parseerror:
            return
        elif lookahead[0] == 2 or lookahead[0] == 3 or lookahead[1] == '(':
            ParseF()
            ParseT1()
        else:
            print("T 错误")
            parseerror = 2
            # exit(0)

    def ParseF():  # F的分析子程序 F->i|(E);  i,(     *,+,),;
        global lookahead, parseerror
        if parseerror:
            return
        elif lookahead[0] == 2 or lookahead[0] == 3:
            MatchToken('i')
        elif lookahead[1] == '(':
            MatchToken('(')
            ParseE()
            MatchToken(')')
        else:
            print("F 错误")
            parseerror = 3
            # exit(0)

    def ParseE1():  # E'的分析子程序 E'->+TE'|;   +,e     ),;
        global lookahead, parseerror
        if parseerror:
            return
        elif lookahead[1] == '+':
            MatchToken('+')
            ParseT()
            ParseE1()
        elif lookahead[1] == ')' or lookahead[1] == ';':
            pass
        else:
            print("E' 错误")
            parseerror = 4
            # exit(0)

    def ParseT1():  # T'的分析子程序 T'->*FT'|;   *,e     +,),;
        global lookahead, parseerror
        if parseerror:
            return
        elif lookahead[1] == '*':
            MatchToken('*')
            ParseF()
            ParseT1()
        elif lookahead[1] == '+' or lookahead[1] == ')' or lookahead[1] == ';':
            pass
        else:
            print("T' 错误")
            parseerror = 5
            # exit(0)

    def MatchToken(type):
        global lookahead, parseerror
        mate = 0
        if parseerror:
            return
        elif type == "i":  # 匹配常数或表达式
            if lookahead[0] == 2 or lookahead[0] == 3:
                mate = 1
        else:  # 匹配符号
            if lookahead[1] == type:
                mate = 1
        if mate:
            lookahead = GetToken()  # 读入下一个
        else:
            print("需要",type, "实际", lookahead, "匹配错误")
            parseerror = 6
            # exit(0)

    def GetToken():
        global program, lookahead
        return program.pop(0)

    global program, lookahead,parseerror
    parseerror = 0  # 错误标记
    lookahead = program.pop(0)
    ParseE()
    if parseerror==0:
        print("正确")


file = "program.txt"
file = open(file)  # 读取文件
while i := file.readline():
    program = []  # 记录读到的句子
    scan(i, 0)
    print(i[:-1])
    Parse()
file.close()

program.txt文件部分:

10;
1+2;
(1+2)*3+(5+6*7);
((1+2)*3+4;
1+2+3+(*4+5);
(a+b)*(c+d);
((ab3+de4)**5)+1;

 结果:

10;
正确
1+2;
正确
(1+2)*3+(5+6*7);
正确
((1+2)*3+4;
需要 ) 实际 [5, ';'] 匹配错误
1+2+3+(*4+5);
E 错误
(a+b)*(c+d);
正确
((ab3+de4)**5)+1
F 错误

 小计:关于递归下降LL(1)分析程序的写法:

        在LL(1)递归下降分析程序中,每个非终结符都有一个独属的分析子程序,比如这篇文章的文法,就有多达5个非终结分析子程序,1个终结符分析程序,因为每一个非终结符的程序都是独立写出来的,所以这种方法对于每一种新的文法都要重写。(相比之下,表驱动的LL(1)文法分析程序就友好很多,但这里不说)

        首先我们要求要写文法的select集(选择集),判别是不是LL(1)文法,若是,就可以进行下一步(不是LL(1)文法的话,消除左递归,提取左因子,还不行就没法用这个方法。)

        开始,有一个指针(变量,这里是lookahead),指向(保存着)当前要分析的词汇,在这篇文章中,它是[type,data]形式的,type是类型,我们只需知道2是标识符,3是常量,data保存着具体的字符,比如“(”,“15”

        针对所有终结符,有一个统一的判断函数(MatchToken),输入要判断的符号类型,然后判断当前指针指向的变量是不是,如果不是,就抛出错误,结束分析,否则就指针右移。

        针对每一个非终结符,有自己专属的分析程序,这篇文章的命名统一为Parse+非终结符,假如,有一个产生式:A   ->    Ba | b | ε        #这里ε代表空

       select(A->Ba)=  a,d        select(A->b)= b        select(A->ε)= e      

 (select(A->Ba)和 select(A->b),select(A->ε)不可能有交集,否则就不是LL(1)文法了)

那么,他的分析程序就长这样:

def ParseA():
    if lookahead == 'a' or lookahead == 'd':
        ParseB()
        MatchToken('d')
    elif lookahead == 'b':
        MatchToken('b')
    elif lookahead == 'e':
        pass
    else:
        (报错)

        每次判断lookahead是不是这个产生式的选择集,是的话,就按它的产生式,有非终结符就调用非终结符的分析程序,有终结符就调用终结符的判断程序(MatchToken),空的话没有,就直接pass就行。如果没找到的话,抛出错误。特别要注意,算选择集别把的句子终结符(#)忘了

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值