40 个版本 (20 个稳定版)
4.0.6+ruby-3.1.2 | 2024年4月2日 |
---|---|
4.0.5+ruby-3.1.2 | 2023年7月20日 |
4.0.4+ruby-3.1.1 | 2022年10月17日 |
4.0.3+ruby-3.1.1 | 2022年6月21日 |
0.7.0 | 2020年11月19日 |
#71 在 解析器实现
2,761 每月下载量
用于 6 个包 (4 个直接使用)
1MB
25K SLoC
lib-ruby-parser
lib-ruby-parser
是用 Rust 编写的 Ruby 解析器。
基本用法
use lib_ruby_parser::{Parser, ParserOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let options = ParserOptions {
buffer_name: "(eval)".to_string(),
..Default::default()
};
let mut parser = Parser::new(b"2 + 2".to_vec(), options);
println!("{:#?}", parser.do_parse());
Ok(())
}
特性
简单来说;它速度快,精度高,并且界面美观。
与 Ripper/
RubyVM::AST
的比较
- 它基于 MRI 的
parse.y
,因此返回 完全相同 的令牌序列。 - 它已在下载量最高的 300 个 gem(约 4M 行代码)上进行了测试,以及
rubyspec
和ruby/ruby
仓库上进行了测试,并且与Ripper.lex
没有差异。 - 它比
Ripper
快 5 倍左右,Ripper 解析 4M 行代码需要约 24 秒,而lib-ruby-parser
需要约 4.5 秒。即约为 950K 行代码/秒。您可以在bench/
目录中找到基准测试,它们不包括任何 I/O 或 GC。 - 它有一个非常好的接口。AST 是强类型的并且有良好的文档。
- 它不会丢弃关于令牌的信息。所有节点都有关于它们源位置的信息。
与 whitequark/parser 的比较
- 它快得多(在相同的机器上,4M 行代码的语料库可以在 245 秒内被解析)
- 它有一个非常相似的接口(从 AST 结构和错误报告方面来说)
- 然而,AST 是强类型的,因此如果某个东西是可空的,则它将被明确定义和记录。
- 重要的是,它不依赖于 Ruby
测试语料库有 4,176,379
行代码和 170,114,575
字节,因此在我本地机器上的近似解析速度为
解析器 | 总时间 | 每秒字节 | 每秒行数 |
---|---|---|---|
lib-ruby-parser | ~4.4s | ~38,000,000 | ~950,000 |
ripper | ~24秒 | ~7,000,000 | ~175,000 |
whitequark/parser | ~245秒 | ~700,000 | ~17,000 |
语法版本管理
lib-ruby-parser
遵循 MRI/master。目前没有计划支持像 whitequark/parser
那样支持多个版本的方案。
库版本管理
Ruby版本 | lib-ruby-parser版本 |
---|---|
3.0.0 | 3.0.0+ |
3.1.0 | 4.0.0+ruby-3.1.0 |
从4.0.0
开始,lib-ruby-parser遵循SemVer。基本版本根据API更改而增加,而元数据与当前Ruby版本相匹配,即 X.Y.Z+ruby-A.B.C
表示
X.Y.Z
基础版本- 它解析Ruby
A.B.C
两个版本分别增加。
编码
默认情况下,lib-ruby-parser
只能解析编码为 UTF-8
或 ASCII-8BIT/BINARY
的源文件。
可以在ParserOptions
中传递一个decoder
函数,该函数接收由库识别的编码和字节数组。它必须返回UTF-8编码的字节数组或错误
use lib_ruby_parser::source::{InputError, Decoder, DecoderResult};
use lib_ruby_parser::{Parser, ParserOptions, ParserResult, LocExt};
fn decode(encoding: String, input: Vec<u8>) -> DecoderResult {
if "US-ASCII" == encoding.to_uppercase() {
// reencode and return Ok(result)
return DecoderResult::Ok(b"# encoding: us-ascii\ndecoded".to_vec());
}
DecoderResult::Err(InputError::DecodingError(
"only us-ascii is supported".to_string(),
))
}
let options = ParserOptions {
decoder: Some(Decoder::new(Box::new(decode))),
..Default::default()
};
let mut parser = Parser::new(b"# encoding: us-ascii\n3 + 3".to_vec(), options);
let ParserResult { ast, input, .. } = parser.do_parse();
assert_eq!(ast.unwrap().expression().source(&input).unwrap(), "decoded".to_string())
无效字符串值
Ruby不要求字符串字面量在它们的编码中有效。这就是为什么以下代码是有效的
# encoding: utf-8
"\xFF"
UTF-8中字节序列 255
是无效的,但MRI忽略了它。
但并非所有语言都支持它,这就是为什么字符串和符号节点封装了一个自定义的 StringValue
而不是普通的 String
。
如果你的语言支持无效字符串,你可以使用这个StringValue
的原始 .bytes
。例如,这个库的Ruby包装器可以这样做。
如果你的语言不支持它,最好调用 .to_string_lossy()
,该函数将所有不受支持的字符替换为特殊的 U+FFFD REPLACEMENT CHARACTER (�)
。
正则表达式
Ruby在解析过程中从字面量构建正则表达式以
- 验证它们
- 如果正则表达式用于匹配并且包含命名捕获,则声明局部变量
为了反映这种行为,lib-ruby-parser
使用 Onigurama 编译、验证和解析正则表达式字面量。
默认情况下,此功能是禁用的,但您可以通过启用 "onig"
功能来添加它。
Bison
lib-ruby-parser
的语法是使用一个为该项目编写的自定义 Bison框架 构建的。
对于开发,您需要在本地安装Bison的最新版本。当然,对于crates.io的发布构建不是必需的(因为编译的 parser.rs
已包含在发布构建中,并且 build.rs
转换它的代码已被排除)。
如果您直接从GitHub使用它,还需要Bison(因为 parser.rs
在gitignore中)
其他语言的绑定
性能分析
您可以使用 parse
示例
$ cargo run --bin parse --features=bin-parse -- --print=N --run-profiler --glob "blob/**/*.rb"
基准测试
使用 download.rb
脚本可以生成 4M 行代码的代码库
$ ruby gems/download.rb
然后,运行一个比较 Ripper
和 lib-ruby-parser
的脚本(附带的成果来自 2024 年 3 月)
$ ./scripts/bench.sh
Running lib-ruby-parser
Run 1:
Time taken: 4.4287733330 (total files: 17895)
Run 2:
Time taken: 4.4292764170 (total files: 17895)
Run 3:
Time taken: 4.4460961250 (total files: 17895)
Run 4:
Time taken: 4.4284508330 (total files: 17895)
Run 5:
Time taken: 4.4695665830 (total files: 17895)
--------
Running MRI/ripper
Run 1:
Time taken: 24.790103999897838 (total files: 17894)
Run 2:
Time taken: 23.145863000303507 (total files: 17894)
Run 3:
Time taken: 25.50493900012225 (total files: 17894)
Run 4:
Time taken: 24.570900999940932 (total files: 17894)
Run 5:
Time taken: 26.0963700003922 (total files: 17894)
模糊测试
首先,请确保切换到夜间模式
$ rustup default nightly
然后安装 cargo-fuzz
$ cargo install cargo-fuzz
并运行模糊器(根据需要更改 --jobs
的数量或删除它以仅运行一个并行进程)
$ RUST_BACKTRACE=1 cargo fuzz run parse --jobs=8 -- -max_len=50
依赖关系
~0.5–11MB
~133K SLoC