语法解析器例程编写中遇到的常见报错与解决
前两天一位私信问:
在语言中有矩阵的语法如下:a=[1 2 3],但是如果矩阵中带有符号,那么会错误解析.例如[1 -2 3]会被解析为[-1 3],在处理中空格全部都被过滤掉了
于是用 rply定制版 做一简单演示。很久没用,碰到不少报错,最后的语法实现很不如意,但至少通过了最基本测试。
演示分支 在此,运行 % pytest tests/test_按语法分词.py
进行测试。此文将各种报错尽量还原并记录下如何解决。
起初草稿如下(在 tests/test_语法分析器.py,以为不需无空格语法支持):
def test_数组(self):
分词母机 = 分词器母机()
分词母机.添了("左括号", r"[")
分词母机.添了("右括号", r"]")
分词母机.添了("减", r"-")
分词母机.添了("数", r"\d")
分析器母机 = 语法分析器母机(["数", "左括号", "右括号", "减"])
@分析器母机.语法规则("数组 : 左括号 数列 右括号")
def 数组(p):
return [] + p[0]
@分析器母机.语法规则("数列 : 数")
@分析器母机.语法规则("数列 : 数列 数")
def 数列(片段):
if len(片段) == 1:
return [片段[0].getint()]
if len(片段) == 2:
return 片段[0] + [片段[1].getint()]
@分析器母机.语法规则("正负数 : 数")
@分析器母机.语法规则("正负数 : 减 数")
def 正负数(片段):
if len(片段) == 1:
return 片段[0].getint()
if len(片段) == 2:
return -片段[0].getint()
分词器 = 分词母机.产出()
分析器 = 分析器母机.产出()
assert 分析器.分析(分词器.lex('[1 -2 3]')) == [1, -2, 3]
运行测试报错:
def test_数组(self):
分词母机 = 分词器母机()
> 分词母机.添了("左括号", r"[")
tests/test_语法分析器.py:55:
...
FAILED tests/test_语法分析器.py::TestParser::test_数组 - re.error: unterminated character set at position 0
加上转义符:
分词母机.添了("左括号", r"\[")
分词母机.添了("右括号", r"\]")
报错:
@分析器母机.语法规则("数列 : 数")
@分析器母机.语法规则("数列 : 数列 数")
def 数列(片段):
if len(片段) == 1:
> return [片段[0].getint()]
E AttributeError: '词' object has no attribute 'getint'
片段[0]
为一个词(token),本身没有getint方法。因为草稿是从另一个测试用例改出来的,而该用例用了 BoxInt。于是改为自带的getstr:
@分析器母机.语法规则("正负数 : 数")
@分析器母机.语法规则("正负数 : 减 数")
def 正负数(片段):
if len(片段) == 1:
return int(片段[0].getstr())
if len(片段) == 2:
return -int(片段[0].getstr())
另还有个警告:
ParserGeneratorWarning: 规则 '正负数' 无用
发现 数列
规则未使用 正负数
,改为:
@分析器母机.语法规则("数列 : 正负数")
@分析器母机.语法规则("数列 : 数列 正负数")
def 数列(片段):
if len(片段) == 1:
return [片段[0]]
if len(片段) == 2:
return 片段[0] + 片段[1]
接下来就看不清楚了:
> assert 分析器.分析(分词器.lex('[1 -2 3]')) == [1, -2, 3]
tests/test_语法分析器.py:85:
...
rply/分词器.py:114: 分词报错
======================================================= short test summary info =======================================================
FAILED tests/test_语法分析器.py::TestParser::test_数组 - rply.报错.分词报错: (None, SourcePosition(idx=2, lineno=1, colno=3))
想起 [1
这种无空格设计,估计需要无空格语法解析支持,于是改为(顺便放到 tests/test_按语法分词.py):
assert 分析器.按语法分词(分词器.lex('[1 -2 3]')) == [1, -2, 3]
没好多少:
rply.报错.按语法分词报错: [(0, '左括号'), (1, '数'), (2, None)]
于是将 分词器.py
中的 调试细节
改为 1,看得到分词尝试细节,比如:
变长模式已试完:词名:左括号, 模式=re.compile('\\[')
位置 0 第一次尝试:词名:右括号, 模式=re.compile('\\]')
位置 0 第一次尝试:词名:减, 模式=re.compile('-')
位置 0 第一次尝试:词名:数, 模式=re.compile('\\d')
完全无匹配
栈已空!
看来是根据语法分词失败。于是简化用例:
assert 分析器.按语法分词(分词器.分词('[1]')) == [1]
#assert 分析器.按语法分词(分词器.分词('[1 -2 3]')) == [1, -2, 3]
至少分词通过。报错看得懂了:
@分析器母机.语法规则("数组 : 左括号 数列 右括号")
def 数组(p):
> return [] + p[0]
E TypeError: can only concatenate list (not "词") to list
...
找到匹配词:], 路径历史:{0: 词名:左括号, 模式=re.compile('\\['), 1: 词名:数, 模式=re.compile('\\d'), 2: 词名:右括号, 模式=re.compile('\\]')}
改正错误:
@分析器母机.语法规则("数组 : 左括号 数列 右括号")
def 数组(p):
return p[1]
第一个用例通过~ 加负数测试:
assert 分析器.按语法分词(分词器.分词('[-1]')) == [-1]
继续报错:
if len(片段) == 2:
> return -int(片段[0].getstr())
E ValueError: invalid literal for int() with base 10: '-'
继续改:
if len(片段) == 2:
return -int(片段[1].getstr())
第二个用例通过~ 第三个:
assert 分析器.按语法分词(分词器.分词('[1 3]')) == [1, 3]
分词失败:
变长模式已试完:词名:左括号, 模式=re.compile('\\[')
位置 0 第一次尝试:词名:右括号, 模式=re.compile('\\]')
位置 0 第一次尝试:词名:减, 模式=re.compile('-')
位置 0 第一次尝试:词名:数, 模式=re.compile('\\d')
完全无匹配
栈已空!
======================================================= short test summary info =======================================================
FAILED tests/test_按语法分词.py::Test按语法分词::test_数组 - rply.报错.按语法分词报错: [(0, '左括号'), (1, '数'), (2, None)]
看来是空格导致的。头痛医头吧:
分词母机.添了("数", r"\d\s*")
报错:
@分析器母机.语法规则("数列 : 数列 正负数")
def 数列(片段):
if len(片段) == 1:
return [片段[0]]
if len(片段) == 2:
> return 片段[0] + 片段[1]
E TypeError: can only concatenate list (not "int") to list
片段[0] 是列表,片段[1] 是数,于是:
if len(片段) == 2:
return 片段[0] + [片段[1]]
第三个通过~继续测试:
assert 分析器.按语法分词(分词器.分词('[-1 3]')) == [-1, 3]
assert 分析器.按语法分词(分词器.分词('[1 -2 3]')) == [1, -2, 3]
assert 分析器.按语法分词(分词器.分词('[1 -2 3 -4 5 6 -7]')) == [1, -2, 3, -4, 5, 6, -7]
全通过~,才发现不需要 strip() 数末的空格,这么妙么?