4 个版本

0.2.0 2023年11月27日
0.1.3 2023年11月21日
0.1.2 2023年11月14日
0.1.1 2023年8月30日

解析器工具 中排名 128

每月下载量 33

GPL-3.0 许可协议

67KB
2K SLoC

tuck5:新的尝试

(制作解析器生成器)

Tuck5 是一个以 Rust 编写的词法/解析器生成库,重点关注性能和易用性。`meta` 系统是主要和推荐的与 tuck5 交互的方法。它提供了一种领域特定语言,允许开发人员轻松定义如何转换一组标记。在底层,这种简单的语法以快速循环的方式执行,具有前所未有的开发者对执行的控制水平。

理解 TUCK 的本质

tuck5 使用与词法分析框架相符的标记系统。标记可以是分支或叶子。如果一个标记是叶子,它包含对其语料库文本中索引的范围。如果是分支,它包含一个标记的向量作为其子代。标记还包含一些额外数据:作为开发者,您能够静态地控制这些数据以满足您的需求。(注意:如果您正在使用 `meta` 系统,此辅助数据以所有者字符串向量的形式出现,这些字符串作为标签。计划进行字符串指针或基于位标志的系统过渡。这不会影响 `meta` 系统的使用体验。)

解析语料库字符串时,字符串的字符最初被转换为一系列叶子标记,其中每个标记有两个标签:`a`,其中 `a` 是标记的内容,`u97`,其中 `u97` 是字符的 UTF-8 值的十进制转换,以及 `ws` 仅当字符被分类为 Unicode 空白字符时。

然后,应用一系列规则到标记上。一个规则由一个可能匹配一系列标记的条件、一个转换方法(是否将匹配序列转换为单个叶子标记、单个分支标记或完全删除),以及应用到将被转换的匹配标记上的标签系列。有关此内容的示例,请参阅 [[#使用 `meta` 系统]] 部分。

每条规则都逐个应用于每个标记。许多规则接受多个标记。它们对达到语料库字符串末尾有特别定义的行为。每条规则都会重复应用于语料库字符串,直到不能再应用为止。如果指定,可以按顺序、重复地应用一组规则,直到它们都无法应用。

使用 meta 系统

使用此语法定义一个元解析器(由 meta 系统生成的解析器)。

规则

'abc' 对于任何字符串 abc:如果下一个标记的内容正好等于引号之间的字符,则匹配。

a..z 对于任何字符 az:如果下一个标记是一个字符长,并且该字符位于 az 之间(包括端点),则匹配。

{.abc.} 对于任何有 x 个字符的字符串 abc:如果下一个 x 个标记正好匹配 abc 中的每个字符,则匹配。

tag 对于任何字符串 tag:如果下一个标记具有标签 tag,则匹配。

rule1 & rule2:如果 rule1 从标记 x 应用到标记 y,然后 rule2 从标记 y + 1 应用到标记 z。可以链接使用。

rule1 | rule2:如果 rule1rule2 应用,则匹配。可以链接使用。

rule?:如果 rule 匹配下一个 x 个标记,则此规则匹配 x 个标记。否则,匹配 0 个标记。不建议单独使用(可能直到实现警告才会解除解析器的锁定)。

rule*:重复匹配 rule。如果 rule 匹配标记 xy,则再次在标记 yz 上尝试匹配,依此类推。如果 rule 不匹配,则匹配 rule 匹配过的所有标记。再次强调,不建议单独使用,因为即使 rule 匹配 0 次,它也会匹配。

rule+:匹配一次 rule 后重复匹配 rule:见上文。相当于 rule & rule*。可以单独使用。

转换

rule . tag0, tag1,;:如果 rule 匹配下一个 x 个标记,将这些标记转换为一个具有指定标签的叶标记(丢弃“子”数据)。比下面的替代方案更高效。

rule : tag0, tag1,;:如果 rule 匹配下一个 x 个标记,将这些标记转换为一个具有指定标签的分支标记(具有转换后的标记作为子标记)。对于转换为 AST 非常有用。

rule~;:如果 rule 匹配下一个 x 个标记,则移除这些标记。通常用于移除空白字符,但如果目标是生成抽象语法树(AST),也可以用于注释。

杂项

# bla bla bla (line break):一行注释。

## bla bla bla ##:多行注释。

{ transformation transformation ... }:依次应用这些转换,直到没有匹配项。对于保持操作顺序非常有用。

(rule):与规则相同。在元系统的操作顺序或清晰度方面非常有用。

示例

tests 中的 calculator 文件夹中查看元系统的完整示例。以下是它的简化版本的解释。

(0..9)+. positive, int, expr;

在这里,我们定义了正整数。 0..9 表示字符范围,将匹配从 0 到 9 的数字。这里的 + 表示匹配一个或多个数字。

int & '.' & int. positive, decimal, expr;

在这里,int 匹配我们最近使用 int 标签定义的任何标记。如果找到一个整数序列,后跟一个点号,然后跟一个整数(根据我们的定义,可以有一个前导零),这些将被转换成一个十进制标记。

'-' & positive. negative, expr;

现在我们定义负数。如果匹配到一个负号,然后是一个正数,我们应用 negative 标签。

ws~;

在这里,我们移除由预定义的 ws 标签指定的空白字符。

{
    '(' & expr & ')': parens, expr;
}

这个语句周围的括号表示还有更多要跟随它的语句,并且它们应该按顺序执行。当我们添加操作时,我们将看到这一点。现在,如果括号包围了一个带有 expr 标签(你可能已经注意到它被添加到了每个数字上)的标记,所有三个标记将被转换成一个带有 parensexpr 标签的标记。

{
    '(' & expr & ')': parens, expr;
    expr & '*' | '/' & expr: oper, expr;
}

在这里,我们向我们的重复转换添加乘法和除法。通过括号,你会发现任何嵌套括号、乘法和除法的序列都将遵循 PEMDAS 规则。

{
    '(' & expr & ')': parens, expr;
    expr & '*' | '/' & expr: oper, expr;
    expr & '+' | '-' & expr: oper, expr;
}

最后,通过添加加法和减法,我们完成了我们的简单计算器。要访问 Rust 中的标记,请使用 tuck5::meta::eval_prog_from_text 函数,该函数需要您的元解析器的文本和要解析的文本。结果将是一个标记向量。

无运行时依赖项