这篇文章是因为前一篇文章c++版好多缺陷,而选择用python实现词法分析器于语法分子整合,python操作便捷,对字符串处理灵活宽松,选择python,珍爱生命!!!

c++版本:
编译原理预测分析表一篇解决你所有问题(c++版)

实验 预测分析表方法

一、实验目的

理解预测分析表方法的实现原理。

二、实验内容

编写一通用的预测法分析程序,要求有一定的错误处理能力,出错后能够使程序继续运行下去,直到分析过程结束。可通过不同的文法(通过数据表现)进行测试。

三、实验内容提示

1.算法数据构造:

构造终结符数组:char Vt[10][5]={“id”,”+”……};
构造非终结符数组:char Vn[10]={ };
构造follow集数组:char *follow[10][10]={ } (可将follow集与预测分析表合并存放,可省略,直接给出分析表。)
数据构造示例(使用的预测分析表构造方法1):

/*data1.h简单算术表达式数据*/
char	VN[10][5]={"E","E'","T","T'","F"}; //非终结符表
int		length_vn=5;   //非终结符的个数

char	VT[15][5]={"id","+","*","(",")","#"};  //终结符表
int		length_vt=6; //终结符的个数

char	Fa[15][10]={"TE'","+TE'","","FT'","*FT'","","(E)","id"};  
//产生式表:E->TE' 1:E'->+TE' 2:E'->空 
// 3:T->FT' 4:T'->*FT' 5:T'->空 6:F->(E) 7:F->id

int		analysis_table[10][11]={0,-1,-1,0,-2,-2,0,0,0,0,0,
							-1,1,-1,-1,2,2,0,0,0,0,0,
							3,-2,-1,3,-2,-2,0,0,0,0,0,
							-1,5, 4,-1,5, 5,0,0,0,0,0,
							7,-2,-2,6,-2,-2,0,0,0,0,0};
//预测分析表,-1表示出错,-2表示该行终结符的follow集合,用于错误处理

(1) 预测分析表的构造方法1
给文法的正规式编号:存放在字符数组中,从0开始编号,正规式的编号即为该正规式在数组中对应的下标。
构造正规式数组:char P[10][10]={“E->TE’”,”E’->+TE’”,………}; (正规式可只存储右半部分,如E->TE’可存储为TE’ ,正规式中的符号可替换,如可将E’改为M )
构造预测分析表:int analyze_table[10][10]={ } //数组元素值存放正规式的编号,-1表示出错
(2)预测分析表的构造方法2
可使用三维数组
Char analyze_table[10][10][10]={ }

Char *analyze_table[10][10][10]={ }

2.针对预测分析表构造方法1的查找方法提示:

(1) 查非终结符表得到非终结符的序号no1
(2) 查终结符表得到终结符的序号no2
(3) 根据no1和no2查正规式表得到对应正规式的序号no3=analyze_table[no1][no2] ,如果no3=-1 表示出错。
(4) 根据no3查找对应的正规式P[no3]
(5) 对正规式进行处理

3.错误处理机制

紧急方式的错误恢复方法(抛弃某些符号,继续向下分析)
(1)栈顶为非终结符A,串中当前单词属于FOLLOW(A),则从栈中弹出A(此时可认为输入串中缺少A表示的结构),继续分析。 ---------错误编号为1
(2)栈顶为非终结符A,串中当前单词不属于FOLLOW(A),则可使串指针下移一个位置(认为输入串中当前单词多余),继续分析。----------错误编号为2
(3)栈顶为终结符,且不等于串中当前单词,则从栈中弹出此终结符(认为输入串中缺少当前单词)或者将串指针下移一个位置(认为串中当前单词多余)。在程序中可选择上述两种 观点中的一种进行处理。-------------错误编号3
因此error()函数的编写方式可按如下方式处理

Error(int  errornum)
{
If(errornum==1)………………
Else if(errornum==2)……………
Else ………………..
//或者可用choose case语句处理
}

4.增加了错误处理的预测分析程序预测分析程序的算法:

将“#”和文法开始符依次压入栈中;              
把第一个输入符号读入a;
do{
把栈顶符号弹出并放入x中;
if(x∈VT)
{
if(x==a)  将下一输入符号读入a;
else error(3 );
}
else
if(M[x,a]=“x→y1y2…yk”)
{
按逆序依次把yk、yk−1、…、y1压入栈中;
输出“x→y1y2…yk”;
}
else if  afollow(x)error(1 );  else  error(2);
}while(x!=“#”)

三.实验要求
给定算术表达式文法,编写程序。
测试数据:
1.算术表达式文法

E→TE’
E’ → +TE’|- TE’|ε
T→FT’
T’ →*FT’ |/ FT’ |%FT’|ε
F→(E) |id|num

给定一符合该文法的句子,如id+id*id#,运行预测分析程序,给出分析过程和每一步的分析结果。
输出形式参考下图:

四、编写实验报告

实验分析:

1.将原算术表达式方法改写为LL(1)文法为:

E→TE’
E’ → +TE’|- TE’|ε
T→FT’
T’ →*FT’ |/ FT’ |%FT’|ε
F→(E) |id|num

2.求出每个非终结符的FIRST和FOLLOW集;

非终结符 FIRET FOLLOW
E { (,id,num } { #,) }
E’ { +,-,ε } { #,) }
T {(,id,num} { +,-,#,) }
T’ { *,/,%,ε } { +,-,#,) }
F { (,id,num } { *,/,%,+,-,# }

3.构造预测分析表

坐标 0 1 2 3 4 5 6 7 8 9
非\终结符 * / % + - ( ) id num #
0 E E→ TE’ E→ TE’ E→ TE’
1 E’ E’ → +TE’ E’ → -TE’ E’ → ε E’ → ε
2 T T→FT’ T→FT’ T→FT’
3 T’ T’ →*FT’ T’ →/FT’ T’ →%FT’ T’ →ε T’ →ε T’ →ε T’ →ε
4 F F→(E) F→id F→num

4.绘制编程流程图

5.python代码实现

首先构建词法分析器:
创建 词法分析.py,代码如下

import re
class Token(object):

    # 初始化
    def __init__(this):
        # 存储分词的列表
        this.results = []

        # 行号
        this.lineno = 1

        # 关键字
        this.keywords = ['auto', 'struct', 'if', 'else', 'for', 'do', 'while', 'const',
                         'int', 'double', 'float', 'long', 'char', 'short', 'unsigned',
                         'switch', 'break', 'de***t', 'continue', 'return', 'void', 'static',
                         'auto', 'enum', 'register', 'typeof', 'volatile', 'union', 'extern']
        ''' regex中:*表示从0-, +表示1-, ?表示0-1。对应的需要转义 { 表示限定符表达式开始的地方 \{ () 标记一个子表达式的开始和结束位置。子表达式可以获取共以后使用:\( \) r表示原生字符串。 '''

        Keyword = r'(?P<Keyword>(auto){1}|(double){1}|(int){1}|(if){1}|' \
                  r'(#include){1}|(return){1}|(char){1}|(stdio\.h){1}|(const){1})'
        # 运算符
        Operator = r'(?P<Operator>\+\+|\+=|\+|--|-=|-|\*|#|\*=|/=|/|%=|%)'

        # 分隔符/界符
        Separator = r'(?P<Separator>[,:\{}:)(<>])'

        # 数字: 例如:1 1.9
        Number = r'(?P<Number>\d+[.]?\d+)'

        # 变量名 不能使用关键字命名
        ID = r'(?P<ID>[a-zA-Z_][a-zA-Z_0-9]*)'

        # 方法名 {1} 重复n次
        Method = r'(?P<Method>(main){1}|(printf){1})'

        # 错误 \S 匹配任意不是空白符的字符
        # Error = r'(?P<Error>.*\S+)'
        Error = r'\"(?P<Error>.*)\"'

        # 注释 ^匹配行的开始 .匹配换行符以外的任意字符 \r回车符 \n换行符
        Annotation = r'(?P<Annotation>/\*(.|[\r\n])*/|//[^\n]*)'

        # 进行组装,将上述正则表达式以逻辑的方式进行拼接, 按照一定的逻辑顺序
        # compile函数用于编译正则表达式,生成一个正则表达式对象
        this.patterns = re.compile('|'.join([Annotation, Keyword, Method, ID, Number, Separator, Operator, Error]))

    # 读文件
    def read_file(this, filename):
        with open(filename, "r",encoding="UTF-8") as f_input:
            return [line.strip() for line in f_input]

    # 结果写入文件
    def write_file(this, lines, filename='results.txt'):
        with open(filename, "a") as f_output:
            for line in lines:
                if line:
                    f_output.write(line)
                else:
                    continue

    def get_token(this, line):

        # finditer : 在字符串中找到正则表达式所匹配的所有字串, 并把他们作为一个迭代器返回
        for match in re.finditer(this.patterns, line):
            # group():匹配的整个表达式的字符 # yield 关键字:类似return ,返回的是一个生成器,generator
            yield (match.lastgroup, match.group())

    def run(this, line, flag=True):
        tokens =[]
        for token in this.get_token(line):
            if flag:
                tokens.append(token)
                print("line %3d :" % this.lineno, token)
            ''' else: yield "line %3d :" % this.lineno + str(token) + "\n" '''
        return tokens

    def printrun(this, line, flag=True):
        for token in this.get_token(line):
            if flag:
                print("lines x: ", token)

def main() :
    token = Token()
    filepath = input("请输入文件路径:")
    print('-' * 20, '词法分析', '-' * 20)
    lines = token.read_file(filepath)
    tokenss = []
    for line in lines:
        tokens = token.run(line, True)
        for i in tokens:
            tokenss.append(i[1])
    return tokenss

然后编写语法分析:
新建 语法分析.py,代码如下

import 词法分析

class Stack():   #定义类
	def __init__(self):  #产生一个空的容器
		self.__list = []
	def push(self, item):  #入栈
		self.__list.append(item)
	def pop(self):  #出栈
		return self.__list.pop()
	def speek(self):  #返回栈顶元素
		return self.__list[-1]
	def is_empty(self):  #判断是否已为空
		return not self.__list
	def size(self):  #返回栈中元素个数
		return len(self.__list)

# 用来存储从txt文件中读取的字符串
aa=" ";
# 用来标记字符
pp=0;
# 非终结符表
VN = ["E", "e", "T", "t", "F"];
# 终结符表 l->/ m->% i->id n->num
VT=['*','l','m','+','-','(',')','id','num','#'];

Fa=["Te","+Te","-Te","","Ft","*Ft","nFt","mFt","","(E)","id","num"];

F=["E->","E'->","E'->","E'->","T->","T'->","T'->","T'->","T'->","F->","F->","F->"];
# 构造预测分析表,-1表示出错,-2表示该行终结符的follow集合,用于错误处理
analysis_table=[
    [-2,-2,-2,-2,-2,0,-1,0,0,-1],
    [-2,-2,-2,1,2,-2,3,-2,-2,3],
    [-2,-2,-2,-1,-1,4,-1,4,4,-1],
    [5,6,7,8,8,-2,8,-2,-2,8],
    [-2,-2,-1,-1,-1,9,-1,10,11,-2]
];
# 定义栈
stack = Stack()
# 判断字符x是否是终结符
def includevt(x):
    if x in VT:
        return 1
    else:
        return 0
# 查找非终结符,终结符 在预测分析表中的坐标,返回坐标对应信息
def includean(x, a):
    # 非终结符
    for i in range(len(VN)):
        if VN[i] == x:
            break;
    # 终结符
    for j in range(len(VT)):
        if VT[j] == a:
            break;
    return analysis_table[i][j]

# 错误处理
def destory():
    flag = 0;
    flagg = 0;
    # 将 "#"和文法开始符依次压入栈中
    stack.push('#')
    # 将第一个非终结符入栈
    stack.push(VN[0])
    # 把第一个输入符号读入a, aa
    global pp
    a=aa[pp]
    x=""
    while x!='#':
        # print(stack.__dict__.values())
        if flag == 0:
            # 把栈顶符号弹出并放入
            x = stack.pop()
        flag = 0
        # 如果a是终结符
        if includevt(a) == 1:
            if includevt(x) == 1:
                if a==x:
                    if a=='#':
                        flagg=1
                        print(x, "\t\t", a, "\t\t"," 结束")
                        return 1
                    else:
                        print(x, "\t\t", a, "\t\t"," 匹配终结符", x)
                    pp=pp+1;
                    #将下一输入符号读入a;
                    a = aa[pp];
                else:
                    flag = 1
                    print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
                    pp =pp +1
                    a = aa[pp]

            elif includean(x,a) >= 0:
                h = includean(x, a);
                print(x, "\t\t", a, "\t\t", " 展开非终结符 ", F[h], Fa[h])
                if Fa[h] in aa:
                    stack.push(Fa[h])
                else:
                    aF = Fa[h][::-1]
                    for i in range(len(aF)):
                        stack.push(aF[i])
            elif includean(x, a) == -1:
                flag = 1
                print(x, "\t\t", a, "\t\t" , " 出错 ,从栈顶弹出", x)
                x = stack.pop()
            else:
                flag = 1
                print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
                pp = pp +1
                a = aa[pp]
        else:
            flag = 1
            print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
            pp = pp + 1
            a = aa[pp]
    if flagg == 0:
        print(x,"\t\t",a,"\t\t"," 结束 \n")

aa=词法分析.main()
print("语法分析工程如下 :")
print('-'*20,'文法如下','-'*20)
print("E->TE'")
print("E'->+TE'|-TE'|~")
print("T->FT'")
print("T'->*FT'|/FT'|%FT'|~")
print("F->(E)|id|num")
print("要分析的语句是 :",aa)
print("语法分析工程如下 :")
print("栈顶元素 \t\t 当前单词记号 \t\t 动作 ")
print("-"*50)
destory()

6.特别注意

项目目录要自己建立读取文件:

7.测试结果


8.实验总结

之前做了一个版本的c++版,但是存在一些问题,大多数问题都是可以通过整合词法分子器可以解决的,比如数id+id*id#,我们就可以将其用词法分析器进行分析,分析结果为
[‘id’, ‘+’, ‘id’, ‘*’, ‘id’, ‘#’]
这样,我们再进行处理时就方便多了。