实验要求
-
构造“某程序设计语言子集”的语法分析器。
-
语法分析方法:自选(自上而下语法分析或自下而上语法分析)。
-
语言成分:算术表达式、赋值语句、分支语句(if-then,if-then-else)、循环语句(while-do)。其中,分支语句、循环语句的条件式只需要布尔型变量或常量;分支语句、循环语句不必嵌套(例如,if 语句中不嵌套 if 语句)。
-
输入方式:文本文件(如:。 txt) .
-
输出内容及输出方式:如果输入串是句子,输出是“什么”句子;若有语法错误,指出错误位置及错误原因。可以直接在监视器上显示结果,也可以将结果输出到文件中。
-
语法分析中,调用词法分析(第 1 次集中上机的任务所对应的程序)的结果,以获取每一个单词。
-
测试程序样例:
序号 | 输入程序 | 输出结果 |
1 | j:=k+j*m; | 赋值语句 |
2 | if x then j:=j+1; | if-then 分支语句,嵌套赋值语句 |
3 | if x then j:=j+1 else j:=j+2; | if-then-else 分支语句,嵌套赋值语句 |
4 | while x do j:=j+2; | while-do 循环语句,嵌套赋值语句 |
5 | j:=i*+1; | 语法错误,位置… |
6 | if x then; | 语法错误,位置… |
提交结果:
实验报告(电子版,在思源空间提交)
包括:该程序设计语言文法的语法分析程序、关键算法的文字解释、与词法分析程序的衔接、测试样例(输入/输出)、实验总结。
前期工作
该 part 为语法分析前的必备工作阐述,包括第一次实验的词法分析改造,于词法分析程序的衔接
在第一个实验中,我们完成了一个词法分析器,我们简单阐述其作用:
当我们给定字符串时,会返回一个列表,列表的元素为 Token 对象,包括符号 ,识别符 ,识别码。
因此,为了后面的语法分析,即 LL(1)文法分析,我们需要把列表修改为栈。这样就可以保证获取到每一个列表对象,然后可以用来分析,下面是关键衔接代码
def getNextToken(self):
if self.index < len(self.tokens):
token = self.tokens[self.index]
self.index += 1
return token
else:
return Token('', 'FILE-END', 0)
def getNowToken(self):
if self.index < len(self.tokens):
token = self.tokens[self.index]
return token
else:
return Token('', 'FILE-END', 0)
这就将列表改为了栈,需要注意一点:当遍历完数组时,我们需要返回一个Token('', 'FILE-END', 0)
对象,来保证栈可以正常分析!
LL(1)文法分析
原理
LL(1)文法是一种特殊的上下文无关文法,它满足以下两个条件:
-
左递归(Left Recursive)的消除:该文法中不存在任何形如 A->Aα的产生式,即文法不存在左递归。
-
预测性(Predictive)分析:对于文法中任何一个非终结符 A 和终结符 a,当 A 推导出 a 时,都能够在查看下一个输入符号 a 的情况下唯一确定应该使用哪一个产生式。
满足这两个条件的上下文无关文法就是 LL(1)文法。
LL(1)文法具有以下特点:
-
可以通过预测性分析算法构造出相应的 LL(1)分析器。
-
分析过程从左到右扫描输入串,同时从左到右推导。
-
每一步仅需查看当前输入符号就可以确定下一步使用哪个产生式。
构造
LL(1)文法分析表是实现 LL(1)分析器的核心数据结构。它是一个二维表,行对应非终结符,列对应终结符。
构造 LL(1)分析表的步骤如下:
-
计算每个非终结符的 first 集合和 follow 集合。
-
first(A) 是从 A 出发能推导出的所有终结符的集合。
-
follow(A) 是可能出现在 A 后面的所有终结符的集合。
-
-
根据文法的产生式,填写分析表的内容:
-
如果 A → a 是一个产生式,且 a 在 first(A) 中,则在 M[A, a] 中放置 A → a。
-
如果 ε 在 first(A) 中,则对于所有 b 在 follow(A) 中, M[A, b] 中放置 A → ε。
-
如果存在某个终结符 a,使得 A → a 和 B → a 都在文法中,则该文法不是 LL(1) 文法。
-
至此,我们学习完毕了 LL(1)文法的原理以及文法分析表的构造,下面我们将阐述本次实验的 LL(1)文法
算术表达式 LL(1)文法
通过编译原理的课程学习,我们可以得到如下 LL(1)文法进行算术表达式:
E->TE'
E'->+TE'|-TE'|$$\varepsilon$$
T->FT'
T'->*FT'|/FT'|$$\varepsilon$$
F->(E)|d
我们不难验证,该文法即为包含加减乘除和括号运算的算术表达式 LL(1)文法。
不难求得该文法的文法分析表:
CSDN公式显示有点小问题,不做优化了
+ | - | * | / | d | ( | ) | # | |
---|---|---|---|---|---|---|---|---|
E | E->TE' | E->TE' | ||||||
E' | E'->+TE' | E'->-TE' | E'->$$\varepsilon$$ | E'->$$\varepsilon$$ | ||||
T | T->FT' | T->FT' | ||||||
T' | T'->$$\varepsilon$$ | T'->$$\varepsilon$$ | T'->*FT' | T'->/FT' | T'->$$\varepsilon$$ | |||
F | F->d | F->(E) |
举个例子,当分析器处理到 (d+d)
这个输入串时,它的分析过程如下:
-
首先处理
(
,查找分析表中 F 行(
列的产生式F->(E)
,于是将(E)
压入栈。 -
然后处理
d
,查找分析表中 F 行d
列的产生式F->d
,于是将d
压入栈。 -
接着处理
+
,查找分析表中 E' 行+
列的产生式E'->+TE'
,于是将+TE'
压入栈。 -
再处理
d
,查找分析表中 F 行d
列的产生式F->d
,于是将d
压入栈。 -
最后处理
)
,查找分析表中 F 行)
列的产生式F->)
,于是将)
弹出栈。
代码实现
为了语法分析的方便,我创建了一个父类:
class BaseParser:
"""
基础语法分析器类
"""
flag: bool
token: Token
end : int
def __init__(self, tokenStream, end=-1):
self.tokenStream = tokenStream
self.end = end
self.get_next_token()
def get_next_token(self):
"""
获取下一个token
"""
if self.end > 0:
if self.tokenStream.index == self.end:
self.token = Token('', 'FILE-END', 0)
return
self.token = self.tokenStream.getNextToken()
def handle_syntax_error(self):
"""
处理语法错误
"""
self.flag = False
print(f"第{self.tokenStream.index-1}个词附近出现错误;")
print(f"详细情况: 符号为{self.tokenStream.tokens[self.tokenStream.index-1].lexeme} "
f"类型为{self.tokenStream.tokens[self.tokenStream.index-1].type}")
self.get_next_token()
def find(self, args):
for i in range(self.tokenStream.index, len(self.tokenStream.tokens)):
if self.tokenStream.tokens[i].code in args:
return i
return -1
该类的好处减少了一些函数的重复编写,其作用会在后面的说明和步骤中逐步体现!
-
处理错误的语法可以直接在控制台展示错误
-
get_next_token
通过类变量end
和调用tokenStream.getNextToken()
保证栈的次序!其中 end 变量用来标识需要识别的片段末尾,保证不会超出识别范围导致的错误!
下面给出算术表达式的分析:
class CAL_S(BaseParser):
"""
算术表达式语法类
算术表达式的语法如下:
E->TE'
E'->+TE'|-TE'|e
T->FT'
T'->*FT'|/FT'|e
F->(E)|d
"""
def start(self):
"""
开始分析表达式
"""
self.flag = True
self.__E()
return self.flag
def __E(self):
"""
处理E->TE'
"""
self.__T()
self.__E1()
def __E1(self):
"""
处理E'->+TE'|-TE'|e
"""
if self.token.code in (PLUS, MINUS):
self.get_next_token()
self.__T()
self.__E1()
elif self.token.code not in (RPAREN, 0):
self.handle_syntax_error()
def __T(self):
"""
处理T->FT'
"""
self.__F()
self.__T1()
def __T1(self):
"""
处理T'->*FT'|/FT'|e
"""
if self.token.code in (MULTIPLY, DIVIDE):
self.get_next_token()
self.__F()
self.__T1()
elif self.token.code not in (MINUS, PLUS, RPAREN, 0):
self.handle_syntax_error()
def __F(self):
"""
处理F->(E)|d
"""
if self.token.code in (IDENTIFIER, CONSTANT):
self.get_next_token()
elif self.token.code == LPAREN:
self.get_next_token()
self.__E()
if self.token.code == RPAREN:
self.get_next_token()
else:
self.handle_syntax_error()
else:
self.handle_syntax_error()
本人代码结构清楚,变量规范,可以直接查看无需注释和代码段,无需再阐述过多!
代码实现结果
目前,我们还没有添加文件功能,可以先写一个简单的测试段,控制台输入输出查看代码结果:
if __name__ == '__main__':
while True:
content = input()
ts = TokenStream(content)
print(ts)
cal = CAL_S(ts)
if cal.start():
print("是算术表达式")
else:
print("不是算术表达式")
下面是部分实验(更多可以见附录)
样例1:
输入:y+x*7+o
输出:
(y ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(x ,IDENTIFIER ,1)
(* ,MULTIPLY ,16)
(7 ,CONSTANT ,2)
(+ ,PLUS ,14)
(o ,IDENTIFIER ,1)
是算术表达式
样例2:
输入:66+*9
输出:
(66 ,CONSTANT ,2)
(+ ,PLUS ,14)
(* ,MULTIPLY ,16)
(9 ,CONSTANT ,2)
第2个词附近出现错误;
详细情况: 符号为* 类型为MULTIPLY
第3个词附近出现错误;
详细情况: 符号为9 类型为CONSTANT
不是算术表达式
样例3:
输入:(j+i)*k+((1+1)+2)
输出:
(( ,LPAREN ,18)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(i ,IDENTIFIER ,1)
() ,RPAREN ,19)
(* ,MULTIPLY ,16)
(k ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(( ,LPAREN ,18)
(( ,LPAREN ,18)
(1 ,CONSTANT ,2)
(+ ,PLUS ,14)
(1 ,CONSTANT ,2)
() ,RPAREN ,19)
(+ ,PLUS ,14)
(2 ,CONSTANT ,2)
() ,RPAREN ,19)
是算术表达式
可以看到,在输入样例的算术表达式判断中,该代码表现良好,可以识别出错误所在!
赋值语句
文法重构
前面,我们已经实现了算术表达式的文法,由于本次实验要求特殊,因此,我们可以不用再构造新的文法来表达赋值语句,而在原有的基础上进行改造即可!
在原有的文法上加上如下一句文法,即可标识赋值语句
S->d := E
E->TE'
.....
不再给出构造表了,感兴趣可以自行推导,在代码中已经蕴含了其构造思路!
代码实现
class ASSIGN_S(BaseParser):
"""
赋值语句表达类
表达式如下形式
IDENTIFIER ASSIGN 算术表达式
"""
def start(self):
self.flag = True
# 先判断IDENTIFIER
if self.token.code in (IDENTIFIER,):
self.get_next_token()
else:
self.handle_syntax_error()
return self.flag
# 在判断ASSIGN
if self.token.code in (ASSIGN,):
cal = CAL_S(self.tokenStream, end=self.end)
flag = cal.start()
if flag:
return flag
else:
self.handle_syntax_error()
return self.flag
return self.flag
测试结果
可以和算术表达式的验证代码类似编写,不再给出,可见附录
样例1:
输入:y:=x+10
输出:
(y ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(x ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(10 ,CONSTANT ,2)
赋值语句
样例2:
输入:yy+1 := 9
输出:
(yy ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(1 ,CONSTANT ,2)
(:= ,ASSIGN ,26)
(9 ,CONSTANT ,2)
第1个词附近出现错误;
详细情况: 符号为+ 类型为PLUS
不是赋值语句
至此,完成了赋值语句的编写!
IF 语句
文法重构
前面,我们已经实现了赋值表达式的文法,由于本次实验要求特殊,因此,我们可以不用再构造新的文法来表达赋值语句,而在原有的基础上进行改造即可!
在原有的文法上加上如下语句,即可标识 IF 控制语句
S->IF_S|ASSIGN_S
IF_S->if E then ASSIGN_S IF_M
IF_M->; | else ASSIGN_S;
ASSIGN_S->d := E
E->TE'
.....
但是,需要注意的是,该方法不再满足 LL(1)文法,感兴趣可以自行推导,不过由于实验要求简单,本人采取了展望的策略,完成了类 LL(1)文法的实现,代码实现如下!
代码实现
class IF_S(BaseParser):
"""
IF语句表达类
按照实验报告的要求IF语句满足以下条件:
1.无嵌套
2.if 算术表达式 then 赋值语句 ;
3.if 算术表达式 then 赋值语句 else 赋值语句;
关键技术:
我们使用了向前展望的方式来判断该文法是否满足条件,以此来进行判断,这不是一个很好的方法,
但对于该特殊条件而言,该方法是有效而且快速的
"""
def start(self):
self.flag = True
# 判断if
if self.token.code != IF:
self.handle_syntax_error()
return self.flag
# 判断算术表达式
# 1.先找到THEN
then_pivot = self.find((THEN,))
# 2.然后判断算术表达式
cal = CAL_S(self.tokenStream, then_pivot)
flag = cal.start()
if not flag:
return False
self.get_next_token()
# 判断then
if self.token.code != THEN:
self.handle_syntax_error()
return self.flag
# 判断赋值语句
# 1.先找到ELSE 或 ;
pivot = self.find((ELSE, SEMICOLON))
# 2.进行赋值表达式的判定
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断是否有else语句
if self.token.code == ELSE:
# 判断赋值语句
# 1.先找到 ;
pivot = self.find((SEMICOLON,))
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断结束
if self.token.code != SEMICOLON:
self.handle_syntax_error()
return self.flag
return self.flag
需要注意的是,虽然我们是在 LL(1)文法的基础上进行改进,但是,当遇到控制语句,LL(1)文法不再奏效,但是由于实验要求:其中,分支语句、循环语句的条件式只需要布尔型变量或常量;分支语句、循环语句不必嵌套(例如,if 语句中不嵌套 if 语句)。因此,我采用了展望的方式,具体而言如下:
-
找到 if 语句中的 THEN 进行定位
-
找到 if 语句中的 ELSE 或 ;进行定位
然后,根据定位结果可以提前判断对错,也可以进行构造。虽然时间复杂度上升了,但是由于实验要求简单,而且展望信息一般相邻较近,其速度和准确性依旧有保证!
测试结果
样例1:
输入:if x then y:=99+2;
输出:
(if ,IF ,8)
(x ,IDENTIFIER ,1)
(then ,THEN ,9)
(y ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(99 ,CONSTANT ,2)
(+ ,PLUS ,14)
(2 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
IF语句
样例2:
输入:if y+88 then x:=9292 else y:=(x+1023)*9;
输出:
(if ,IF ,8)
(y ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(88 ,CONSTANT ,2)
(then ,THEN ,9)
(x ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(9292 ,CONSTANT ,2)
(else ,ELSE ,10)
(y ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(( ,LPAREN ,18)
(x ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(1023 ,CONSTANT ,2)
() ,RPAREN ,19)
(* ,MULTIPLY ,16)
(9 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
IF语句
样例3:
输入:if x:=2 then y:=3
输入:
(if ,IF ,8)
(x ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(2 ,CONSTANT ,2)
(then ,THEN ,9)
(y ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(3 ,CONSTANT ,2)
第2个词附近出现错误;
详细情况: 符号为:= 类型为ASSIGN
第3个词附近出现错误;
详细情况: 符号为2 类型为CONSTANT
不是IF语句
至此,完成了控制语句IF的编写
循环语句
文法重构
重构的文法如下:
在原有的文法上加上如下语句,即可标识 WHILE 控制语句
S->IF_S|ASSIGN_S|WHILE_S
WHILE_S->while E do ASSIGN_S;
.....
代码实现
class WHILE_S(BaseParser):
"""
WHILE语句表达类
按照实验报告的要求IF语句满足以下条件:
1.无嵌套
2.while 算术表达式 DO 赋值语句 ;
"""
def start(self):
self.flag = True
# 判断if
if self.token.code != WHILE:
self.handle_syntax_error()
return self.flag
# 判断算术表达式
# 1.先找到DO
then_pivot = self.find((DO,))
# 2.然后判断算术表达式
cal = CAL_S(self.tokenStream, then_pivot)
flag = cal.start()
if not flag:
return False
self.get_next_token()
# 判断赋值语句
# 1.先找到 ;
pivot = self.find((SEMICOLON,))
# 2.进行赋值表达式的判定
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断结束
if self.token.code != SEMICOLON:
self.handle_syntax_error()
return self.flag
return self.flag
测试结果
样例1:
输入:while x do j:=j+2;
输出:
(while ,WHILE ,12)
(x ,IDENTIFIER ,1)
(do ,DO ,11)
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(2 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
WHILE语句
样例2:
输入:while 22 yy:=2
输出:
(while ,WHILE ,12)
(22 ,CONSTANT ,2)
(yy ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(2 ,CONSTANT ,2)
第2个词附近出现错误;
详细情况: 符号为yy 类型为IDENTIFIER
第3个词附近出现错误;
详细情况: 符号为:= 类型为ASSIGN
不是WHILE语句
样例3:
输入:while x0 do y:=8
输出:
(while ,WHILE ,12)
(x0 ,IDENTIFIER ,1)
(do ,DO ,11)
(y ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(8 ,CONSTANT ,2)
第5个词附近出现错误;
详细情况: 符号为8 类型为CONSTANT
不是WHILE语句
文件输入输出
下面实现文件输入输出
代码实现
在LAB1中我们已经实现了文件的输入输出,为了不再占用篇幅,可以在附录代码中查看!
测试代码如下:
if __name__ == '__main__':
fw = fileWriter("../fileSet/output/lab2.txt")
cnt = 1
with open('../fileSet/input/lab2.txt', 'r') as file:
for content in file:
ts = TokenStream(content)
fw.write(f"第{cnt}行")
fw.write(f"输入:{content.strip()}")
fw.write(f"输出:")
fw.write(str(ts).strip())
parse = BEGIN(ts)
fw.write(parse.start())
fw.write("-----------------------------------------")
cnt += 1
展示结果
输入文档:
j:=k+j*m
if x then j:=j+1;
if x then j:=j+1 else j:=j+2;
while x do j:=j+2;
j:=i*+1
if x then;
输出文档:
第1行
输入:j:=k+j*m
输出:
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(k ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(j ,IDENTIFIER ,1)
(* ,MULTIPLY ,16)
(m ,IDENTIFIER ,1)
赋值语句
-----------------------------------------
第2行
输入:if x then j:=j+1;
输出:
(if ,IF ,8)
(x ,IDENTIFIER ,1)
(then ,THEN ,9)
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(1 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
IF分支语句,嵌套赋值语句
-----------------------------------------
第3行
输入:if x then j:=j+1 else j:=j+2;
输出:
(if ,IF ,8)
(x ,IDENTIFIER ,1)
(then ,THEN ,9)
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(1 ,CONSTANT ,2)
(else ,ELSE ,10)
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(2 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
IF分支语句,嵌套赋值语句
-----------------------------------------
第4行
输入:while x do j:=j+2;
输出:
(while ,WHILE ,12)
(x ,IDENTIFIER ,1)
(do ,DO ,11)
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(j ,IDENTIFIER ,1)
(+ ,PLUS ,14)
(2 ,CONSTANT ,2)
(; ,SEMICOLON ,23)
WHILE-DO循环语句,嵌套赋值语句
-----------------------------------------
第5行
输入:j:=i*+1
输出:
(j ,IDENTIFIER ,1)
(:= ,ASSIGN ,26)
(i ,IDENTIFIER ,1)
(* ,MULTIPLY ,16)
(+ ,PLUS ,14)
(1 ,CONSTANT ,2)
语法错误:第5个词附近出现错误;
详细情况: 符号为1类型为CONSTANT
-----------------------------------------
第6行
输入:if x then;
输出:
(if ,IF ,8)
(x ,IDENTIFIER ,1)
(then ,THEN ,9)
(; ,SEMICOLON ,23)
语法错误:第2个词附近出现错误;
详细情况: 符号为then类型为THEN
-----------------------------------------
至此,全部任务完美完成!
实验总结
实验心得
首先,在学习LL(1)文法的相关知识时,我发现它具有诸多优点,如分析过程直观、分析效率高、错误诊断容易等。这使得它成为编译原理课程中广泛使用的一种文法形式。在实际编写语法分析程序的过程中,我也亲身体会到了这些优势。
在编写程序的过程中,最大的收获就是加深了对first集和follow集概念的理解。这两个集合的计算方法是构建预测分析表的关键,正确掌握它们对于实现递归下降分析算法至关重要。通过反复练习,我逐渐熟练掌握了计算first集和follow集的技巧,这也为后续的程序实现奠定了坚实的基础。
另外,在处理文法的左递归和空产生式问题时,我也学到了很多宝贵的经验。左递归会导致递归下降分析器陷入无限循环,因此需要特殊处理。而空产生式则可能会导致分析过程出现歧义,需要仔细分析文法的结构。通过解决这些问题,我不仅提高了编程能力,也加深了对编译原理知识的理解。
总的来说,这个实验是一次非常宝贵的实践机会。通过亲自动手编写LL(1)语法分析程序,我不仅掌握了相关的理论知识,也积累了丰富的实践经验。
实验改进
在完成了使用LL(1)文法进行基本语法分析程序编写的实验之后,我对未来的改进展望有以下几点思考:
-
扩展文法类型和分析方法的探索
-
目前的实验主要集中在LL(1)文法上,未来可以尝试使用其他类型的文法,如LR(1)文法,并编写相应的语法分析程序。这可以加深对不同文法及其分析方法的理解。
-
-
语义分析和中间代码生成的实现
-
语法分析只是编译过程的一部分,未来可以在此基础上,实现语义分析和中间代码生成的功能。这将使编译器的功能更加完整。
-
-
错误处理和报告的改进
-
当前的错误处理和报告功能较为简单,未来可以进一步完善,提供更加友好和详细的错误信息,帮助使用者快速定位和解决问题。
-
-
性能优化和可扩展性的提升
-
对于大型程序的编译,性能优化也是一个重要的考量因素。未来可以研究一些性能优化的技术,如使用更高效的数据结构和算法。
-
同时还可以增强程序的可扩展性,使其能够处理更复杂的语言特性和语法结构。
-
附录
赋值语句测试代码
if __name__ == '__main__':
content = input()
ts = TokenStream(content)
print(ts)
ass = ASSIGN_S(ts)
if ass.start():
print("赋值语句")
else:
print("不是赋值语句")
控制IF语句测试代码
if __name__ == '__main__':
content = input()
ts = TokenStream(content)
print(ts)
ifs = IF_S(ts)
if ifs.start():
print("IF语句")
else:
print("不是IF语句")
控制语句WHILE测试代码
if __name__ == '__main__':
content = input()
ts = TokenStream(content)
print(ts)
ws = WHILE_S(ts)
if ws.start():
print("WHILE语句")
else:
print("不是WHILE语句")
文件输入输出代码
class fileWriter:
"""封装文件输出类"""
def __init__(self, filename):
self.filename = filename
# 初始化+清空+创建文件
self.fw = open(self.filename, 'w', encoding="utf-8")
self.fw.write(str(datetime.now()) + '\n')
self.fw.close()
def write(self, msg):
# 追加模式
self.fw = open(self.filename, 'a', encoding='utf-8')
self.fw.write(msg + "\n")
self.fw.close()
错误查找模块
def getErrorInfo(tokenStream:TokenStream):
err = (f"第{tokenStream.index - 1}个词附近出现错误;" + "\n"
+ f"详细情况: 符号为{tokenStream.tokens[tokenStream.index - 1].lexeme}"
+ f"类型为{tokenStream.tokens[tokenStream.index - 1].type}")
return err
完整代码
from config import *
from lab1.lab1 import TokenStream, Token, fileWriter
class BaseParser:
"""
基础语法分析器类
"""
flag: bool
token: Token
end: int
def __init__(self, tokenStream, end=-1):
self.tokenStream = tokenStream
self.end = end
self.get_next_token()
self.err = ""
def get_next_token(self):
"""
获取下一个token
"""
if self.end > 0:
if self.tokenStream.index == self.end:
self.token = Token('', 'FILE-END', 0)
return
self.token = self.tokenStream.getNextToken()
def handle_syntax_error(self):
"""
处理语法错误
"""
self.flag = False
print(f"第{self.tokenStream.index - 1}个词附近出现错误;")
print(f"详细情况: 符号为{self.tokenStream.tokens[self.tokenStream.index - 1].lexeme} "
f"类型为{self.tokenStream.tokens[self.tokenStream.index - 1].type}")
self.get_next_token()
def find(self, args):
for i in range(self.tokenStream.index, len(self.tokenStream.tokens)):
if self.tokenStream.tokens[i].code in args:
return i
return -1
class CAL_S(BaseParser):
"""
算术表达式语法类
算术表达式的语法如下:
E->TE'
E'->+TE'|-TE'|e
T->FT'
T'->*FT'|/FT'|e
F->(E)|d
"""
def start(self):
"""
开始分析表达式
"""
self.flag = True
self.__E()
return self.flag
def __E(self):
"""
处理E->TE'
"""
self.__T()
self.__E1()
def __E1(self):
"""
处理E'->+TE'|-TE'|e
"""
if self.token.code in (PLUS, MINUS):
self.get_next_token()
self.__T()
self.__E1()
elif self.token.code not in (RPAREN, 0):
self.handle_syntax_error()
def __T(self):
"""
处理T->FT'
"""
self.__F()
self.__T1()
def __T1(self):
"""
处理T'->*FT'|/FT'|e
"""
if self.token.code in (MULTIPLY, DIVIDE):
self.get_next_token()
self.__F()
self.__T1()
elif self.token.code not in (MINUS, PLUS, RPAREN, 0):
self.handle_syntax_error()
def __F(self):
"""
处理F->(E)|d
"""
if self.token.code in (IDENTIFIER, CONSTANT):
self.get_next_token()
elif self.token.code == LPAREN:
self.get_next_token()
self.__E()
if self.token.code == RPAREN:
self.get_next_token()
else:
self.handle_syntax_error()
else:
self.handle_syntax_error()
class ASSIGN_S(BaseParser):
"""
赋值语句表达类
表达式如下形式
IDENTIFIER ASSIGN 算术表达式
"""
def start(self):
self.flag = True
# Check IDENTIFIER
if self.token.code == IDENTIFIER:
self.get_next_token()
else:
self.handle_syntax_error()
return self.flag
# Check ASSIGN
if self.token.code == ASSIGN:
cal = CAL_S(self.tokenStream, end=self.end)
if not cal.start():
self.handle_syntax_error()
return self.flag
else:
self.handle_syntax_error()
return self.flag
return self.flag
class IF_S(BaseParser):
"""
IF语句表达类
按照实验报告的要求IF语句满足以下条件:
1.无嵌套
2.if 算术表达式 then 赋值语句 ;
3.if 算术表达式 then 赋值语句 else 赋值语句;
关键技术:
我们使用了向前展望的方式来判断该文法是否满足条件,以此来进行判断,这不是一个很好的方法,
但对于该特殊条件而言,该方法是有效而且快速的
"""
def start(self):
self.flag = True
# 判断if
if self.token.code != IF:
self.handle_syntax_error()
return self.flag
# 判断算术表达式
# 1.先找到THEN
then_pivot = self.find((THEN,))
# 2.然后判断算术表达式
cal = CAL_S(self.tokenStream, then_pivot)
flag = cal.start()
if not flag:
return False
self.get_next_token()
# 判断then
if self.token.code != THEN:
self.handle_syntax_error()
return self.flag
# 判断赋值语句
# 1.先找到ELSE 或 ;
pivot = self.find((ELSE, SEMICOLON))
# 2.进行赋值表达式的判定
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断是否有else语句
if self.token.code == ELSE:
# 判断赋值语句
# 1.先找到 ;
pivot = self.find((SEMICOLON,))
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断结束
if self.token.code != SEMICOLON:
self.handle_syntax_error()
return self.flag
return self.flag
class WHILE_S(BaseParser):
"""
WHILE语句表达类
按照实验报告的要求IF语句满足以下条件:
1.无嵌套
2.while 算术表达式 DO 赋值语句 ;
"""
def start(self):
self.flag = True
# 判断if
if self.token.code != WHILE:
self.handle_syntax_error()
return self.flag
# 判断算术表达式
# 1.先找到DO
then_pivot = self.find((DO,))
# 2.然后判断算术表达式
cal = CAL_S(self.tokenStream, then_pivot)
flag = cal.start()
if not flag:
return False
self.get_next_token()
# 判断赋值语句
# 1.先找到 ;
pivot = self.find((SEMICOLON,))
# 2.进行赋值表达式的判定
assign = ASSIGN_S(self.tokenStream, pivot)
flag = assign.start()
if not flag:
return False
self.get_next_token()
# 判断结束
if self.token.code != SEMICOLON:
self.handle_syntax_error()
return self.flag
return self.flag
class BEGIN:
def __init__(self, tokenStream: TokenStream):
self.ts = tokenStream
def start(self):
token = self.ts.getNowToken()
if token.code == IF:
ifs = IF_S(self.ts)
return "IF分支语句,嵌套赋值语句" if ifs.start() else "语法错误:" + getErrorInfo(self.ts)
if token.code == WHILE:
ws = WHILE_S(self.ts)
return "WHILE-DO循环语句,嵌套赋值语句" if ws.start() else "语法错误:" + getErrorInfo(self.ts)
ass = ASSIGN_S(self.ts)
return "赋值语句" if ass.start() else "语法错误:" + getErrorInfo(self.ts)
def getErrorInfo(tokenStream:TokenStream):
err = (f"第{tokenStream.index - 1}个词附近出现错误;" + "\n"
+ f"详细情况: 符号为{tokenStream.tokens[tokenStream.index - 1].lexeme}"
+ f"类型为{tokenStream.tokens[tokenStream.index - 1].type}")
return err
if __name__ == '__main__':
fw = fileWriter("../fileSet/output/lab2.txt")
cnt = 1
with open('../fileSet/input/lab2.txt', 'r') as file:
for content in file:
ts = TokenStream(content)
fw.write(f"第{cnt}行")
fw.write(f"输入:{content.strip()}")
fw.write(f"输出:")
fw.write(str(ts).strip())
parse = BEGIN(ts)
fw.write(parse.start())
fw.write("-----------------------------------------")
cnt += 1
print("完成!")