7.2 CPython 解析器-分词器

编程语言对词法分析器(lexer)有不同的实现。有些语言会将词法分析器生成器(lexer generator)作为解析器生成器(parser generator)的一个补充。

CPython 有一个用 C 语言编写的解析器-分词器(parser-tokenizer)模块。

相关源文件

以下是与解析器-分词器相关的文件:

文件用途

Python/pythonrun.c

从输入执行解析器和编译器;

Parser/parsetok.c

解析器和分词器的实现;

Parser/tokenizer.c

分词器的实现;

Include/token.h

单词符号类型申明,由Tools/scripts/generate_token.py 文件生成;

Include/node.h

用于分词器的语法分析树节点接口和宏定义。

从文件向解析器输入数据

解析器-分词器的入口点是:PyParser_ASTFromFileObject()其获取一个文件句柄、编译器标志和PyArena 实例并把文件对象转换为一个模块。

这个函数主要有两个步骤:

  1. 通过 PyParser_ParseFileObject() 函数转换成具象语法树(CST);

  2. 通过使用抽象语法树(AST)函数 PyAST_FromNodeObject() 转换为抽象语法树或模块。

PyParser_ParseFileObject() 函数有两个重要的任务:

  1. 通过调用 PyTokenizer_FromFile() 创建一个分词器状态实例 tok_state

  2. 通过调用 parsetok() 将单词符号转换为一棵具象语法树(由一系列 node 节点构成)。

解析器-分词器工作流

解析器-分词器接收文本输入并循环执行分词器和解析器,直到光标到达文本末尾才结束(或者出现了一个语法错误)。

在执行前,解析器-分词器会先创建出一个 tok_state 实例,分词器会将所有的状态存放到这个临时的数据结构中。分词器状态包括当前光标位置、行等信息。

解析器-分词器通过调用 tok_get() 获取到下个单词符号(token)。解析器-分词器将生成的单词符号 ID 传递给解析器,其将使用解析器生成器生成的 DFA 在具象语法树上创建节点。

tok_get() 函数是整个 CPython 代码基线中最复杂的函数之一。其已经超过了 640 行并且包含了几十年的边缘案例、新语言特性和语法。

循环调用分词器和解析器的流程如下图所示:

PyParser_ParseFileObject() 函数返回的具象语法树根节点是下一阶段将具象语法树(CST)转换为抽象语法树(AST)的关键环节。

节点类型定义在 Include/node.h 文件中:

由于具象语法树是一棵包含语法、单词符号 ID 和符号的树,因此编译器很难基于 Python 语言做出快速决策。

在你学习 AST 之前,这里有个办法可以看到解析阶段的输出:CPython 有一个叫做 parser 的标准模块,其通过 Python API 暴露出一个 C 函数。

输出将会是一个数值,使用的是由 make regen-grammar 阶段产生的单词符号和符号数值,这些数值存储在 Include/token.h 头文件中:

为了更容易理解,你可以将所有这些符号和单词模块中的数字都放入到字典中,然后递归的将 parser.st2list() 的输出值替换为单词符号的名字。

你可以用一个简单的表达式来运行 lex()函数,比如:查看 a + 1 是如何表示成一棵语法分析树:

在输出中,你可以看到小写的符号,如:arith_expr ,以及大写的单词符号,如:NUMBER

Last updated