> For the complete documentation index, see [llms.txt](https://hai-shi.gitbook.io/cpython-internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hai-shi.gitbook.io/cpython-internals/5-language-and-grammar/5.4-regen-grammar.md).

# 5.4 重新生成语法

要查看 CPython 3.9 中引入的新 PEG 生成器：`pegen`，实际上，你可以更改部分 Python 语法。在 `Grammar/python.gram` 中搜索 `small_stmt`，查看小语句的定义：

图片不翻译

需要关注的是行 `'pass' { _Py_Pass(EXTRA) }` 用于 `pass` 语句：

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-c92669b20a169fcfbc3491cb00000db384204121%2F%E5%9B%BE5.5.2%20pass%E9%93%81%E8%B7%AF%E5%9B%BE.png?alt=media" alt=""><figcaption></figcaption></figure>

通过添加一个`|`以及 `proceed` 字面量来更改此行以接受终结符号（关键字）`'pass'` 或者 `'proceed'` 作为关键字。

```bash
| ('pass'|'proceed') { _Py_Pass(EXTRA) }
```

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-107f96b1db5a00ccd95672ea8f7e3faa11a13862%2F%E5%9B%BE5.5.3%20pass%E4%BF%AE%E6%94%B9%E5%90%8E%E7%9A%84%E9%93%81%E8%B7%AF%E5%9B%BE.png?alt=media" alt=""><figcaption></figcaption></figure>

接下来，重建语法文件。CPython 附带了脚本可以自动实现语法重新生成。

在 macOS 和 Linux 上，可以运行 `make regen-pegen` 命令：

```bash
$ make regen-pegen
```

在 Windows 上，可以使用 `--regen` 标志来运行 `PCBuild` 目录中的 build.bat：

```bash
> build.bat --regen
```

你应该能在输出上看到新的 `Parser/pegen/parse.c` 文件已重新生成。

当你重新编译 CPython 时使用重新生成的解析器表，它就将使用新的语法。使用上一章中用于操作系统的相同编译步骤。

如果你的代码编译成功，那么你就可以使用新的 CPython 二进制文件并启动一个交互式解释器。

在交互式解释器中，你可以尝试定义一个函数。用你编译进 Python 语法中的 `proceed` 关键字来替代 `pass` 语句。

```sh
$ ./python
Python 3.9 (tags/v3.9:9cf67522, Oct 5 2020, 10:00:00)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def example():
... proceed
...
>>> example()
```

恭喜你，你已经修改了 CPython 语法并且编译出属于你自己的 CPython 版本。

接下来，你会继续探索单词符号以及其与语法的关系。

### 单词符号（Token）

{% hint style="info" %}
**注**

译者注：“Token” 一词有多种翻译名称，大多数被翻译成 “记号”，但译者还是更认同《现代编译原理（赵克佳等人译）》中的翻译，将其翻译为 “单词符号” 更为贴切，简称 “单词”。
{% endhint %}

在 `Grammar` 目录中，除了语法文件之外，还有一个 `Grammar/Tokens` 文件，其包含了分析树的叶子节点上能找到的所有类型。每个单词都有一个名字和一个生成的唯一 ID。这些名字使引用分词器（`tokenizer`）中的单词符号变得更简单。

{% hint style="info" %}
**注**

`Grammar/Tokens` 文件是 Python 3.8 的新特性。
{% endhint %}

比如，左括号被称为 `LPAR` ，而顿号被称为 `SEMI` 。你会在本书后半部分看到这些单词符号。

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-c9cd5e0d48e2b94d965b2457eb93613f7be9ac6b%2F%E5%9B%BE5.5.7%20%E5%8D%95%E8%AF%8D%E4%B8%80.png?alt=media" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-3b0c04de6a7279f66a2b69809ee3f0328e740770%2F%E5%9B%BE5.5.8%20%E5%8D%95%E8%AF%8D%E4%BA%8C.png?alt=media" alt=""><figcaption></figcaption></figure>

与 `Grammar` 文件一样，如果你修改了 `Grammar/Token` 文件，你就需要重新运行 `pegen`。

你可以使用 CPython 的 `tokenize` 模块查看单词符号的运行情况。

{% hint style="info" %}
**注**

用 Python 编写的分词器是一个实用程序模块。实际的 Python 解析器使用一个不同的过程来识别单词符号。
{% endhint %}

创建一个 `test_tokens.py` 的简单 Python 脚本：

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-0239b86d0814edd3f50c0502aa0b8c251137048a%2F%E5%9B%BE5.5.9%20%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81.png?alt=media" alt=""><figcaption></figcaption></figure>

将 `test_tokens.py` 文件输入到 Python 的 `tokenize` 标准库中，你会看到一系列按行展开的单词和符号。

使用 -e 标志来输出准确的单词符号名称：

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-83fd31ee807c53869b7a51708b5bd19da6fb0050%2F%E5%9B%BE5.5.10%20%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%E5%8D%95%E8%AF%8D1.png?alt=media" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1029588898-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJhewUmzI3BNeGgeFH9Rv%2Fuploads%2Fgit-blob-86eaf521491460dce4d0abe3bfda620d4b51a966%2F%E5%9B%BE5.5.11%20%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%E5%8D%95%E8%AF%8D2.png?alt=media" alt=""><figcaption></figcaption></figure>

在输出中，第一列是行和列的坐标范围，第二列是单词符号名称，而最后一列则是单词符号的值。

在输出中，`tokenize` 模块已经暗示了几个单词符号：

* `ENCODING` 单词符号用于 `utf-8` ;
* `DEDENT` 用于结束函数申明；
* `ENDMARKER` 用于结束文件；
* 结尾处有一个空行。

一个最佳实践是在 Python 源文件末尾中保留一个空行。如果你忘记这样做，那么 CPython 会为你自动补上空行。

`tokeinze` 模块是用纯 Python 语言写的，相关代码位于 `Lib/tokenize.py` 中。

要查看 C 解析器的详细读数，你可以使用 `-d` 标志来运行 Python 的调试构建。使用之前创建的 `test_tokens.py` 脚本，用如下命令执行：

代码不翻译

在输出中，你可以非常明显的看到 `procceed` 是一个关键词。在下一章节中，你将会看到执行 Python 二进制文件如何进入到分词器以及从那里执行代码会发生什么。

如要你要清理代码，请回退对 `Grammar/python.gram` 的修改，重新生成语法文件以及清理构建和重新编译：

对于 macOS 或者 Linux 使用以下选项：

$ git checkout -- Grammar/Grammar

$ make regen-grammar

$ make clobber

$ make -j2 -s

或者对于 Windows 使用以下选项：

\> git checkout -- Grammar/Grammar

\> build.bat --regen

\> build.bat -t CleanAll

\> build.bat -t Build
