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 次
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
对于任何字符 a
和 z
:如果下一个标记是一个字符长,并且该字符位于 a
和 z
之间(包括端点),则匹配。
{.abc.}
对于任何有 x 个字符的字符串 abc
:如果下一个 x 个标记正好匹配 abc
中的每个字符,则匹配。
tag
对于任何字符串 tag
:如果下一个标记具有标签 tag
,则匹配。
rule1 & rule2
:如果 rule1
从标记 x 应用到标记 y,然后 rule2
从标记 y + 1 应用到标记 z。可以链接使用。
rule1 | rule2
:如果 rule1
或 rule2
应用,则匹配。可以链接使用。
rule?
:如果 rule
匹配下一个 x 个标记,则此规则匹配 x 个标记。否则,匹配 0 个标记。不建议单独使用(可能直到实现警告才会解除解析器的锁定)。
rule*
:重复匹配 rule
。如果 rule
匹配标记 x 到 y,则再次在标记 y 到 z 上尝试匹配,依此类推。如果 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
标签(你可能已经注意到它被添加到了每个数字上)的标记,所有三个标记将被转换成一个带有 parens
和 expr
标签的标记。
{
'(' & 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
函数,该函数需要您的元解析器的文本和要解析的文本。结果将是一个标记向量。