为对「木兰」的 1400 多行用 rply 实现的语法规则相关代码进行分析,学习了 rply 的入门文档。详见: 【悬赏】对原始可执行文件的功能进行测试 · Issue #4 · MulanRevive/bounty​ github.com

用于解析简单加减法,只需下面的代码

from rply import LexerGenerator

分词器母机 = LexerGenerator()

分词器母机.add('数', r'\d+')

分词器母机.add('加', r'\+')
分词器母机.add('减', r'-')

分词器 = 分词器母机.build()

for  in 分词器.lex('1+1-1'):
    print()

lg本为 lexer generator 的缩写,generator 感觉像是机床中母机的概念。

在 python 3.7.4 下测试:

$ python 成功.py 
Token('数', '1')
Token('加', '+')
Token('数', '1')
Token('减', '-')
Token('数', '1')

挺好,支持中文词名,如”数/加/减”,很齐整。

下面,入门语法分析器生成

目标是分析简单的四则运算:

<表达式> ::= "\d+"
               | <表达式> "+" <表达式>
               | <表达式> "-" <表达式>
               | <表达式> "*" <表达式>
               | <表达式> "/" <表达式>
               | "(" <表达式> ")"

如前,先写分词器,发觉文档中此行lg.ignore(‘\s+’)的正则表达式少了个 r。

并编写语法树,为对应节点编写求值方法。

最后是语法分析器生成。限于 python 水平,直接将分词器、语法树写在了一起以便测试。

感觉 rply 对中文标识符的支持很不错,基本上业务相关部分都可以中文化。还有运算符优先级部分尚未摸透,不过可以待看「木兰」的优先级部分代码时研究:

分析器母机 = ParserGenerator(
    # 所有词名
    ['数', '左括号', '右括号',
     '加', '减', '乘', '除'
    ],
    # A list of precedence rules with ascending precedence, to
    # disambiguate ambiguous production rules.
    precedence=[
        ('left', ['加', '减']),
        ('left', ['乘', '除'])
    ]
)

@分析器母机.production('表达式 : 数')
def 数表达式(片段):
    # 匹配规则右部的片段列表
    return (int(片段[0].getstr()))

@分析器母机.production('表达式 : 左括号 表达式 右括号')
def 括号表达式(片段):
    return 片段[1]

@分析器母机.production('表达式 : 表达式 加 表达式')
@分析器母机.production('表达式 : 表达式 减 表达式')
@分析器母机.production('表达式 : 表达式 乘 表达式')
@分析器母机.production('表达式 : 表达式 除 表达式')
def 二元运算表达式(片段):
     = 片段[0]
     = 片段[2]
    运算符 = 片段[1]
    if 运算符.gettokentype() == '加':
        return (, )
    elif 运算符.gettokentype() == '减':
        return (, )
    elif 运算符.gettokentype() == '乘':
        return (, )
    elif 运算符.gettokentype() == '除':
        return (, )
    else:
        raise AssertionError('不应出现')

分析器 = 分析器母机.build()

27 行的规则部分大约花了一小时参透,以此速度对「木兰」的 ply 代码进行分析,最少需要50小时,嗯,可以接受。当然需要更多时间编写测试代码。