7.5 一个示例:添加一个约等于比较运算法

如果要将本章节所有内容串联起来,你可以在 Python 语言中添加一个新的语法特性并重新编译 CPython 来掌握本章节内容。

比较表达式会比较两个或多个值:

在比较表达式中用到的运算符被称为比较运算符。以下可能是一些你能认出来的比较运算符:

  • 小于:<;

  • 大于:>;

  • 等于:==;

  • 不等于:!=。

参见

PEP 207 为 Python 2.1 提出了在数据模型中的丰富比较运算符。此 PEP 包含了自定义 Python 类型实现比较方法的上下文、历史以及解释。

现在,让我们来添加另外一个比较运算符:约等于,由 ~= 表示,其将拥有如下行为:

  • 如果你比较一个浮点数和一个整数,那么它会将浮点数转换为整数并比较结果;

  • 如果你比较两个整数,那么它会使用普通的相等运算符。

新的运算符会在交互式解释器中返回如下结果:

为了添加新的运算符,首先你需要更新 CPython 语法。在 Grammar/python.gram 文件中,比较运算符被定义为一个符号(symbol):comp_op

代码不翻译

修改 compare_op_bitwise_or_pair 表达式以允许新的 ale_bitwise_or 对:

代码不翻译

在现有的 is_bitwise_or 表达式下定义新的 ale_bitwise_or 表达式:

...
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or ...
ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or
    { _PyPegen_cmpop_expr_pair(p, AlE, a) }

这个新的类型定义了一个包含 '~=' 终结符号的命名表达式:ale_bitwise_or

函数调用 _PyPegen_cmpop_expr_pair(p, AlE, a) 是一个从 AST 获取 cmpop 节点的表达式。类型为 AlEAlmost Equal。

接下来,在 Grammar/Tokens 中添加单词符号(token):

ATEQUAL '@='
RARROW '->'
ELLIPSIS '...'
COLONEQUAL ':='
# Add this line
ALMOSTEQUAL '~='

为了更新 C 语言中的语法和单词符号,你需要重新生成头文件。

在 MacOS 或 Linux上使用以下命令:

$ make regen-token regen-pegen

在 Windows 上运行 PCBuild 目录中的以下命令:

> build.bat --regen

这些步骤会自动更新分词器(tokenizer)。比如:打开 Parser/token.c 源文件,你可以看到 PyToken_TwoChars() 函数已经被修改:

如果你在这个阶段重新编译 CPython 并且打开一个交互式解释器,那么你可以看到分词器已经可以成功识别到这个单词符号,但是此时 AST 还不知道如何处理它:

这个异常是由 Python/ast.c 文件中的 ast_for_comp_op() 函数引起,原因是交互式解释器还无法把 ALMOSTEQUAL 识别为一个比较运算符中的一个有效运算符。

Compare 是一个定义在 Parser/Python.asdl 文件中的一个表达式类型,其具有的属性:左表达式、操作符(ops)列表以及一个要比较的表达式列表(comparators):

Compare 定义中是对 cmpop 枚举的引用:

这是一个可以充当比较运算符的抽象语法树叶子节点的列表集合。我们定义的约等于运算符没在里面所以需要补充完整。更新这个选项集合来包含一个新类型:ALE

接下来,我们重新生成 AST 来更新 AST 的 C 头文件:

这个操作会更新 Include/Python-ast.h 文件中的比较运算符(_cmpop)枚举,来包含 ALE 选项:

抽象语法树此时还没有能力将 ALMOSTEQUAL 单词符号和 ALE 比较运算符关联上。所以你还要为抽象语法树更新 C 语言代码。

找到 Python/ast.c 文件中的 ast_for_comp_op() 函数并找到运算符单词符号的 switch 语句。这个函数会返回 _cmop 枚举值中的一个。

添加两行代码,用于捕捉 ALMOSTEQUAL 单词符号并返回 AlE 比较运算符:

在这个阶段,分词器和 AST 可以解析代码,但是编译器还是不知道如何处理这个运算符。为了测试抽象语法树的表示,我们可以使用 ast.parse() 函数来探索表达式的第一个运算符:

这是我们 AlE 比较运算符类型的一个实例,因此抽象语法树才能正确的分析代码。

在下一个章节,你会继续学习 CPython 编译器是如何工作的,并继续修改约等于运算符来构建其执行逻辑。

Last updated