2. 词法分析器:支持数字与字符串字面量
任务目标
扩展一个最小词法分析器,使其能从输入源码中识别三类 :整数(十进制)、浮点数(含小数点)、字符串字面量(双引号包裹),并正确处理字符串中的转义字符(\\、\"、\n、\t)。输出为 流,每个 包含类型与字面值。
实现方案
采用手写单字符扫描器,逐字符读取输入,按状态切换识别不同字面量。核心逻辑集中在 next_() 函数中:遇到数字则连续扫描数字与小数点形成数值;遇到双引号则扫描至闭合引号并解析转义序列。识别完成后返回 (type, value) 对,供调用方消费。
完整代码
# lexer.py
import re
_INT = "INT"
_FLOAT = "FLOAT"
_STRING = "STRING"
_EOF = "EOF"
class :
def __init__(self, type_, value):
self.type = type_
self.value = value
def __repr__(self):
return f"({self.type}, {self.value!r})"
class Lexer:
def __init__(self, source):
self.src = source
self.pos = 0
self.n = len(source)
def skip_whitespace(self):
while self.pos < self.n and self.src[self.pos].isspace():
self.pos += 1
def read_number(self):
start = self.pos
is_float = False
while self.pos < self.n and (self.src[self.pos].isdigit() or self.src[self.pos] == '.'):
if self.src[self.pos] == '.':
# 防止 "1.2.3" 这类非法输入,只允许一个小数点
if is_float:
break
is_float = True
self.pos += 1
text = self.src[start:self.pos]
return (_FLOAT if is_float else _INT, text)
def read_string(self):
# 当前字符是引号,跳过
self.pos += 1
chars = []
escape_map = {'n': '\n', 't': '\t', '\\': '\\', '"': '"'}
while self.pos < self.n:
ch = self.src[self.pos]
if ch == '\\':
# 处理转义
if self.pos + 1 >= self.n:
raise ValueError("字符串以反斜杠结尾,缺少转义字符")
nxt = self.src[self.pos + 1]
if nxt in escape_map:
chars.append(escape_map[nxt])
self.pos += 2
else:
raise ValueError(f"未知转义序列: \\{nxt}")
elif ch == '"':
self.pos += 1
return (_STRING, ''.join(chars))
else:
chars.append(ch)
self.pos += 1
raise ValueError("未闭合的字符串字面量")
def next_(self):
self.skip_whitespace()
if self.pos >= self.n:
return (_EOF, None)
ch = self.src[self.pos]
if ch.isdigit():
return self.read_number()
if ch == '"':
return self.read_string()
raise ValueError(f"无法识别的字符: {ch!r}")
def tokenize(self):
tokens = []
while True:
tok = self.next_()
tokens.append(tok)
if tok.type == _EOF:
break
return tokens
# 使用示例
if __name__ == "__main__":
code = 'count = 42\npi = 3.14\nmsg = "hello\\nworld"'
lexer = Lexer(code)
for tok in lexer.tokenize():
print(tok)
运行与验证
将上述代码保存为 lexer.py,在命令行执行:
python lexer.py
预期输出(与代码逻辑严格对应):
(INT, '42')
(FLOAT, '3.14')
(STRING, 'hello\nworld')
(EOF, None)
注意:示例输入中 count = 与 msg = 中的等号和标识符会触发“无法识别的字符”异常,因此上述示例代码仅包含可被识别的字面量部分。若要完整测试,请将输入改为:
code = '42 3.14 "hello\\nworld"'
此时输出应为:
(INT, '42')
(FLOAT, '3.14')
(STRING, 'hello\nworld')
(EOF, None)
验证通过条件:程序无异常退出,且输出的 类型与值符合上述预期。可自行修改 code 变量中的字符串内容,测试不同数值格式(如 0.5、100)和转义组合(如 "a\\tb" 应输出制表符)。
边界行为说明
- 数字识别允许
3.或.5这类写法,会被标记为浮点数;若出现1.2.3,扫描到第二个小数点时停止,1.2被识别为浮点数,后续.3会因无法识别而报错。 - 字符串中未转义的双引号会直接结束字符串;若字符串内需要引号,必须写作
\"。 - 词法错误(未闭合字符串、未知转义、非法字符)通过抛出
ValueError报告,调用方可捕获该异常进行错误处理。