2. 词法分析【实验二 词法分析错误处理】
词法分析实验 编程题
词法分析【实验二】
- 2. 词法分析【实验二 词法分析错误处理】
-
- 【问题描述】
- 【输入形式】
- 【输出形式】
- 【样例输入】
- 【样例输出】
- 【样例说明】
- 【评分标准】
- 评测结果(满分10分)
- 解题思路
-
- 总结
- 代码内容(python语言)
- 参考代码(python语言)
【问题描述】
编写词法分析程序,在实验一的基础之上添加错误识别功能,基本要求如下:
(1)输入的被编译源文件统一命名为
1.
t
x
t
1.txt
1.txt ;
(2)输出结果中包含如下两种信息:
错误所在的行号 错误类型编码 (行号与错误类型编码之间只有一个空格)
其中,错误类型编码按下表中的定义输出,行号从1开始计数: 
101 非法字符 如:#、@、¥等
102 不符合构词规则 包括:非法标识符;小数点后面没有数字等
103 注释未闭合 缺少闭合的*/
104 字符类型缺少配对的’
105 字符类型缺少配对的“
【输入形式】
程序代码,请在代码中读取
1.
t
x
t
1.txt
1.txt 文件
【输出形式】
行号 错误码 按如上要求将错误信息打印输出,也可以自行保存到错误信息文件中
e
r
r
o
r
.
t
x
t
error.txt
error.txt 。
【样例输入】
float b_2 = 20. ;
int a = 12 ;
【样例输出】
1 102
【样例说明】
每个测试用例的错误类型说明:
-
用例1:浮点数构词错误;非法字符
-
用例2:八进制数构词错误;字符常量错误情况1;字符常量错误情况2
-
用例3:非法字符;浮点数构词错误;字符串错误
-
用例4:字符常量错误;注释错误
-
用例5:标识符构词错误;十六进制数构词错误;非法字符
【评分标准】
按正确样例数目进行评分
(1)本次实验的每个测试用例各包含1-3个错误,均来自上表,识别出一个测试用例中的全部错误后得2分;
(2)上表中未包含的错误,可以自行设计,本次实验不考核;
(3)完成本次实验时,不需要输出词法分析实验一要求输出的内容。
评测结果(满分10分)

解题思路
本题核心是在实验一词法解析基础上,新增五类词法错误的检测与记录:扩展分析器类,新增错误列表存储(行号,错误码);逐行解析时,针对不同错误类型设计检测逻辑——未知字符标记
101
101
101 非法字符,浮点数小数点后无数字标记
102
102
102 构词错误,多行注释未闭合标记
103
103
103 ,字符常量缺单引号标记
104
104
104 ,字符串常量缺双引号标记
105
105
105 ;解析过程中实时检测并记录错误,解析完成后按行号排序输出错误信息;同时保留实验一的合法单词解析功能,确保错误检测不影响原有词法分析流程。该方法通过针对性的错误校验逻辑,覆盖题目要求的所有错误类型,精准记录错误行号和编码,适配自动评测的输出格式要求。
总结
代码内容(python语言)
import sys
class Token:
def __init__(self, lexeme, token_type, line):
self.lexeme = lexeme
self.token_type = token_type
self.line = line
def __str__(self):
return f"{self.lexeme:<15} {self.token_type:<5} {self.line}"
class LexicalAnalyzer:
def __init__(self, source_code):
self.source_code = source_code
self.lines = source_code.splitlines()
# 关键字-类别码映射
self.keywords = {
'char': 101, 'int': 102, 'float': 103, 'if': 111,
'return': 106, 'do': 109, 'while': 110, 'void': 107,
'const': 105, 'else': 112
}
# 运算符-类别码映射(与C版本完全一致)
self.operators = {
'=': 219, '+': 209, '>': 213, '-': 210,
'*': 206, '/': 204, '<': 211, '>=': 207,
'<=': 208, '==': 215, '!=': 216, '&&': 217,
'||': 218, '!': 205
}
# 分隔符-类别码映射(与C版本完全一致)
self.delimiters = {
'{': 301, '}': 302, ';': 303, '(': 201,
')': 202, ',': 304, '[': 203, ']': 204,
'.': 309 # 添加点号,C中为309
}
self.ID_CODE = 700
self.INT_CODE = 400
self.FLOAT_CODE = 800
self.CHAR_CODE = 500
self.STR_CODE = 600
self.errors = [] # (行号, 错误编码)
# 仅识别ASCII字母和下划线
def is_ascii_letter(self, c):
return ('a' <= c <= 'z') or ('A' <= c <= 'Z') or c == '_'
# 仅识别ASCII数字
def is_ascii_digit(self, c):
return '0' <= c <= '9'
# 仅识别ASCII十六进制字符
def is_ascii_hex_digit(self, c):
return ('0' <= c <= '9') or ('a' <= c <= 'f') or ('A' <= c <= 'F')
# 跳过空白字符
def skip_whitespace(self, line, pos):
while pos < len(line) and line[pos].isspace():
pos += 1
return pos
# 分析标识符或关键字
def analyze_identifier_or_keyword(self, line, pos, line_num):
start = pos
while pos < len(line) and (self.is_ascii_letter(line[pos]) or self.is_ascii_digit(line[pos])):
pos += 1
lexeme = line[start:pos]
token_type = self.keywords.get(lexeme, self.ID_CODE)
# 检查标识符后是否紧跟非法字符(非ASCII字母/数字/下划线/空白/运算符/分隔符)
if pos < len(line):
ch = line[pos]
if not (self.is_ascii_letter(ch) or self.is_ascii_digit(ch) or ch == '_' or ch.isspace() or
ch in self.operators or ch in self.delimiters):
self.errors.append((line_num, 101))
return None, len(line) # 跳过整行
return (lexeme, token_type, line_num), pos
# 分析数字
def analyze_number(self, line, pos, line_num):
start = pos
n = len(line)
illegal = False
# 十六进制 0x…
if line[pos] == '0' and pos + 1 < n and line[pos+1] in ('x', 'X'):
pos += 2
if pos >= n or not self.is_ascii_hex_digit(line[pos]):
self.errors.append((line_num, 102))
return None, n # 跳过整行
while pos < n and self.is_ascii_hex_digit(line[pos]):
pos += 1
# 检查后跟字母/下划线(仅ASCII字母)
if pos < n and (self.is_ascii_letter(line[pos]) or line[pos] == '_'):
self.errors.append((line_num, 102))
return None, n # 跳过整行
lexeme = line[start:pos]
return (lexeme, self.INT_CODE, line_num), pos
# 八进制 0… (非十六进制的0开头数字)
if line[pos] == '0' and pos + 1 < n and line[pos+1] not in ('x', 'X'):
pos += 1
# 检查后续字符是否为八进制数字(0-7)
while pos < n:
if line[pos] in '01234567':
pos += 1
elif line[pos].isdigit(): # 8或9,非法
illegal = True
self.errors.append((line_num, 102))
# 跳过整行
return None, n
else:
break
# 检查后跟字母/下划线
if pos < n and (self.is_ascii_letter(line[pos]) or line[pos] == '_'):
self.errors.append((line_num, 102))
return None, n # 跳过整行
if illegal:
return None, n
lexeme = line[start:pos]
return (lexeme, self.INT_CODE, line_num), pos
# 十进制整数或浮点数
has_dot = False
while pos < n:
c = line[pos]
if self.is_ascii_digit(c):
pos += 1
elif c == '.' and not has_dot:
has_dot = True
pos += 1
if pos >= n or not self.is_ascii_digit(line[pos]):
self.errors.append((line_num, 102))
return None, n # 跳过整行
elif c == '.' and has_dot:
self.errors.append((line_num, 102))
return None, n # 跳过整行
else:
break
# 检查后跟字母/下划线
if pos < n and (self.is_ascii_letter(line[pos]) or line[pos] == '_'):
self.errors.append((line_num, 102))
return None, n # 跳过整行
lexeme = line[start:pos]
token_type = self.FLOAT_CODE if has_dot else self.INT_CODE
return (lexeme, token_type, line_num), pos
# 分析字符常量
def analyze_char_literal(self, line, pos, line_num):
start = pos
pos += 1
char_count = 0
while pos < len(line) and line[pos] != "'":
if line[pos] == '\\\\':
pos += 1
if pos < len(line):
pos += 1
char_count += 1
else:
break
else:
pos += 1
char_count += 1
if pos >= len(line) or line[pos] != "'":
self.errors.append((line_num, 104))
return None, len(line) # 跳过整行
pos += 1
if char_count != 1:
self.errors.append((line_num, 102))
lexeme = line[start+1:pos–1] if (pos–1) > start else ""
return (lexeme, self.CHAR_CODE, line_num), pos
# 分析字符串常量
def analyze_string_literal(self, line, pos, line_num):
start = pos
pos += 1
while pos < len(line) and line[pos] != '"':
if line[pos] == '\\\\':
pos += 1
pos += 1
if pos >= len(line) or line[pos] != '"':
self.errors.append((line_num, 105))
return None, len(line) # 跳过整行
pos += 1
lexeme = line[start+1:pos–1] if (pos–1) > start else ""
return (lexeme, self.STR_CODE, line_num), pos
# 分析运算符
def analyze_operator(self, line, pos, line_num):
if pos + 1 < len(line):
two_char = line[pos:pos+2]
if two_char in self.operators:
return (two_char, self.operators[two_char], line_num), pos + 2
one_char = line[pos]
if one_char in self.operators:
return (one_char, self.operators[one_char], line_num), pos + 1
self.errors.append((line_num, 101))
return None, len(line) # 跳过整行
# 分析分隔符
def analyze_delimiter(self, line, pos, line_num):
c = line[pos]
if c in self.delimiters:
return (c, self.delimiters[c], line_num), pos + 1
self.errors.append((line_num, 101))
return None, len(line) # 跳过整行
# 主分析函数
def analyze(self):
line_num = 1
total_lines = len(self.lines)
while line_num <= total_lines:
line = self.lines[line_num – 1]
pos = 0
line_length = len(line)
while pos < line_length:
pos = self.skip_whitespace(line, pos)
if pos >= line_length:
break
current_char = line[pos]
# 单行注释
if current_char == '/' and pos + 1 < line_length and line[pos+1] == '/':
pos = line_length # 跳过整行
continue
# 多行注释(逐字符扫描,模拟C行为)
if current_char == '/' and pos + 1 < line_length and line[pos+1] == '*':
pos += 2
comment_start_line = line_num
found_end = False
while True:
# 在当前行内逐字符查找 */
while pos < line_length:
if line[pos] == '*' and pos + 1 < line_length and line[pos+1] == '/':
pos += 2
found_end = True
break
pos += 1
if found_end:
break
# 未找到,继续下一行
line_num += 1
if line_num > total_lines:
# 注释未闭合,错误记录在最后一行
self.errors.append((total_lines, 103))
# 设置 line_num 为 total_lines,pos 为行尾,并标记结束
line_num = total_lines
pos = line_length
found_end = True
break
line = self.lines[line_num – 1]
line_length = len(line)
pos = 0
continue
# 标识符/关键字
if self.is_ascii_letter(current_char):
_, pos = self.analyze_identifier_or_keyword(line, pos, line_num)
continue
# 数字
if self.is_ascii_digit(current_char):
_, pos = self.analyze_number(line, pos, line_num)
continue
# 字符常量
if current_char == "'":
_, pos = self.analyze_char_literal(line, pos, line_num)
continue
# 字符串常量
if current_char == '"':
_, pos = self.analyze_string_literal(line, pos, line_num)
continue
# 运算符
if current_char in self.operators or (pos+1 < line_length and line[pos:pos+2] in self.operators):
_, pos = self.analyze_operator(line, pos, line_num)
continue
# 分隔符
if current_char in self.delimiters:
_, pos = self.analyze_delimiter(line, pos, line_num)
continue
# 其他非法字符
self.errors.append((line_num, 101))
pos = line_length # 跳过整行
line_num += 1
def output_errors(self):
self.errors.sort(key=lambda x: x[0])
for line, code in self.errors:
print(f"{line} {code}")
if __name__ == '__main__':
try:
with open('1.txt', 'r', encoding="utf-8") as f:
test_code = f.read()
except FileNotFoundError:
print("错误:未找到1.txt文件,请确保文件存在!")
sys.exit(1)
analyzer = LexicalAnalyzer(test_code)
analyzer.analyze()
analyzer.output_errors()
参考代码(python语言)
# 定义单词-编码映射表(整合所有类别,方便查询)
TOKEN_MAP = {
# 关键字
'char': 101, 'int': 102, 'float': 103, 'break': 104,
'const': 105, 'return': 106, 'void': 107, 'continue': 108,
'do': 109, 'while': 110, 'if': 111, 'else': 112, 'for': 113,
# 界符
'{': 301, '}': 302, ';': 303, ',': 304,
# 运算符
'(': 201, ')': 202, '[': 203, ']': 204, '!': 205, '*': 206,
'/': 207, '%': 208, '+': 209, '-': 210, '<': 211, '<=': 212,
'>': 213, '>=': 214, '==': 215, '!=': 216, '&&': 217, '||': 218,
'=': 219, '.': 220,
# 类别标识(非直接匹配,用于动态识别)
'整数': 400, '字符': 500, '字符串': 600, '标识符': 700, '实数(float)': 800
}
# 关键字集合(快速判断)
KEYWORDS = {'char', 'int', 'float', 'break', 'const', 'return', 'void',
'continue', 'do', 'while', 'if', 'else', 'for'}
# 单字符运算符/界符集合
SINGLE_OPERATORS = {'(', ')', '[', ']', '!', '*', '/', '%', '+', '-',
'<', '>', '=', '.', '{', '}', ';', ','}
# 双字符运算符前缀(需要检查下一个字符)
DOUBLE_OP_PREFIX = {'<', '>', '!', '&', '|', '='}
DOUBLE_OP_MAP = {
'<': '<=', '>': '>=', '!': '!=',
'&': '&&', '|': '||', '=': '=='
}
# 合法字符集合(用于判断错误码101)
LEGAL_CHARS = {
# 字母(大小写)、数字、下划线
*[chr(c) for c in range(65, 91)], *[chr(c) for c in range(97, 123)],
*[chr(c) for c in range(48, 58)], '_',
# 空白符
' ', '\\t', '\\n',
# 特殊符号
'+', '-', '*', '/', '%', '=', '<', '>', '!', '&', '|', '(', ')', '{', '}',
'[', ']', ',', '.', ';', "'", '"', '\\\\', '$' # $作为结束符
}
class LexicalAnalyzer:
def __init__(self, code):
# 预处理:保留换行(注释识别需要),添加结束符
self.code = code.strip() + '$'
self.pos = 0 # 当前字符位置
self.current_char = self.code[self.pos] # 当前字符
self.errors = [] # 存储错误信息:(错误码, 描述, 位置)
self.row = 1 # 当前行号
self.col = 0 # 当前列号
self.next = False
# 读取下一个字符
def next_char(self):
self.pos += 1
self.current_char = self.code[self.pos] if self.pos < len(self.code) else '$'
self.col += 1
if self.next:
self.row += 1
self.col = 1
self.next = False
if self.current_char == '\\n':
self.next = True
# 跳过空白字符(空格、制表符)
def skip_whitespace(self):
while self.current_char in (' ', '\\t', '\\n'):
self.next_char()
# 判断是否为字母/下划线
def is_letter_or_underline(self, char):
return char.isalpha() or char == '_'
# 判断是否为数字
def is_digit(self, char):
return char.isdigit()
# 检查字符是否合法(错误码101判断)
def is_legal_char(self, char):
return char in LEGAL_CHARS
# 识别单行注释(//)和多行注释(/* */)
def skip_comment(self):
# 情况1:单行注释 //
if self.current_char == '/' and self.code[self.pos + 1] == '/':
self.next_char()
self.next_char()
# 跳过直到换行或结束符
while self.current_char not in ('\\n', '$'):
self.next_char()
return True
# 情况2:多行注释 /*
elif self.current_char == '/' and self.code[self.pos + 1] == '*':
self.next_char()
self.next_char()
# 状态:寻找 */ 闭合
while self.current_char != '$':
if self.current_char == '*' and self.code[self.pos + 1] == '/':
self.next_char()
self.next_char()
return True
self.next_char()
# 多行注释未闭合
self.errors.append((103, "注释未闭合", self.row, self.col)) # 自定义-2标识注释未闭合,最终输出对应描述
return True
return False
# 识别标识符/关键字(含构词规则检查:错误码102)
def identify_identifier_or_keyword(self):
token = ''
# 构词规则:标识符必须以字母/下划线开头
if not self.is_letter_or_underline(self.current_char):
self.errors.append((102, f"不符合构词规则:标识符不能以{self.current_char}开头", self.row, self.col))
self.next_char()
return None
# 状态S1:收集字母/数字/下划线
while self.is_letter_or_underline(self.current_char) or self.is_digit(self.current_char):
token += self.current_char
self.next_char()
# 判断是否为关键字
if token in KEYWORDS:
return (TOKEN_MAP[token], token, self.row)
else:
return (TOKEN_MAP['标识符'], token, self.row)
# 识别数字(整数/实数,含构词规则检查:错误码102)
def identify_number(self):
token = ''
# 状态S2:收集整数部分
while self.is_digit(self.current_char):
token += self.current_char
self.next_char()
# 检查是否为实数(状态S3)
if self.current_char == '.':
token += self.current_char
self.next_char()
# 收集小数部分
if self.is_digit(self.current_char):
while self.is_digit(self.current_char):
token += self.current_char
self.next_char()
return (TOKEN_MAP['实数(float)'], token, self.row)
else:
# 构词错误:小数点后无数字
self.errors.append((102, f"不符合构词规则:实数{token}小数点后无数字", self.row, self.col))
# 退回字符,仅保留整数部分
self.pos -= 1
self.current_char = self.code[self.pos]
return (TOKEN_MAP['整数'], token[:–1], self.row) if token[:–1] else None
# 纯整数
return (TOKEN_MAP['整数'], token, self.row) if token else None
# 识别运算符/界符
def identify_operator_or_delimiter(self):
current = self.current_char
# 状态S4:检查双字符运算符
if current in DOUBLE_OP_PREFIX:
next_c = self.code[self.pos + 1] if self.pos + 1 < len(self.code) else '$'
double_op = current + next_c
if double_op in DOUBLE_OP_MAP.values():
self.next_char() # 跳过第二个字符
self.next_char()
return (TOKEN_MAP[double_op], double_op, self.row)
# 状态S5:单字符运算符/界符
self.next_char()
return (TOKEN_MAP[current], current, self.row) if current in TOKEN_MAP else None
def identify_string_literal(self):
if self.current_char == '"':
current = ''
self.next_char()
while self.current_char != '"':
current += self.current_char
if self.current_char == '$' or self.current_char == '\\n':
self.errors.append((105, f"字符串未闭合", self.row, self.col))
return (TOKEN_MAP['字符'], current, self.row)
self.next_char()
if self.current_char == '"':
self.next_char()
return (TOKEN_MAP['字符串'], current, self.row)
elif self.current_char == "'":
self.next_char()
current = ''
if self.current_char != "'":
current += self.current_char
self.next_char()
if self.current_char != "'":
self.errors.append((104, f"字符字面量只能包含一个字符", self.row, self.col))
# while self.current_char != "'" and self.current_char != '$':
# self.next_char()
return (TOKEN_MAP['字符'], current, self.row)
else:
self.next_char()
return (TOKEN_MAP['字符'], current, self.row)
else:
return (TOKEN_MAP['字符'], current, self.row)
# 核心分析方法
def analyze(self):
tokens = []
while self.current_char != '$':
self.skip_whitespace()
if self.current_char == '$':
break
# 第一步:检查是否为注释
if self.skip_comment():
continue
# 第二步:检查字符是否合法(错误码101)
if not self.is_legal_char(self.current_char):
self.errors.append((101, f"错误字符:{self.current_char}", self.row, self.col))
self.next_char()
continue
# 第三步:分支识别
if self.is_letter_or_underline(self.current_char):
# 标识符/关键字分支
token = self.identify_identifier_or_keyword()
if token:
tokens.append(token)
elif self.is_digit(self.current_char):
# 数字分支
token = self.identify_number()
if token:
tokens.append(token)
elif self.current_char in SINGLE_OPERATORS or self.current_char in DOUBLE_OP_PREFIX:
# 运算符/界符分支
token = self.identify_operator_or_delimiter()
if token:
tokens.append(token)
elif self.current_char == '"' or self.current_char == "'":
# 字符串字面量分支
token = self.identify_string_literal()
if token:
tokens.append(token)
else:
# 其他合法字符但未识别(理论上不会走到这里,兜底)
self.errors.append((102, f"不符合构词规则:{self.current_char}", self.row, self.col))
self.next_char()
# 输出错误信息
for err_code, _, row, _ in self.errors:
print(f"{row} {err_code}")
return tokens
class Token:
def __init__(self, lexeme, token_type, line):
self.lexeme = lexeme
self.token_type = token_type
self.line = line
def __str__(self):
return f"{self.lexeme:<15} {self.token_type:<7} {self.line}"
# 测试示例
if __name__ == '__main__':
# 测试代码(包含注释、错误字符、构词错误、未闭合注释)
with open('1.txt', 'r', encoding="utf-8") as f:
test_code = f.read()
# 创建分析器并执行分析
analyzer = LexicalAnalyzer(test_code)
result = analyzer.analyze()
# 输出有效token
# for code, content, row in result:
# t = Token(content, code, row)
# print(t)


