一、实验目的
进行确定的自顶向下语法分析,对LL(1)文法定义的语言进行语法分析设计及实现,能够正确掌握语法分析的技术原理及方法,针对不同LL(1)文法描述的语言开展LL(1)递归下降分析模型设计及分析处理。
二、实验环境(仪器设备、软件等)
1、机房电脑 Window7
2、Dev-C++/ Eclipse等
三、实验要求
自顶向下语法分析是指从文法的开始符号出发,根据当前输入符号(单词符号)唯一地确定选用某个产生式替换相应非终结符以往下推导。这个过程中,使用了最左推导,亦即总是选择每个句型的最左非终结符进行替换。一个LL(1)文法可以使用确定的自定向下分析,在确定某文法为LL(1)文法后,可以使用LL(1)分析方法,如递归下降的分析方法,检查任意输入是否为满足文法规则的句子。下面提供了2个LL(1)文法,可任选其一作为你的实验文法,设计并实现你的递归下降分析程序。
① 第1个文法:
- S→AaS
- S→BbS
- S→d
- A→a
- B→ε
- B→c
- 第2个文法:为某语言的文法规则。
- <语句>∷=<变量>∶=<表达式>
- <语句>∷= IF<表达式>THEN<语句>[ELSE <语句>]
- <变量>∷= i[‘[’<表达式>‘]’]
- <表达式>∷= <项>{+<项>}
- <项>∷= <因子>{*<因子>}
- <因子>∷= <变量>|‘(’<表达式>‘)’
自主查阅参考资料,或参考课堂讲解、课本,自主设计并利用高级程序设计语言(开发环境不限,编码语言不限,如C/C++/JAVA/Python等)开展递归下降分析程序的编码实践,要求能对输入进行语法分析,并输出实验结果,解释实验和理论分析结果差异并撰写实验报告,对语法分析器输入输出的要求如下。
- 输入:不论选用哪个实验文法,都必须测试至少2组输入。一组为满足文法规则的输入,一组为不满足文法规则的输入,不满足文法规则的输入用以检查分析程序能否正常检查语法错误并报错。注意,若以第1个文法为实验文法,则输入为符号串形式(要求至少有5个以上符号组成),若选用第2个文法,则输入为代码形式。
- 输出:要求输出每组输入的分析结果,即该输入是否为满足文法规则,若不满足,则何处出错,提供出错位置。
- 选做项:当输入的符号串/代码有多处语法错误时,语法分析程序能从错误中恢复,并继续检查后续错误,直到输入全部分析完毕,给出输入中所有错误位置。
四、实验步骤
1. 实验文法选择及预处理
采用文法:选择第2个类Pascal语言文法,规则如下:
<语句> → <变量>:=<表达式> | IF<表达式>THEN<语句>[ELSE<语句>]
<变量> → i[ [<表达式>] ]
<表达式> → <项>{+<项>}
<项> → <因子>{*<因子>}
<因子> → <变量> | '('<表达式>')'
预处理操作:
消除隐式左递归:原文法中表达式和项使用扩展巴科斯范式({ ... }表示重复),直接转换为递归循环结构。
左因子提取:针对IF语句的可选ELSE分支,通过条件判断实现分支选择;对<因子>的两个产生式分支,通过前瞻token类型进行预测。
2. 递归下降分析器设计
总体架构:
词法分析模块:
定义12种token类型(如IDENTIFIER、IF、THEN等)
实现字符流到token流的转换,支持标识符、操作符、关键字的识别
错误处理:检测非法字符(如未闭合的:=)并抛出异常
语法分析模块:
核心函数映射:每个非终结符对应一个解析函数
文法规则 |
解析函数 |
功能描述 |
<语句> |
parse_statement |
处理赋值/IF条件语句 |
<变量> |
parse_variable |
解析变量及数组下标 |
<表达式> |
parse_expression |
处理加法运算表达式 |
<项> |
parse_term |
处理乘法运算项 |
<因子> |
parse_factor |
解析变量或括号表达式 |
优先级实现:通过函数嵌套调用层次体现运算符优先级(括号 > * > +),例如:
parse_expression调用parse_term处理加法运算
parse_term调用parse_factor处理乘法运算
错误处理机制:
严格token验证:在eat()方法中检查当前token是否符合预期,若不匹配则抛出含位置信息的语法错误
错误传播:异常从深层递归函数逐层传递至总入口parse(),确保错误信息可追溯
3. 关键功能实现
IF语句解析:
验证IF关键字后解析表达式
强制匹配THEN关键字和后续语句
通过条件判断处理可选的ELSE分支
数组下标支持:
在parse_variable()中检测[符号,递归解析下标表达式并匹配闭合的]
括号优先级处理:
在parse_factor()中识别(符号,递归解析内部表达式并匹配)
import sys
# Token类型枚举
class TokenType:
IDENTIFIER = 'IDENTIFIER'
IF = 'IF'
THEN = 'THEN'
ELSE = 'ELSE'
ASSIGN = ':='
PLUS = '+'
MULTIPLY = '*'
LBRACKET = '['
RBRACKET = ']'
LPAREN = '('
RPAREN = ')'
END = 'END'
# 词法分析器
class Lexer:
def __init__(self, input_str):
self.input = input_str
self.pos = 0
self.current_char = self.input[self.pos] if self.pos < len(self.input) else None
def advance(self):
self.pos += 1
if self.pos < len(self.input):
self.current_char = self.input[self.pos]
else:
self.current_char = None
def skip_whitespace(self):
while self.current_char is not None and self.current_char.isspace():
self.advance()
def get_next_token(self):
while self.current_char is not None:
if self.current_char.isspace():
self.skip_whitespace()
continue
# 识别标识符
if self.current_char.isalpha():
identifier = ''
while self.current_char is not None and (self.current_char.isalnum() or self.current_char == '_'):
identifier += self.current_char
self.advance()
return {'type': TokenType.IDENTIFIER, 'value': identifier}
# 识别操作符
if self.current_char == ':':
self.advance()
if self.current_char == '=':
self.advance()
return {'type': TokenType.ASSIGN, 'value': ':='}
else:
raise Exception(f"Invalid character ':{self.current_char}' at position {self.pos}")
# 识别其他单字符token
token_map = {
'+': TokenType.PLUS,
'*': TokenType.MULTIPLY,
'[': TokenType.LBRACKET,
']': TokenType.RBRACKET,
'(': TokenType.LPAREN,
')': TokenType.RPAREN
}
if self.current_char in token_map:
token_type = token_map[self.current_char]
token_value = self.current_char
self.advance()
return {'type': token_type, 'value': token_value}
# 识别关键字
if self.current_char == 'I':
self.advance()
if self.current_char == 'F':
self.advance()
return {'type': TokenType.IF, 'value': 'IF'}
if self.current_char == 'T':
self.advance()
if self.current_char == 'H':
self.advance()
if self.current_char == 'E':
self.advance()
if self.current_char == 'N':
self.advance()
return {'type': TokenType.THEN, 'value': 'THEN'}
if self.current_char == 'E':
self.advance()
if self.current_char == 'L':
self.advance()
if self.current_char == 'S':
self.advance()
if self.current_char == 'E':
self.advance()
return {'type': TokenType.ELSE, 'value': 'ELSE'}
raise Exception(f"Unexpected character '{self.current_char}' at position {self.pos}")
return {'type': TokenType.END, 'value': None}
# 递归下降语法分析器
class Parser:
def __init__(self, lexer):
self.lexer = lexer
self.current_token = self.lexer.get_next_token()
def eat(self, token_type):
if self.current_token['type'] == token_type:
self.current_token = self.lexer.get_next_token()
else:
raise Exception(
f"Syntax error: Expected {token_type}, got {self.current_token['type']} at position {self.lexer.pos}")
def parse(self):
try:
self.parse_statement()
if self.current_token['type'] != TokenType.END:
raise Exception(f"Unexpected token {self.current_token} at end of input")
print("Syntax analysis successful!")
except Exception as e:
print(f"Syntax Error: {str(e)}")
def parse_statement(self):
if self.current_token['type'] == TokenType.IF:
self.parse_if_statement()
else:
self.parse_assignment()
def parse_if_statement(self):
self.eat(TokenType.IF)
self.parse_expression()
self.eat(TokenType.THEN)
self.parse_statement()
if self.current_token['type'] == TokenType.ELSE:
self.eat(TokenType.ELSE)
self.parse_statement()
def parse_assignment(self):
self.parse_variable()
self.eat(TokenType.ASSIGN)
self.parse_expression()
def parse_variable(self):
self.eat(TokenType.IDENTIFIER)
if self.current_token['type'] == TokenType.LBRACKET:
self.eat(TokenType.LBRACKET)
self.parse_expression()
self.eat(TokenType.RBRACKET)
def parse_expression(self):
self.parse_term()
while self.current_token['type'] == TokenType.PLUS:
self.eat(TokenType.PLUS)
self.parse_term()
def parse_term(self):
self.parse_factor()
while self.current_token['type'] == TokenType.MULTIPLY:
self.eat(TokenType.MULTIPLY)
self.parse_factor()
def parse_factor(self):
if self.current_token['type'] == TokenType.IDENTIFIER:
self.parse_variable()
elif self.current_token['type'] == TokenType.LPAREN:
self.eat(TokenType.LPAREN)
self.parse_expression()
self.eat(TokenType.RPAREN)
else:
raise Exception(f"Unexpected token {self.current_token} in factor")
# 测试用例
test_cases = [
"i := a + b * c", # 合法
"IF x THEN y ELSE z", # 合法
"a[1+2] := (3 * 4)+5", # 合法
"x := y + * z", # 非法:缺少右操作数
"IF THEN ELSE", # 非法:缺少表达式和语句
"matrix[1][2] := 3" # 非法:不支持多维数组
]
for idx, test in enumerate(test_cases):
print(f"\nTest case {idx + 1}: {test}")
lexer = Lexer(test)
parser = Parser(lexer)
parser.parse()
输入案例 |
类型 |
预期结果 |
实际输出 |
i := a + b * c |
合法 |
分析成功 |
"Syntax analysis successful!" |
IF x THEN y ELSE z |
合法 |
分析成功 |
"Syntax analysis successful!" |
a[1+2] := (3 * 4)+5 |
合法 |
分析成功 |
"Syntax analysis successful!" |
x := y + * z |
非法 |
检测*前缺少操作数 |
"Syntax Error: Expected IDENTIFIER, got..." |
IF THEN ELSE |
非法 |
检测IF后缺少表达式 |
"Syntax Error: Expected IDENTIFIER..." |
matrix[1][2] := 3 |
非法 |
检测多维数组不兼容文法 |
"Syntax Error: Unexpected token..." |
2. 结果分析
合法用例:程序正确识别所有符合文法的结构,包括嵌套表达式、IF语句和数组下标。
非法用例:
错误定位:通过lexer.pos精确报告出错位置(如x := y + * z中*的位置)
错误类型:明确错误原因(如期望IDENTIFIER但遇到MULTIPLY)
错误阻断:遇到首个错误后立即终止分析,避免产生误导性后续错误
3. 误差与改进
现存不足:
错误恢复局限:无法从错误中恢复继续分析后续内容(如缺少分号时跳过至下一语句)
解决方案:在eat()中添加同步恢复机制,预定义同步token集合(如;、THEN等)
多维数组支持缺失:文法仅支持单层数组下标
解决方案:扩展文法规则为<变量> → i{[<表达式>]} ,修改parse_variable()支持循环解析下标
关键字冲突:未完全处理标识符与关键字的冲突(如变量名IF被误识别为关键字)
改进方案:在词法分析阶段优先匹配关键字,确保标识符不包含保留字
根本原因:
递归下降分析法对文法设计高度敏感,需严格满足LL(1)特性。实验中未完全处理前瞻冲突和错误恢复场景,导致部分边界情况未被覆盖。