1 个不稳定版本
0.1.0 | 2020 年 4 月 1 日 |
---|
#335 in 模板引擎
105KB
1K SLoC
批量示例生成器
批量示例生成器是一个使用 Rust 创建的工具,用于基于 pest 语法(PEG)创建数十/数百/数千/数百万个随机示例。它可以用于生成 AI 模型训练的字符串/结构化数据,或像语法模糊器一样查找错误。
目录
摘要
bulk_examples_generator 有两种形式:二进制或 crate(库)。
如果您只想生成示例,请参阅 二进制使用
如果您想为自己的需求使用 crate,请参阅 Crate 使用
二进制使用
好吧!您只是想生成一些示例或测试此应用程序。
需求
- 您必须安装 Rust(最低版本:1.36.0)
就这样了吗?
是的。
为什么没有可执行文件可用?
一旦这个库变得稳定,我将为 Windows 和 Linux 创建可执行文件。
入门
- 安装应用程序或克隆仓库
cargo install bulk_examples_generator
如果您克隆了仓库而不是使用 bulk-examples-generator
,则必须使用 cargo run -- <comands>
- 创建一个PEST语法并将其放入文件中,例如:
mytest.pest
// I like Rust!
language = {"Rust" | "Python" | "Go" | "Java" | "PHP" | "Haskell"}
one = {"1"}
daysNumber = {one ~ " day" | !one ~ ASCII_NONZERO_DIGIT ~ " days"}
sentence = {"I have been programming in " ~ language ~ " for " ~ daysNumber ~ "."}
- 执行以下命令
bulk_examples_generator --grammar mytest.pest --quantity 3 --start-rule sentence --out-type stdout
或者更短
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o stdout
您将看到类似的内容
I have been programming in Rust for 3 days.
I have been programming in PHP for 5 days.
I have been programming in Haskell for 1 day.
使用
使用此应用程序需要4个参数
- PEST表示法中的语法
- 一个数字(生成示例的数量)
- 起始规则(生成开始的地方)
- 输出类型(保存或打印示例的位置)
bulk_examples_generator -g life.pest -q 3000 -s love -o stdout
语法
要使用批量示例生成器,您需要知道如何用PEST表示法编写语法。
注意:并非所有PEST语法都受支持,请在此处查看
您可以在此处验证您的语法
附加功能
在语法中添加的唯一功能是BLACKLIST生成
黑名单生成
这是一个更深入的机制,用于避免在特定规则中生成元素
假设您有这个语法
Text = {"Random text"}
FlowContent = {Header | Main | Form | Section | Text }
// Header can't contains Main Element
Header = {"<header>" ~ FlowContent* ~ "</header>"}
Main = {"<main>" ~ FlowContent+ ~ "</main>"}
// Form can't contains Form Element
Form = {"<form>" ~ FlowContent* ~ "</form>"}
Section = {"<section>" ~ FlowContent+ ~ "</section>"}
这可能会生成像这样的问题元素
<header>
<main> <!-- main is forbidden at this point -->
<section>
<form>
</form>
Random text
</section>
</main>
</header>
为了满足限制,您可以这样做
Text = {"Random text"}
FlowContent = {Header | Main | Form | Section | Text }
FlowContentWithoutMain = {Header | Form | Section | Text }
FlowContentWithoutForm = {Header | Main | Section | Text }
Header = {"<header>" ~ FlowContentWithoutMain* ~ "</header>"}
Main = {"<main>" ~ FlowContent+ ~ "</main>"}
Form = {"<form>" ~ FlowContentWithoutForm* ~ "</form>"}
Section = {"<section>" ~ FlowContent+ ~ "</section>"}
但如果规则或限制很多,这会使语法变得复杂。
那么使用否定谓词会怎么样呢?
Text = {"Random text"}
FlowContent = {Header | Main | Form | Section | Text }
Header = {"<header>" ~ (!Main ~ FlowContent)* ~ "</header>"}
Main = {"<main>" ~ FlowContent+ ~ "</main>"}
Form = {"<form>" ~ (!Form ~ FlowContent)* ~ "</form>"}
Section = {"<section>" ~ FlowContent+ ~ "</section>"}
否定仅在一层上起作用,然后其他规则可以生成主元素。
<header>
<section>
<main> <!-- main still is forbidden at this point -->
<section>
<form>
Random text
</form>
</section>
</main>
</section>
</header>
解决方案是使用BLACKLIST生成
Text = {"Random text"}
FlowContent = {Header | Main | Form | Section | Text }
Header = {"<header>" ~ "|BLACKLIST|I|Main|" ~ FlowContent* ~ "|BLACKLIST|R|Main|" ~ "</header>"}
Main = {"<main>" ~ FlowContent+ ~ "</main>"}
Form = {"<form>" ~ "|BLACKLIST|I|Form|" ~ FlowContent* ~ "|BLACKLIST|R|Form|" ~ "</form>"}
Section = {"<section>" ~ FlowContent+ ~ "</section>"}
<!-- A correct generation -->
<main>
<header>
<section>
<form></form>
</section>
Random textRandom text
</header>
</main>
它是如何工作的?
Blacklist是一个列表,用于避免在生成的任何级别中打开规则
您可以添加如下规则"|BLACKLIST|I|MyRule|"
或同时添加多个规则,如下所示:"|BLACKLIST|I|MyRule|OtherRule|"
您可以通过如下方式删除规则"|BLACKLIST|R|MyRule|"
或同时删除多个规则,如下所示:"|BLACKLIST|R|MyRule|OtherRule|"
起始规则
需要一个起始规则来开始生成,如果语法中不存在起始规则,则示例将打印规则的名称
bulk_examples_generator -g mytest.pest -q 3 -s cookies -o stdout
cookies
cookies
cookies
输出类型(stdout、文件、文件夹或调试)
Stdout
您可以使用参数--out-type stdout
或-o stdout
在stdout中打印示例
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o stdout
您将看到类似的内容
I have been programming in Rust for 3 days.
I have been programming in PHP for 5 days.
I have been programming in Haskell for 1 day.
您可以使用--print-progress
标志打印生成的示例数量,枚举不是顺序的,因为生成是并行的
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o stdout
您将看到类似的内容
Example #2 generated:
I have been programming in Rust for 3 days.
Example #3 generated:
I have been programming in PHP for 5 days.
Example #1 generated:
I have been programming in Haskell for 1 day.
文件
您可以使用">"将所有示例保存到文件中。
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o stdout > "big-file.txt"
文件夹
您可以使用--out-type folder
或-o folder
与--output-folder
一起选择文件夹并将示例保存到该文件夹中(每个示例一个文件)。
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o folder --output-folder examples
默认情况下,所有文件都将具有名为"example-{}.txt"的名称,其中{}是示例的编号,您可以使用--template-name
选项进行更改
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o folder --output-folder examples --template-name "book-number-{}.txt"
在此模式下,您将看到一个进度条,显示已过时间以及预计剩余时间。
调试
目前您可以使用--out-type debug
或-o debug
打印加载的选项、语法AST、生成的进度,最后以向量的形式打印生成的示例。
bulk_examples_generator -g mytest.pest -q 3 -s sentence -o debug
不久的将来,我期望通过提供更多信息来改进调试模式。
配置文件
让我们以下面的语法为例
// configGramar.pest
Body = {Element+}
Element = {Paragraph | Anchor | Text}
Paragraph = {"<p>" ~ Element* ~ "</p>"}
Anchor = {"<a>" ~ (Text | Paragraph)+ ~ "</a>"}
Text = {ASCII_ALPHA{1,5}}
运行几次
bulk_examples_generator-g configGramar.pest-q1 -o stdout-s Body
您可以看到,生成有时非常长,这是技术上的正确做法,因为修饰符“*”和“+”表示零次或多次以及一次或多次,更多表示无穷大,但我们没有无穷的时间,因此这就是为什么存在上限。
要使用配置,只需创建一个包含参数的 TOML 文件,并使用 --config-file
或 c
来加载生成中的文件。
bulk_examples_generator -g configGramar.pest -q 1 -o stdout -s Body -c config.toml
您可以在配置文件中使用 8 个参数
全局参数
expand_limit
在生成中打开的最大规则数,当达到此限制时,后续规则的生成将返回参数 text_expand_limit。
默认值: 无(无限制)
soft_limit
要处理一个规则,元素被放置在堆栈中。如果语法非常深或递归,堆栈中的元素数量将很大,如果堆栈的长度超过此参数的值,则将将修饰符“*”和“+”分别转换为范围 [0,1] 和 [1,2],以减少要处理的项的数量。
默认值 10.000
hard_limit
在生成示例的过程中,每个处理的表达式都会增加表达式计数器,如果参数值达到,则从现在开始的所有未处理的表达式将不会产生任何结果,标识符将只返回参数 text_expand_limit。
默认值 25.000
limit_depth_level
示例的整个生成过程都在堆栈中发生(没有递归),除了一个小表达式 !b ~ a
。
如果您有一个具有许多否定词的递归语法,则参数 limit_depth_level 返回参数 text_expand_limit。
默认值 200
表达式参数
参数描述 | 描述 | 默认值 |
---|---|---|
text_expand_limit | 这是在 hard_limit 或 limit_depth_level 达到时规则返回的文本。 | "" |
upper_bound_zero_or_more_repetition | 这是 rule* 中的上限。 |
5 |
upper_bound_one_or_more_repetition | 这是 rule+ 中的上限。 |
5 |
upper_bound_at_least_repetition | 这是 rule{n,} 中的上限。 |
10 |
max_attempts_negation | 在 !b ~ a 中生成 a 的最大尝试次数。 |
100 |
命令行选项
bulk_examples_generator--帮助
点击查看选项
USAGE:
bulk-examples-generator.exe [FLAGS] [OPTIONS] --grammar <grammar> --out-type <out-type> --quantity <quantity> --start-rule <start-rule>
FLAGS:
-h, --help
Prints help information
--print-progress
Used when the out-type is stdout
Print "Example #n generated:" before print the example
-V, --version
Prints version information
OPTIONS:
-c, --config-file <config-file>
Config file for generate elements, for more details pleaser refer to README Default config available in
src/config/default.toml
-g, --grammar <grammar>
Path of grammar for generate examples
-o, --out-type <out-type>
Where to write the examples: one of debug, stdout, folder
debug: Print results in stdout (vec form) for debugging purposes
stdout: Print results in stdout
folder: Create one file for each example (use template_name for personalize the filename)
--output-folder <output-folder>
Output folder to save the examples
-q, --quantity <quantity>
Quantity of examples to generate
-s, --start-rule <start-rule>
Rule to start generation of examples
-t, --template-name <template-name>
Name of the files, e.g. html-test-{}.html, {} will be used for enumerating the example [default:
example-{}.txt]
基准测试
批量示例生成器的速度有多快?
目前应用程序使用并行性在示例级别生成示例,这意味着如果您有四个逻辑核心,那么您的电脑可以同时生成 4 个示例,从而在生成过程中提供速度提升。
您可以使用以下命令(入门示例)执行基本基准测试,以测试生成 100 个示例的速度。
注意:请记住,为了获得基准测试的准确结果,您必须关闭所有不必要的程序,并且笔记本电脑连接到电源。
cargobench ----verbose --measurement-time 120
在我的笔记本电脑上,结果是 [最小值 平均值 最大值]
- Intel i5 6198DU(2 个物理核心/4 个逻辑核心)
- DDR4 16GB
- SSD Sata 西部数据
Benchmarking Readme grammar example: Warming up for 3.0000 s
Benchmarking Readme grammar example: Collecting 100 samples in estimated 138.32 s (20200 iterations)
Benchmarking Readme grammar example: Analyzing
Readme grammar example time: [7.9290 ms 8.0080 ms 8.0937 ms]
slope [7.9290 ms 8.0937 ms] R^2 [0.6422301 0.6410218]
mean [7.5284 ms 7.8428 ms] std. dev. [747.06 us 860.73 us]
median [7.6470 ms 8.0680 ms] med. abs. dev. [686.19 us 1.3668 ms]
此测试不包括打印或保存示例所需的时间,仅包括生成示例所需的时间。
如果我外推结果(不要这样做,只是一个例子),我们将得到以下时间
示例 | 估算时间 |
---|---|
100 | 8毫秒 |
1.000 | 80毫秒 |
10.000 | 0,8秒 |
100.000 | 8秒 |
1.000.000 | 80秒 |
10.000.000 | 13.3分钟 |
对于基本的语法来说,这是一个可接受的时间,我将在之后添加更大语法的更多基准。
它可能会更快吗?
是的,目前并行化是在示例级别,但是有一些规则可以并行生成,从而提高大语法的生成速度。目前我想纠正应用程序可能出现的错误。
Crate 使用
入门
只需在Cargo.toml中添加即可。
bulk_examples_generator = "^0.1"
示例
// Default configuration for the generator
let mut config: GeneratorConfig = Default::default();
// Grammar string
let mut grammar = r#"
language = {"Rust" | "Python" | "Go" | "Java" | "PHP" | "Haskell"}
one = {"1"}
daysNumber = {one ~ " day" | !one ~ ASCII_NONZERO_DIGIT ~ " days"}
sentence = {"I have been programming in " ~ language ~ " for " ~ daysNumber ~ "."}
"#;
// Generate the examples
let results = parallel_generate_examples(
grammar.to_string(), // The grammar
5, // Quantity of examples
"sentences".to_string(), // Start rule
&config, // Config of the generator
false, // Print progress
false, // Print in stdout, false return a vector with the examples
);
println!("{:?}", results);
配置
// Default configuration for the generator
let mut config: GeneratorConfig = Default::default();
// Change the configuration
config.upper_bound_one_or_more_repetition = 20;
// Or load a config from TOML file
let mut config_file = GeneratorConfig::new("config.toml").unwrap();
可用函数
目前有4个函数可用
parallel_generate_examples
用于生成示例的函数。 文档
parallel_generate_save_examples
用于生成和保存示例的函数。 文档
compile_grammar
编译语法字符串,创建一个HashMap,其中规则作为键,组件作为条目。这可以用来查看AST或检查生成的示例的有效性 文档
parse_input
使用提供的语法解析生成的示例,是pest的解析函数的符号链接。用于检查示例的有效性 文档
支持的语法
改编自pest参考。
✔️标志表示目前该语法受支持,并且我已经在基本/中等水平上测试了该语法(如果发现错误,请创建问题!)。
❓标志表示目前支持未知,也许我已经编写了支持代码,但没有用任何语法进行测试。
❌标志表示目前不受支持。我知道我没有编写与该语法相关的任何代码。
语法 | 含义 | 受支持 | 观察 |
---|---|---|---|
foo= { ... } |
[常规规则] | ✔️ | |
bar= _{ ... } |
[静默] | ❓ | |
baz= @{ ... } |
[原子] | ❓ | |
qux= ${ ... } |
[复合-原子] | ❓ | |
plugh= !{ ... } |
[非原子] | ❓ | |
内置ASCII规则 | [内置ASCII规则] | ✔️ | |
内置Unicode规则 | [内置Unicode规则] | ❌ | |
"abc" |
[精确字符串] | ✔️ | |
^"abc" |
[不区分大小写] | ❓ | 受支持但尚未测试 |
'a'..'z' |
[字符范围] | ✔️ | |
ANY |
[任意字符] | ❓ | 受支持但尚未完全测试 |
foo~bar |
[序列] | ✔️ | |
baz|qux |
[有序选择] | ✔️ | |
foo* |
[零或更多] | ✔️ | 有一个参数可以更改上限 |
bar+ |
[一或更多] | ✔️ | 有一个参数可以更改上限 |
baz? |
[可选] | ✔️ | |
qux{n} |
[恰好 n] | ✔️ | |
qux{m,n} |
[在 m 和 n (包含)之间] | ✔️ | |
qux{m,} |
[至少 m ] | ✔️ | 有一个参数可以更改上限 |
qux{,n} |
[最多 n (包含)] | ✔️ | |
&foo |
[正谓词] | ❌ | |
!bar |
[负谓词] | ✔️ ❗ | 它通过暴力方法在表达式如 !b ~ a 中受支持。 |
PUSH(baz) |
[匹配并推送] | ❌ | |
POP |
[匹配并弹出] | ❌ | |
PEEK |
[匹配但不弹出] | ❌ |
常见问题解答
为什么没有可执行文件可用?
一旦这个库变得稳定,我将为 Windows 和 Linux 创建可执行文件。
批量示例生成器的速度有多快?
查看基本基准 这里
它是如何工作的?代码是递归的?
基本上,代码构建语法的解析树,并遍历树以根据表达式生成随机元素;不,大部分代码不是递归的,一个示例的所有生成过程都在栈中发生,除了否定表达式 !b ~ a
。
我可以用pest解析生成的示例吗?
是的,也不是,大多数语法可以解析,但有示例无法解析。
最简单的无法解析的语法示例是
tricky = {("a" | "ab") ~ "c"}
// Generate
// ac -> parseable
// abc -> not parseable
这是因为操作符"|"表示有序选择,然后解析器尝试首先解析"a",然后"bc"失败。
为了解决这个问题,请遵循在编写语法时关于害虫建议的做法
一般来说,在编写带有选择的解析器时,应该将最长或最具体的选项放在首位,将最短或最通用的选项放在最后。
https://pest.rs/book/grammars/peg.html#ordered-choice
应用这些建议,新的语法将看起来像这样
easy = {("ab" | "a") ~ "c"}
// Generate
// ac -> parseable
// abc -> parseable
其他例子是 非回溯
相关问题
我想使用这个crate生成一百万个示例,为什么不能返回一个流或观察者/监听器模型或其他类似的东西呢?
对此表示歉意。目前我想要改进/测试语法并简化代码,如果您想生成这么多示例,请分批调用函数并创建一个问题以了解对此的兴趣。
依赖关系
~11-21MB
~280K SLoC