#parser-combinator #operator-overloading #peg #parser

pom-preview

使用操作符重载而不使用宏实现的 PEG 解析器组合器

1 个版本 (0 个不稳定版本)

3.0.4-alpha-12020年1月6日

#95解析工具

MIT 许可证

170KB
891

pom

Crates.io Build Status Docs Discord

使用操作符重载而不使用宏创建的 PEG 解析器组合器。

文档

什么是 PEG?

PEG 代表解析表达式语法,是一种分析形式语法,即它用一套识别语言中字符串的规则来描述一种形式语言。与 CFG 不同,PEG 不能是歧义的;如果一个字符串可以解析,那么它只有一个有效的解析树。每个解析函数在概念上都将输入字符串作为其参数,并返回以下结果之一

  • 成功,其中函数可以可选地向前移动或消费它所提供的输入字符串中的一个或多个字符,或者
  • 失败,在这种情况下不消费任何输入。

更多内容请阅读 维基百科

什么是解析器组合器?

解析器组合器是一种高阶函数,它接受多个解析器作为输入并返回一个新的解析器作为其输出。解析器组合器允许递归下降解析策略,这有助于模块化分片构建和测试。

使用组合器构建的解析器易于构建、可读、模块化、结构良好且易于维护。使用操作符重载,解析器组合器可以采用中缀操作符的形式,用于将不同的解析器粘合在一起形成一个完整的规则。因此,解析器组合器使得解析器可以以嵌入式风格定义,在代码结构上与形式语法的规则相似。而且,与宏相比,代码更容易调试。

主要优势是不需要经过任何代码生成步骤,你总是使用底层标准语言。除了构建问题(以及围绕错误消息和可调试性的常见问题,公平地说,与代码生成一样糟糕)之外,通常更容易自由混合语法表达式和普通代码。

预定义解析器和组合器的列表

基本解析器 描述
empty() 总是成功,不消费任何输入。
end() 匹配输入的结尾。
sym(t) 匹配单个终端符号 t
seq(s) 匹配符号序列。
list(p,s) 匹配由 s 分隔的 p 列表。
one_of(set) 当前输入符号属于集合时成功。
none_of(set) 当前输入符号不属于集合时成功。
is_a(predicate) 当谓词对当前输入符号返回true时成功。
not_a(predicate) 当谓词对当前输入符号返回false时成功。
take(n) 读取n个符号。
skip(n) 跳过n个符号。
call(pf) 调用解析器工厂,可用于创建递归解析器。
解析器组合器 描述
p | q 匹配p或q,返回第一个成功的匹配结果。
p + q 匹配p和q,如果两者都成功,则返回结果的元组。
p - q 匹配p和q,如果两者都成功,则返回p的结果。
p * q 匹配p和q,如果两者都成功,则返回q的结果。
p >> q 解析p并获得结果P,然后解析q并返回q(P)的结果。
-p 当p成功时成功,不消耗输入。
!p 当p失败时成功,不消耗输入。
p.opt() 使解析器成为可选的。返回一个Option
p.repeat(m..n) p.repeat(0..)重复p零次或多次
p.repeat(1..)重复p一次或多次
p.repeat(1..4)匹配p至少1次且最多3次
p.repeat(5)重复p正好5次
p.map(f) 将解析器结果转换为所需的值。
p.convert(f) 将解析器结果转换为所需的值,如果转换出错则失败。
p.pos() 在匹配p后获取输入位置。
p.collect() 收集所有匹配的输入符号。
p.discard() 丢弃解析器输出。
p.name(_) 为解析器指定名称以识别解析错误。
p.expect(_) 标记解析器为预期的,在顺序选择失败时提前终止。

运算符的选择由它们的运算符优先级、算术和“意义”确定。使用*忽略表达式的第一个操作数的结果,+-可以满足表达式的其他需求。

例如,A * B * C - D + E - F将返回C和E的结果作为一对。

示例代码

extern crate pom;
use pom::parser::*;

let input = b"abcde";
let parser = sym(b'a') * none_of(b"AB") - sym(b'c') + seq(b"de");
let output = parser.parse(input);
assert_eq!(output, Ok( (b'b', vec![b'd', b'e']) ) );

示例JSON解析器

extern crate pom;
use pom::parser::*;
use pom::Parser;

use std::collections::HashMap;
use std::str::{self, FromStr};

#[derive(Debug, PartialEq)]
pub enum JsonValue {
	Null,
	Bool(bool),
	Str(String),
	Num(f64),
	Array(Vec<JsonValue>),
	Object(HashMap<String,JsonValue>)
}

fn space() -> Parser<u8, ()> {
	one_of(b" \t\r\n").repeat(0..).discard()
}

fn number() -> Parser<u8, f64> {
	let integer = one_of(b"123456789") - one_of(b"0123456789").repeat(0..) | sym(b'0');
	let frac = sym(b'.') + one_of(b"0123456789").repeat(1..);
	let exp = one_of(b"eE") + one_of(b"+-").opt() + one_of(b"0123456789").repeat(1..);
	let number = sym(b'-').opt() + integer + frac.opt() + exp.opt();
	number.collect().convert(str::from_utf8).convert(|s|f64::from_str(&s))
}

fn string() -> Parser<u8, String> {
	let special_char = sym(b'\\') | sym(b'/') | sym(b'"')
		| sym(b'b').map(|_|b'\x08') | sym(b'f').map(|_|b'\x0C')
		| sym(b'n').map(|_|b'\n') | sym(b'r').map(|_|b'\r') | sym(b't').map(|_|b'\t');
	let escape_sequence = sym(b'\\') * special_char;
	let string = sym(b'"') * (none_of(b"\\\"") | escape_sequence).repeat(0..) - sym(b'"');
	string.convert(String::from_utf8)
}

fn array() -> Parser<u8, Vec<JsonValue>> {
	let elems = list(call(value), sym(b',') * space());
	sym(b'[') * space() * elems - sym(b']')
}

fn object() -> Parser<u8, HashMap<String, JsonValue>> {
	let member = string() - space() - sym(b':') - space() + call(value);
	let members = list(member, sym(b',') * space());
	let obj = sym(b'{') * space() * members - sym(b'}');
	obj.map(|members|members.into_iter().collect::<HashMap<_,_>>())
}

fn value() -> Parser<u8, JsonValue> {
	( seq(b"null").map(|_|JsonValue::Null)
	| seq(b"true").map(|_|JsonValue::Bool(true))
	| seq(b"false").map(|_|JsonValue::Bool(false))
	| number().map(|num|JsonValue::Num(num))
	| string().map(|text|JsonValue::Str(text))
	| array().map(|arr|JsonValue::Array(arr))
	| object().map(|obj|JsonValue::Object(obj))
	) - space()
}

pub fn json() -> Parser<u8, JsonValue> {
	space() * value() - end()
}

fn main() {
	let input = br#"
	{
        "Image": {
            "Width":  800,
            "Height": 600,
            "Title":  "View from 15th Floor",
            "Thumbnail": {
                "Url":    "http://www.example.com/image/481989943",
                "Height": 125,
                "Width":  100
            },
            "Animated" : false,
            "IDs": [116, 943, 234, 38793]
        }
    }"#;

	println!("{:?}", json().parse(input));
}

您可以使用以下命令运行此示例

cargo run --example json

基准测试

解析器 解析相同JSON文件的时间
pom: json_byte 621,319 ns/iter (+/- 20,318)
pom: json_char 627,110 ns/iter (+/- 11,463)
pest: json_char 13,359 ns/iter (+/- 811)

无运行时依赖