本文内容:
本文章实现的文法:
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:a + b + 1] # 拷贝字符
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 - 1)
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就行。如果没找到的话,抛出错误。特别要注意,算选择集别把的句子终结符(#)忘了