此刻支持的语法
{
print(3)
}
项目地址仍在此。
接续上文,添加“块”结构的支持。
首先在之前所有词法规则 之后 添加了如下两条词法规则:
分词器母机.add('前括号', '{\\r*\\n*')
分词器母机.add('后括号', '\\r*\\n*}')
相关语法修改如下:
声明 : 表达式声明
| 赋值
| 块
块 : 前括号 声明列表 后括号
块结构的语法树转换比较有趣。
@分析器母机.production('声明 : 块')
def 单块(片段):
return 语法树.如果(
条件=语法树.常数(True, 片段),
主体=片段[0],
否则=[],
片段=片段)
Python 的语法描述中并无block
一说。
看样子就是将{print(2)}
等价为了这样的 Python 代码:
if True:
print(2)
与下面 python 生成的语法树比对,的确如此。
>>> ast.dump(ast.parse("if True:\n\tprint(2)"), True, True)
"Module(body=[If(test=NameConstant(value=True, lineno=1, col_offset=3), body=[Expr(value=Call(func=Name(id='print', ctx=Load(), lineno=2, col_offset=1), args=[Num(n=2, lineno=2, col_offset=7)], keywords=[], lineno=2, col_offset=1), lineno=2, col_offset=1)], orelse=[], lineno=1, col_offset=0)])"
什么是NameConstant?这里说:
True, False or None. value holds one of those constants.
看起来就是用于真/假/空
三个值。Python 3.8 后就被Constant
替代了。
接下来困惑于前后括号正则表达式中的\\r*\\n*
部分许久。乍一看是为了支持括号前后带换行。
如果仅前括号之后有换行:
{
print(3)}
词法分析结果为:
Token('前括号', '{\n')
Token('标识符', 'print')
Token('(', '(')
Token('整数', '3')
Token(')', ')')
Token('后括号', '}')
语法分析无误,运行输出 3.
但是,如果源码的后括号之前有换行:
{
print(3)
}
则词法分析出了个”换行“:
Token('前括号', '{\n')
Token('标识符', 'print')
Token('(', '(')
Token('整数', '3')
Token(')', ')')
Token('换行', '\n')
Token('后括号', '}')
对比原始木兰的词法解析结果:
Token('LBRACE', '{\n')
Token('IDENTIFIER', 'print')
Token('(', '(')
Token('INTEGER_LITERAL', '3')
Token(')', ')')
Token('RBRACE', '\n}')
为何 \n 能被合在后括号中呢?
对照逆向工程各种尝试,最后发现是这条词法规则放置位置的问题:
分词器母机.add('换行', '\n')
这条规则在逆向中,置于所有 add 的规则最后,在 ignore 规则之前。同样处理之后,词法分析和运行结果即如预期。
看来,词法规则添加的先后决定了词法分析的优先级。只在所有前面的规则无法匹配时,才尝试后面的规则。
就这么点进展,可还是留下了一个隐隐的坑。在这个词法规则中:
分词器母机.add('前括号', '{\\r*\\n*')
逆向中最后还有个参数flags=(re.DOTALL)
根据 rply 源码,这个参数在正则匹配时使用,但不解的是,python 文档中的re.DOTALL似乎只对正则中的.
有效,而前后括号的正则描述中并无.
,那么这个参数是做什么用的呢?
暂且绕过继续。