#pest-grammar #grammar #random #generator #data #random-string #save-file

bin+lib bulk_examples_generator

基于 pest 语法(PEG)创建数十/数百/数千/数百万个随机示例。它可以用于生成 AI 模型训练的字符串/结构化数据,或像语法模糊器一样查找错误。

1 个不稳定版本

0.1.0 2020 年 4 月 1 日

#335 in 模板引擎

MIT 许可证

105KB
1K SLoC

批量示例生成器

批量示例生成器是一个使用 Rust 创建的工具,用于基于 pest 语法(PEG)创建数十/数百/数千/数百万个随机示例。它可以用于生成 AI 模型训练的字符串/结构化数据,或像语法模糊器一样查找错误。

目录

摘要

bulk_examples_generator 有两种形式:二进制或 crate(库)。

如果您只想生成示例,请参阅 二进制使用

如果您想为自己的需求使用 crate,请参阅 Crate 使用

二进制使用

好吧!您只是想生成一些示例或测试此应用程序。

需求

  • 您必须安装 Rust(最低版本:1.36.0)

就这样了吗?

是的。

为什么没有可执行文件可用?

一旦这个库变得稳定,我将为 Windows 和 Linux 创建可执行文件。

入门

  1. 安装应用程序或克隆仓库
cargo install bulk_examples_generator

如果您克隆了仓库而不是使用 bulk-examples-generator,则必须使用 cargo run -- <comands>

  1. 创建一个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 ~ "."}
  1. 执行以下命令
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个参数

  1. PEST表示法中的语法
  2. 一个数字(生成示例的数量)
  3. 起始规则(生成开始的地方)
  4. 输出类型(保存或打印示例的位置)
bulk_examples_generator -g life.pest -q 3000 -s love -o stdout

语法

要使用批量示例生成器,您需要知道如何用PEST表示法编写语法。

查看编写语法的快速指南或查看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-filec 来加载生成中的文件。

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} [在 mn (包含)之间] ✔️
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