本次实战内容是受到Javascript的启发,将Python为人诟病已久的lambda函数改成Javascript风格的箭头函数,效果如下:
上一章讲到修改.asdl
文件,重新构造抽象语法树。本章将讲解修改语法分析文件,并利用pegen重新生成语法分析器。
6. 在Grammar/python.gram
文件中第675行添加如下代码:
| arrowlbd
并在第867行添加如下代码:
arrowlbd[expr_ty]:
| '(' a=[params] ')' '=>' b=expression {
_PyAST_ArrowLbd((a) ? a : CHECK(arguments_ty, _PyPegen_empty_arguments(p)), b, EXTRA)
}
cpython的语法分析器也是通过程序读取语法分析文件生成的程序,这个生成程序叫pegen
,这个语法分析文件是Grammar/python.gram
。语法分析文件是按树形结构描绘的,它遵守pegen
语法规则:
pegen
语法规则不同于EBNF
,它不是上下文无关文法。因此,(a|b)和(b|a)是不一样的。- 一次匹配失败不意味着该语句不合语法,
pegen
会尝试多次匹配。 pegen
可能会记录每次匹配的信息,以缩短匹配时间。- 只有当全文扫描结束后才能确定是否语法错误。
pegen
的语法规则如下:
rule_name[return_type]: expression
- rule_name:规则名称
- return_type:返回的类型,所有类型均在第五步生成的
Include/internal/pycore_ast.h
文件中获取 - expression:语法规则
常见的语法规则如下:
e1 e2
:先匹配e1
再匹配e2
e1 | e2
:若能匹配e1
则成功,否则继续匹配e2
(e)
: 匹配e1
,一般和其他操作符连用,比如(e1)*
[e]
或者e?
;可选的匹配e
e*
:匹配0个或多个e
e+
:匹配1个或多个e
s.e+
;以s
为分隔符,匹配1个或多个e
,比如1,2,3
匹配','.atom+
&e
:匹配e
,但不消费e
,即下次还会尝试匹配e
!e
:不匹配e
,但不消费e
&&e
:强制匹配e
,一般前面会有其他匹配式,比如try:
匹配'try' &&':'
语法规则可以后接花括号,花括号表示该语句匹配成功后的行为,所有行为均为C语言代码,均来自第五步生成的Include/internal/pycore_ast.h
文件中。
举例说明:
for_stmt[stmt_ty]:
| 'for' t=star_targets 'in' ~ ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
_PyAST_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA) }
| ASYNC 'for' t=star_targets 'in' ~ ex=star_expressions ':' tc=[TYPE_COMMENT] b=block el=[else_block] {
CHECK_VERSION(stmt_ty, 5, "Async for loops are", _PyAST_AsyncFor(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)) }
这是for
循环的pegen
语法规则。该语法规则匹配关键字for
+ star_targets
+ 关键字in
+ star_expression
+ 符号:
+ 可选的注释 + block
+ 可选的else_block
。其中,star_targets
、star_expression
、block
和else_block
在其他地方定义。~
意为cut,代表无论star_expression能否匹配成功,都继续往后匹配。
如果匹配成功,则执行函数_PyAST_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)
。
除此以外,该规则还可以匹配带ASYNC
的for
循环。
本步骤位于语法分析阶段,通过语法分析文件生成的语法分析器可实现AST生成。
箭头函数的AST树包含两个节点,一个是用来表示可选参数params
的节点,另一个是函数体的表达式expression
节点,中间用第二步声明的=>
符号相连。产生的行为为调用第五步生成的_PyAST_ArrowLbd
函数。因此箭头函数的语法规则为本步骤开头所示:
arrowlbd[expr_ty]:
| '(' a=[params] ')' '=>' b=expression {
_PyAST_ArrowLbd((a) ? a : CHECK(arguments_ty, _PyPegen_empty_arguments(p)), b, EXTRA)
}
7. 再次在命令行中运行PCBuild/build.bat --regen
生成词法分析程序
本步骤实现了语法分析器的生成,通过该语法分析器可以根据Python源码生成AST树。
运行git status
可以看到修改的文件:
Grammar/python.gram
:刚修改的语法分析文件Parser/parser.c
:修改的语法分析器文件
Parser/parser.c
新增了解析箭头表达式的arrowlbd_rule
函数。
8. 在命令行中运行PCBuild/build.bat
生成Python程序
此时运行生成的Python程序,输入(a) => a * a
。
程序不会提示任何报错,会直接crash。但直接运行以下脚本:
import ast
ast.dump(ast.parse('(a) => a + 1'), indent=4)
可以看到该语句会被正常的解析成AST。
Module(
body=[
Expr(
value=ArrowLbd(
args=arguments(
posonlyargs=[],
args=[
arg(arg='a')],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=BinOp(
left=Name(id='a', ctx=Load()),
op=Add(),
right=Constant(value=1))))],
type_ignores=[])
本章内容介绍了语法分析器的生成,并通过一个例子检验能否正常解析成AST。