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 解析器实现
6,486 每月下载量
535KB
14K SLoC
libcst/native
LibCST 的本地扩展,用于解析新的 Python 语法。
该扩展是用 Rust 编写的,并通过 PyO3 暴露给 Python。它与 libcst 一起打包,可以从 libcst.native
导入。默认情况下,LibCST API 使用此模块进行所有解析。
将来,解析器库可能会作为 Rust 包 单独打包。对此的拉取请求将非常受欢迎。
目标
- 尽可能接近地采用 CPython 语法定义,以减少维护负担。这意味着使用 PEG 解析器。
- 与纯 Python LibCST 解析器的功能一致性:API 应该易于从 Python 使用,支持以目标版本、字节和字符串作为输入进行解析等。
- [未来] 性能。期望的目标是在 2 倍 CPython 性能之内,这将使 LibCST 能够用于交互式用例(例如 IDE)。
- [未来] 错误恢复。解析器应该能够处理部分完整的文档,返回语法正确的部分的 CST,并返回找到的错误列表。
结构
该扩展分为两个 Rust 包:libcst_derive
包含一些宏,以方便 CST 节点的各种功能,而 libcst
包含 parser
本身(包括 Python 语法)、@bgw 实现的 tokenizer
以及 CST 节点的非常基本的表示。解析是通过以下步骤完成的:
- 对输入 utf-8 字符串进行分词(Rust 层不支持字节,它们通过 Python 包装器转换为 utf-8 字符串)
- 在分词后的输入上运行 PEG 解析器,这也会捕获结果语法树中的某些锚定标记
- 使用锚定标记来 膨胀 语法树为适当的 CST
这些步骤被封装在一个高级的 parse_module
API 中,位于这里 这里,同时还包括 parse_statement
和 parse_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解析器在Vec
的Token
上运行(更准确地说,是&'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