#语法 #PEG #解析器 #解析器生成器

bin+lib wee-peg

简单的解析表达式语法(PEG)解析器生成器

1 个不稳定版本

使用旧的 Rust 2015

0.5.4 2017年12月8日

#149解析工具


用于 2 crates

MIT 许可证

155KB
1K SLoC

Rust 中的解析表达式语法

Build Status

这是一个基于 解析表达式语法 的简单解析器生成器。

请参阅 发布说明 了解更新。

此分支

这是一个实验性分支,旨在为 sindra 和相关编程语言(例如 piske)提供一些额外的功能。这些功能可能不如上游功能经过充分测试。

语法定义语法

use super::name;

语法可以开始于一组 use 声明,就像在 Rust 中一样,这些声明包含在生成的模块中。由于语法在其自己的模块中,你必须 use super::StructName; 以访问父模块中的结构。

语法的其余部分定义了一系列语法规则,这些规则匹配您的语言的组件

pub rule_name -> return_type
   = expression

如果规则被标记为 pub,生成的模块将有一个公共函数,该函数从该规则开始解析。

表达式

  • "literal" - 匹配字面字符串
  • "literal"i - 忽略大小写匹配字面字符串
  • [a-zA-Z] - 匹配集合中的一个字符
  • [^a-zA-Z] - 匹配不在集合中的单个字符
  • . - 匹配任何单个字符
  • some_rule - 匹配在语法中其他地方定义的规则并返回其结果
  • some_template<arg1, arg2> - 使用参数展开 模板规则
  • e1 e2 e3 - 按顺序匹配表达式
  • e1 / e2 / e3 - 尝试匹配 e1。如果匹配成功,返回其结果,否则尝试 e2,依此类推。
  • expression? - 匹配 expression 的零次或一次重复。返回一个 Option
  • expression* - 匹配 expression 的零次或多次重复,并将结果作为 Vec 返回
  • expression+ - 匹配 expression 的一次或多次重复,并将结果作为 Vec 返回
  • expression*<n> - 匹配 n 次重复的 expression,并将结果作为 Vec 返回
  • expression*<n,m> - 匹配 nm 次重复的 expression,并将结果作为 Vec 返回。
  • expression*<{foo}> - 评估 Rust 表达式 foo,返回 usize,并匹配这么多次重复的 expression
  • expression ** delim - 匹配零次或多次重复的 expression,并用 delim 分隔,并将结果作为 Vec 返回
  • expression **<n,m> delim - 匹配 nm 次重复的 expression,并用 delim 分隔,并将结果作为 Vec 返回
  • expression ++ delim - 匹配一次或多次重复的 expression,并用 delim 分隔,并将结果作为 Vec 返回
  • &expression - 只有当 expression 在当前位置匹配时才匹配,不消耗任何字符
  • !expression - 只有当 expression 在当前位置不匹配时才匹配,不消耗任何字符
  • a:e1 b:e2 c:e3 { rust } - 依次匹配 e1、e2、e3。如果成功匹配,则运行块中的 Rust 代码并返回其返回值。前导冒号前的变量名绑定到相应表达式的结果。Rust 代码必须包含匹配的花括号,包括字符串和注释中的花括号。
  • a:e1 b:e2 c:e3 {? rust } - 与上面类似,但是 Rust 块返回一个 Result 而不是直接返回值。在 Ok(v) 上,它成功匹配并返回 v。在 Err(e) 上,整个表达式的匹配失败,它尝试其他选项或使用 &str e 报告解析错误。
  • $(e) - 匹配表达式 e,并返回与匹配对应的输入字符串的 &str 切片。
  • #position - 返回表示输入字符串中当前偏移量的 usize,并且不消耗任何字符。
  • #quiet<expression> - 匹配表达式,但在错误信息中不报告其中的字面量为 "expected"。
  • #expected("str") - 匹配失败,并在当前位置报告指定的字符串为期望的符号。
  • #infix<atom> { ... } - 通过优先级提升解析中缀表达式。 详细信息见下文

注释

您可以使用行注释和块注释,就像在 Rust 代码中一样,例如

// comment
name -> String
  = /* weirdly placed comment */ n:$([0-9]+) { from_str::<u64>(n).unwrap() } // comment

错误报告

当匹配失败时,位置信息会自动记录以报告一组 "expected" 标记,这将允许解析器进一步前进。

某些规则不应出现在错误信息中,并且可以使用 #quiet<e> 进行抑制。

whitespace = #quiet<[ \n\t]+>.

如果您希望 "expected" 集合包含更有用的字符串而不是字符集,则可以使用 #quiet#expected 一起使用。

identifier = #quiet<[a-zA-Z][a-zA-Z0-9_]+> / #expected("identifier")

中缀表达式

#infix<atom> { rules... } 提供了一种方便的方法来使用 优先级提升 算法解析二元中缀运算符。

pub arithmetic -> i64 = #infix<number> {
	#L x "+" y { x + y }
	   x "-" y { x - y }
	#L x "*" y { x * y }
	   x "/" y { x / y }
	#R x "^" y { x.pow(y as u32) }
}

原子(在这个例子中,number),是用于解析 "运算符之间的东西" 的规则。它必须是在语法中定义的规则的名称,而不是表达式,因为其返回类型用于生成的代码。每个运算符的操作代码和整个 #infix 表达式返回与该规则相同的类型。在构建 AST 时,此类型通常是一个枚举,具有一个用于原子的变体,以及用于运算符的变体。

每个 #L#R 都引入了一个新的优先级级别,这个级别比之前的优先级级别绑定得更紧密,并包含左(#L)或右(#R)结合运算符。每个运算符规则由一个左操作数变量名、一个运算符表达式、一个右变量名和一个 Rust 动作表达式组成。Rust 代码可以访问这两个命名操作数。

你可以将上面的例子视为一个 PEG 解析器 number (("+" / "-" / "*" / "/" / "^") number)*,然后通过优先级提升将原子和运算符按照结合性和优先级规则组合成树状结构,并为树的每一层运行动作代码。不分配任何中间向量。

模板规则

规则模板可以减少语法中的重复代码

示例

keyword<E> = E !identifierChar whitespace*

STRUCT = keyword<"struct">
ENUM = keyword<"enum">

模板在其被使用的地方内联,不能被标记为 pub

上下文参数

你可以在你的语法的顶部添加一个声明,例如

#![arguments(filename: &str, interner: &mut Interner)]

通过传递参数在整个解析器中

有关示例,请参阅 测试

这些参数将被传递给每个内部解析函数,因此它们必须是 Copy 或是一个 &&mut 引用。小心可变参数。请记住,规则动作可以在后来失败的解析路径上运行,并且不会对最终解析做出贡献。

用法

使用构建脚本

Cargo 构建脚本可以自动将 PEG 语法编译成 Rust 源代码。此方法适用于稳定的 Rust。

使用构建脚本使用 rust-peg 的示例 crate

将以下内容添加到你的 Cargo.toml

[package] # this section should already exist
build = "build.rs"

[build-dependencies]
peg = { version = "0.5" }

使用以下内容创建 build.rs

extern crate peg;

fn main() {
    peg::cargo_build("src/my_grammar.rustpeg");
}

(如果你已经有了 build.rs,只需添加 extern crate peg;peg::cargo_build(...) 调用即可。)

并导入生成的代码

mod my_grammar {
    include!(concat!(env!("OUT_DIR"), "/my_grammar.rs"));
}

然后你可以将语法中的 pub 规则用作模块中的函数。它们接受一个 &str 以进行解析,并返回一个包含解析结果或解析错误的 Result

match my_grammar::my_rule("2 + 2") {
  Ok(r) => println!("Parsed as: {:?}", r),
  Err(e) => println!("Parse error: {}", e),
}

作为语法扩展

rust-syntax-ext 只在 Rust 的 Nightly 构建上工作。

使用 rust-peg 作为语法扩展的示例

将以下内容添加到你的 Cargo.toml

[dependencies]
peg-syntax-ext = "0.5.0"

将以下内容添加到你的 crate 根目录

#![feature(plugin)]
#![plugin(peg_syntax_ext)]

使用 peg_file! modname("mygrammarfile.rustpeg"); 来包含外部文件的语法。该宏扩展为一个名为 modname 的模块,其中包含与您语法中 pub 规则对应的函数。

或者,使用

peg! modname(r#"
  // grammar rules here
"#);`

在您的 Rust 源文件中内嵌一个简短的 PEG 语法。 示例

作为独立的代码生成器

运行 rust-peg input_file.rustpeg 编译语法并在标准输出生成 Rust 代码。

跟踪

如果您在构建项目时将 peg/trace 功能传递给 Cargo,则在运行二进制文件时,将输出解析跟踪到标准输出。例如,

$ cargo run --features peg/trace
...
[PEG_TRACE] Matched rule type at 8:5
[PEG_TRACE] Attempting to match rule ident at 8:12
[PEG_TRACE] Attempting to match rule letter at 8:12
[PEG_TRACE] Failed to match rule letter at 8:12
...

编辑器高亮插件

用户为 .rustpeg 语法创建了文本编辑器语法高亮插件

依赖项

~0.2–7.5MB
~45K SLoC