#parser-generator #parser #nom #context-free-grammar #macro

tree-builder

使用Nom和过程宏生成递归下降解析器的解析器生成库

2个版本

0.0.3 2023年7月25日
0.0.2 2023年7月25日
0.0.1 2023年7月14日

146 in 解析器工具

GPL-3.0 许可证

32KB
198

TreeBuilder,一个轻量级的基于nom的Rust解析器生成库

TreeBuilder是一个专注于从ASCII输入字符串正确解析上下文无关文法的解析器生成器。它尽可能地生成递归下降、回溯解析器,并具有方便的特性。它生成的解析器旨在非常容易使用,如果用户希望同时使用Nom和TreeBuilder,则与Nom解析器兼容。本版本TreeBuilder将不支持左递归文法。

TreeBuilder的[解析器]特质显示了所有TreeBuilder生成的解析器的类型签名。

示例

use tree_builder::{build_tree, Parser};

build_tree! {
    // Parses a Hex color
    HexColor #=> "#",
                 HexDigit,
                 HexDigit?, HexDigit?, HexDigit?, HexDigit?, HexDigit?;

    // Parses a Hex digit
    HexDigit #=> [0-9a-fA-F];
}

fn main() {
    // Will pass
    HexColor::parse("#abd325").unwrap();
    HexColor::parse("#asd000").unwrap();
    // Will fail
    HexColor::parse("#whatever").unwrap();
}

规则的种类

TreeBuilder可以生成输出数据结构仅为String的解析器,或者输出数据结构为更复杂的元组的解析器。如果您使用箭头 #=> 将规则名称与其定义分开,您已创建了一个输出String的词法规则,但如果您使用 =>,则可以选择保留规则定义的哪些部分。

您可以使用 @ 运算符(称为Include)指定您希望保留的部分。解析器的数据结构基于您所做的包含,从左到右。

以下是一个解析JSON数组的规则Arraya和ArrayElems的例子

build_tree!{
    JValue => /* Definition of a JSON value */;

    Array => "[", #s*, @ArrayElems?, #s*, "]";
    ArrayElems => @JValue, #s*, @(",", #s*, @JValue, #s*)*;
}

上述规则生成的数据结构是

struct Array(Optional<Box<ArrayElems>>);

struct ArrayElems(Box<JValue>, Vec<Box<JValue>>);

当您使用选择时,生成的是枚举,其变体名称要么在您仅包含一个非终端的选择中隐式推断,要么需要指定。

选择规则示例

build_tree!{
// Rule representing a JSON value
JValue => @JString
       |  @Number
       |  @Object
       |  @Array
       |  "true" <True>
       |  "false" <False>
       |  "null" <Null>;
}

此规则生成的数据结构

enum JValue {
    JString(Box<JString>),
    Number(Box<Number>),
    Object(Box<Object>),
    Array(Box<Array>),
    True(),
    False(),
    Null(),
}

请注意,在用 => 标记的规则中,目前不能在分组内使用选择。此限制可能在下一个更新中修复。

TreeBuilder的语法

TreeBuilder允许指定使用终结符、非终结符、元字符和分组/子解析器的文法。可以对上述元素应用不同的运算符,这些运算符定义了其中任何一个可以重复多少次。

可用的终结符

  1. 终结符:终结符的写法与任何C-like语言中的字符串相同。它们定义为字符串字符序列,可以是除了’\’和’”’之外的任何字符,或者是一个字符转义序列,如’\n’,由双引号分隔。可能的转义序列包括

    • \n (换行符)
    • \r (回车)
    • \t (制表符)
    • 非破坏性退格(\b)
    • 换页(\f)
    • 反斜杠(\)
    • 双引号(\”)

    终端的一些例子

    • “for”
    • “if”
    • “a123\nb!?.\tc\””
  2. 元字符:元字符是一种在语法规范中具有特定含义或功能的特殊字符。与终端不同,终端解析它们的字符串等效,元字符具有特殊解释,并作为构建强大而优雅的解析规则的基本构建块,用尽可能少的代码。以下是元字符及其含义

    • 点(.):匹配任何单个字符,除了换行符。它作为一个通配符,表示输入字符串中的任何字符。
    • 字符类([ ]):匹配方括号内指定的任何单个字符。例如,[aeiou]匹配任何元音字符。
    • 否定字符类([^ ]):匹配方括号内未指定的任何单个字符。例如,[^0-9]匹配任何非数字字符。
    • 数字(#d):匹配任何数字字符。它与字符类[0-9]等价。例如,#d将匹配从0到9的任何数字。
    • 非数字(#D):匹配任何非数字字符。它与字符类[^0-9]等价。例如,#D将匹配任何不是数字的字符。
    • 单词字符(#w):匹配任何单词字符。它包括字母数字字符(a-z、A-Z和0-9)以及下划线()字符。它与字符类[a-zA-Z0-9]等价。
    • 非单词字符(#W):匹配任何非单词字符。它排除了字母数字字符(a-z、A-Z和0-9)以及下划线()字符,匹配任何其他字符。它与字符类[^a-zA-Z0-9]等价。
    • 空白字符(#s):匹配任何ASCII空白字符,因此匹配\f、\n、\r、\t中的任何一个。
  3. 非终结符:它们的语法与ASCII Rust标识符完全相同。以字母字符或下划线开头,后跟字母数字字符、下划线或两者的字符串。它们在定义语法中的使用是为了指定另一个规则是当前指定规则的组成部分。因此,从使用非终结符的规则生成的解析器将在用户指定的点应用与该非终结符相关的解析器。

  4. 分组:分组指的是将一系列元素或子表达式括在括号内。它允许建立优先级并控制语法规范中元素的评估顺序。这些作为语言中更小的元素之一分组,因为其他操作符可以对分组应用,就像对其他术语应用一样。

可用操作符

操作符 描述
e | e 选择
@e 包含
e? 可选
e* 零次或多次
e+ 一次或多次

在TreeBuilder中的选择是顺序选择,这意味着对于规则可能定义的所有备选方案,只有一个备选方案可以成功。这是由于TreeBuilder将按顺序尝试定义的第一个到最后一个备选方案,如果任何备选方案成功地解析了输入字符串,那么其后的选择将被完全忽略。

依赖

~3.5–5MB
~89K SLoC