6 个版本 (稳定)

1.4.0 2024年5月22日
1.3.1 2024年4月3日
1.2.0 2024年2月19日
1.1.0 2023年10月6日
0.1.0 2023年9月3日

#200 in 解析器实现

Download history 1736/week @ 2024-04-28 1079/week @ 2024-05-05 1068/week @ 2024-05-12 1770/week @ 2024-05-19 1825/week @ 2024-05-26 1914/week @ 2024-06-02 1414/week @ 2024-06-09 1700/week @ 2024-06-16 2632/week @ 2024-06-23 1597/week @ 2024-06-30 1357/week @ 2024-07-07 1830/week @ 2024-07-14 1370/week @ 2024-07-21 1742/week @ 2024-07-28 1578/week @ 2024-08-04 1520/week @ 2024-08-11

6,486 每月下载量

自定义许可证

535KB
14K SLoC

libcst/native

LibCST 的本地扩展,用于解析新的 Python 语法。

该扩展是用 Rust 编写的,并通过 PyO3 暴露给 Python。它与 libcst 一起打包,可以从 libcst.native 导入。默认情况下,LibCST API 使用此模块进行所有解析。

将来,解析器库可能会作为 Rust 包 单独打包。对此的拉取请求将非常受欢迎。

目标

  1. 尽可能接近地采用 CPython 语法定义,以减少维护负担。这意味着使用 PEG 解析器。
  2. 与纯 Python LibCST 解析器的功能一致性:API 应该易于从 Python 使用,支持以目标版本、字节和字符串作为输入进行解析等。
  3. [未来] 性能。期望的目标是在 2 倍 CPython 性能之内,这将使 LibCST 能够用于交互式用例(例如 IDE)。
  4. [未来] 错误恢复。解析器应该能够处理部分完整的文档,返回语法正确的部分的 CST,并返回找到的错误列表。

结构

该扩展分为两个 Rust 包:libcst_derive 包含一些宏,以方便 CST 节点的各种功能,而 libcst 包含 parser 本身(包括 Python 语法)、@bgw 实现的 tokenizer 以及 CST 节点的非常基本的表示。解析是通过以下步骤完成的:

  1. 对输入 utf-8 字符串进行分词(Rust 层不支持字节,它们通过 Python 包装器转换为 utf-8 字符串)
  2. 在分词后的输入上运行 PEG 解析器,这也会捕获结果语法树中的某些锚定标记
  3. 使用锚定标记来 膨胀 语法树为适当的 CST

这些步骤被封装在一个高级的 parse_module API 中,位于这里 这里,同时还包括 parse_statementparse_expression 函数,它们都只接受输入字符串和可选的编码。

这些 Rust 函数通过 这里 使用出色的 PyO3 库暴露给 Python,以及一个 IntoPy 特性,它主要通过 libcst_derive 中的宏实现。

黑客攻击

节点

所有 CST 节点都通过 #[cst_node] proc 宏标记,这会复制节点类型;对于一个名为 Foo 的节点,有

  • DeflatedFoo,它是解析阶段的输出,并且没有通过 crate 的 API 暴露。
    • 它有两个生命周期参数:'r(或语法中的 'input)是 Token 引用的生命周期,而 'a 是原始输入中 str 切片的生命周期。
    • TokenRef 字段包含在这里,而空白字段不包含。
    • 如果没有字段引用其他 CST 节点或 TokenRef,则有一个额外的(私有的)_phantom 字段来“包含”这两个生命周期参数(这是为了使所有 DeflatedFoo 类型的类型参数统一)。
    • 它实现了 Inflate 特性,该特性将 DeflatedFoo 转换为 Foo
  • Foo,这是在 crate 中公开暴露的,并且是通过对 DeflatedFoo 执行 Inflate 操作后的输出。
    • 它只保留了 DeflatedFoo 的第二个('a)生命周期参数来引用原始输入字符串的切片。
    • 空白字段包含在这里,但 TokenRef 不包含。
    • 为它实现了 IntoPy(假设启用了 py crate 功能),它包含将 Foo 转换回 Python 对象的代码;因此,Foo 上的字段与 Python CST 节点实现相匹配(除了带有 #[skip_py] 标记的字段)。

语法

语法主要是从 CPython 语法 的直接翻译,有一些例外。

  • 语法规则的输出是压缩的 CST 节点,它捕获了 AST 以及稍后用于空白解析的附加锚点令牌引用。
  • 语法规则必须是强类型化的,这是由 Rust 编译器强制执行的。相比之下,CPython 语法规则的类型化要松散一些。
  • CPython peg 解析器中的一些功能不支持 rust-peg:关键字、相互递归规则、特殊的 invalid_ 规则、~ 操作符以及提前终止解析器。

PEG解析器在VecToken上运行(更准确地说,是&'input Vec<Token<'a>>),并尽力避免分配任何字符串,仅使用引用。因此,输出节点不拥有任何字符串,而是引用原始输入的片段(因此几乎所有节点的生命周期参数都是'input, 'a)。

空白解析

Inflate特质负责接收一个“压缩”的骨架CST节点,并从锚标记中解析出相关的空白,以生成一个“膨胀”(正常)的CST节点。除了压缩节点外,膨胀还需要一个包含全局信息的空白配置对象,这些信息对于空白解析的某些方面是必需的,如默认缩进。

膨胀消耗压缩节点,同时修改由其引用的标记。这很重要,以确保空白只分配给最多一个CST节点。Inflate特质的实现需要确保所有空白都分配给一个CST节点;这通常通过往返测试来验证(即解析代码,然后将其重新生成以断言原始和生成的字节完全相同)。

一般惯例是,最高可能的节点拥有一定量的空白,这在自顶向下解析器Inflate中应该是简单实现的。在空白在兄弟节点之间共享的情况下,通常最左边的节点拥有空白,除非是尾随逗号和闭合括号的情况,后者拥有空白(与纯Python解析器的向后兼容性)。有关如何实现这一点,请参阅inflate_element的实现。

测试

除了运行Python测试套件外,您还可以使用以下命令运行一些用Rust编写的测试:

cd native
cargo test

这包括单元和往返测试。

此外,您可以使用cargo bench在基于x86的架构上运行一些基准测试。

代码格式化

使用cargo fmt来格式化您的代码。

依赖项

~2.6–10MB
~90K SLoC