likes
comments
collection
share

CPython开发实战:魔改lambda函数(二)

作者站长头像
站长
· 阅读数 18

本次实战内容是受到Javascript的启发,将Python为人诟病已久的lambda函数改成Javascript风格的箭头函数,效果如下:

CPython开发实战:魔改lambda函数(二)

上一章讲到修改.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_targetsstar_expressionblockelse_block在其他地方定义。~意为cut,代表无论star_expression能否匹配成功,都继续往后匹配。

如果匹配成功,则执行函数_PyAST_For(t, ex, b, el, NEW_TYPE_COMMENT(p, tc), EXTRA)

除此以外,该规则还可以匹配带ASYNCfor循环。

本步骤位于语法分析阶段,通过语法分析文件生成的语法分析器可实现AST生成。

CPython开发实战:魔改lambda函数(二)

箭头函数的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树。

CPython开发实战:魔改lambda函数(二)

运行git status可以看到修改的文件:

CPython开发实战:魔改lambda函数(二)

  • 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。