PLY是Python中常用的词法分析器和语法分析器生成工具,其Lexer模块通过用户定义的规则完成词法拆分和令牌返回,实际开发过程中很容易出现各类配置和逻辑错误。

常见错误一:规则匹配顺序不符合预期
PLY的Lexer会按照规则定义的顺序依次尝试匹配,如果长规则定义在短规则之后,就会出现短规则优先匹配的问题。比如要匹配整数和小数,若先定义整数的规则,再定义小数的规则,小数就会被拆分成整数和小数点两部分。
错误示例
import ply.lex as lex
tokens = ['INT', 'FLOAT']
# 先定义整数规则,后定义小数规则
t_INT = r'd+'
t_FLOAT = r'd+.d+'
lexer = lex.lex()
lexer.input("3.14")
for tok in lexer:
print(tok)
上述代码会先输出类型为INT、值为3的令牌,再输出类型为FLOAT、值为.14的令牌,不符合预期。
解决方案
将匹配长度更长的规则放在前面定义即可,修改后代码如下:
import ply.lex as lex
tokens = ['FLOAT', 'INT'] # 令牌列表中顺序不影响匹配,规则定义顺序才影响
# 先定义小数规则,后定义整数规则
t_FLOAT = r'd+.d+'
t_INT = r'd+'
lexer = lex.lex()
lexer.input("3.14")
for tok in lexer:
print(tok)
常见错误二:令牌类型未在tokens列表中声明
PLY要求所有返回的令牌类型都必须提前在tokens元组中声明,如果规则中返回的令牌类型不在该列表中,会直接抛出PLY错误。
错误示例
import ply.lex as lex
# 未声明ID令牌类型
tokens = ['INT']
t_INT = r'd+'
def t_ID(t):
r'[a-zA-Z_][a-zA-Z_0-9]*'
return t
lexer = lex.lex()
执行上述代码会提示PLY: Lexer error. Symbol 'ID' not found in tokens list,无法生成词法分析器。
解决方案
将用到的所有令牌类型都添加到tokens列表中:
import ply.lex as lex
tokens = ['INT', 'ID'] # 补充ID令牌类型
t_INT = r'd+'
def t_ID(t):
r'[a-zA-Z_][a-zA-Z_0-9]*'
return t
lexer = lex.lex()
常见错误三:令牌返回值格式不正确
自定义规则函数中如果没有正确设置令牌的type和value属性,或者返回值不是令牌对象,会导致返回结果异常。比如忘记给令牌赋值value,或者返回了普通字符串。
错误示例
import ply.lex as lex
tokens = ['NUM']
def t_NUM(t):
r'd+'
# 没有设置t.value,直接返回t
return t
lexer = lex.lex()
lexer.input("123")
tok = lexer.token()
print(tok.value) # 输出为空或者不符合预期
解决方案
在规则函数中正确处理令牌属性,字符串类型的令牌值可以直接赋值,复杂逻辑可以自行处理后再返回:
import ply.lex as lex
tokens = ['NUM']
def t_NUM(t):
r'd+'
t.value = int(t.value) # 将字符串转换为整数
return t
lexer = lex.lex()
lexer.input("123")
tok = lexer.token()
print(tok.type, tok.value) # 输出 NUM 123
常见错误四:忽略规则与正常规则冲突
如果在t_ignore中定义了会匹配正常令牌内容的规则,会导致正常令牌被忽略,无法正确返回。比如t_ignore中包含了匹配字母的规则,那么ID令牌就会被忽略。
错误示例
import ply.lex as lex
tokens = ['ID']
t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'
t_ignore = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' # 忽略所有字母
lexer = lex.lex()
lexer.input("abc")
for tok in lexer:
print(tok) # 没有输出,所有字母都被忽略
解决方案
t_ignore只用来定义需要忽略的字符,比如空格、换行、注释等,不要包含正常令牌的匹配内容:
import ply.lex as lex
tokens = ['ID']
t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'
t_ignore = ' tn' # 只忽略空格、制表符、换行符
lexer = lex.lex()
lexer.input("abc")
for tok in lexer:
print(tok) # 正确输出ID类型的abc令牌
总结
PLY Lexer的规则编写和令牌返回问题大多集中在匹配顺序、令牌声明、返回值格式、忽略规则这几个方面,开发时只要注意规则定义的先后顺序,确保所有令牌类型都在tokens列表中声明,正确处理令牌对象的属性,合理设置忽略规则,就能避免绝大多数常见问题。如果碰到报错,可以先检查上述几个环节的配置是否符合要求。