前两天一位私信问:

在语言中有矩阵的语法如下: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() 数末的空格,这么妙么?