使用pest创建rust的语法剖析器
背景最近有机会接触了pest,一个优雅的通过使用Parsing Expression Grammar or PEGs 来生成语法剖析器,正好借助博客园这个平台,来分享一下本身的学习心得,也希望可以借助这个机会,和同行们互相探讨,互相提高。
什么是 Parsing Expression Grammar?
Parsing Expression Grammar(PEG)是一种分析性形式文法,它是用 Pest 界说 Rust 剖析“规则”的方法之一。 Pest 接受具有此类规则界说的文件的输入,并生成遵循它们的 Rust 剖析器。
在编写规则时,我们应该考虑 Pest 和 PEG 的三个界说特征。
第一个特征是贪心匹配。 Pest 将始终尝试将输入的最大值与规则相匹配。 例如,假设我们编写了如下规则:
match one or more alphabets在这种情况下,Pest 将消耗输入中的所有内容,直到达到数字、空格或符号。 在此之前它不会停止。
第二个特征是交替匹配是有序的。 为了理解这意味着什么,假设我们给出了多个匹配来满足一条规则,如下所示:
rule1 | rule2 | rule3Pest 将首先尝试匹配规则 1。 当且仅当规则 1 失败时,Pest 才会尝试匹配规则 2,依此类推。 如果第一条规则匹配,Pest 将不会尝试匹配任何其他规则来找到最佳匹配。
因此,在编写此类替代方案时,我们必须将最具体的替代方案放在前面,将最一样平常的替代方案放在最后。
第三个特征是无回溯,这意味着如果规则无法匹配,剖析器将不会回溯。 相反,Pest 会尝试寻找更好的规则或选择最佳的替代匹配。
这与使用平凡正则表达式或其他范例的剖析器不同,即使没有给出替代选择,它们也可以返回一些标记并尝试找到替代规则。
在 Pest 中使用 PEG 声明 Rust 剖析器的规则
在 Pest 中,我们使用雷同于正则表达式的语法来界说规则,但也有一些差别。 让我们通过写一些例子来看看实际的语法。
任何规则的基本语法如下:
RULE_NAME = { 规则界说 }
大括号前面可以有 _ 和 @ 等符号。 稍后会解释这些符号的寄义。
使用 Pest 中的内置规则匹配单个字符
引号中的任何字符或字符串都与其自身匹配。 例如,“a”将匹配 a 字符,“true”将匹配字符串 true,依此类推。
ANY 是匹配任何 Unicode 字符的规则,包括空格、换行符以及逗号和分号等符号。
ASCII_DIGIT 将匹配数字或数字字符 - 换句话说,以下字符中的任何字符:
0123456789ASCII_ALPHA 将匹配小写和大写字母字符 - 换句话说,以下字符中的任何字符:
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ当然,你可以使用 ASCII_ALPHA_LOWER 和 ASCII_ALPHA_UPPER 来专门匹配各自巨细写的大写和小写字符。
最后,NEWLINE 将匹配表示换行符的控制字符,例如 \n 、 \n\r 或 \r。
Pest 文档中还解释了其他几个内置规则, 你可以直接去到它的官方文档查询
请注意,上述所有规则仅匹配其范例的单个字符,而不匹配多个字符。 例如,对于输入 abcde,按照如下规则:
my_rule = { ASCII_ALPHA }该规则将仅匹配 a 字符,输入的其余部分 - bcde - 将被通报下去以进行进一步剖析。
使用重复匹配多个字符
刚刚我们介绍了怎样匹配单个字符,下面我们再看看怎样匹配多个字符。 在pest里有三种重要范例,我们逐一介绍。
加号 + 表示“一个或多个”。 在这里,Rust 剖析器将尝试匹配至少一次出现的规则。 如果出现多次,Pest 将匹配所有这些。 如果找不到任何匹配项,则会被视为错误。 例如,我们可以这样界说一个数字:
NUMBER = { ASCII_DIGIT+ }如果存在非数字字符(例如字母字符或符号),此规则将匹配失败。
星号 * 表示“零个或多个”。 当我们想要允很多次出现,但即使没有错误也不给出任何错误时,这很有用。 例如,当我们界说下面的列表时,第一个值后面可以有零个或多个逗号值对。
LIST = { "[" ~ NUMBER ~ ("," ~ NUMBER)* "]" }上面的规则规定列表以 [ 开头,然后是一个 NUMBER ,之后可以有零对或多对 , 数字组。 它们被分组在括号中,并在整个括号组上加一个星号 *。 最后,列表以右括号 ] 结尾。
此规则将匹配 、 、 等。 然而,它会在 1 上失败,因为不存在左括号 [ ,以及 。
最后打个问号? 匹配零个或一个,但不超过一个。 例如,要允许在上面的列表中使用尾随逗号,我们可以界说如下规则
LIST = { "[" ~ NUMBER ~ ("," ~ NUMBER)* ","? "]" }此时,这个规则将允许 和 。
除了这三个选项之外,另有其他指定重复的方法,包括允许重复固定次数、或最多 n 次、或 n 到 m 次之间的方法。 这些具体信息可以在 Pest 文档中找到,就不具体展开讲了。
隐式匹配
我们在之前规则界说中看到的 符号 ~ 是用于表示序列。 例如,A ~ B ~ C 翻译为“匹配规则 A,然后匹配 B,然后匹配 C”。
使用显式 ~ 来表示此序列是有效的,因为 ~ 符号周围存在一些隐式的空白匹配。 这是因为在很多情况下,编码规则和语法会忽略空格。 例如,if ( true) 与 if( true ) 和 if (true ) 相同。
因此,Pest 生成的剖析器将自动为我们执行此操纵,而不需要pest语法开辟人员在每个地方手动查抄这一点。 这种隐式匹配在pest语法计划过程中非常的有用。
有序选择
有些时候,你在开辟pest语法的时候,可能想要表达允许与界说的规则匹配的多个规则。 例如,在 JSON 中,值可以是字符串、数组或对象,等等。
对于这种“OR”场景,我们可以使用竖线 | 来表达替代选择的想法。 象征。 上面的 JSON 概念可以写成这样的规则:
VALUE = { STRING | ARRAY | OBJECT }这里请注意,如上所述,在之前我们谈到 PEG 特征时,讲到有序选择会在匹配到第一条规则时,就会结束匹配,不会继续有序选择包含的后续规则。 这里,我们拿个例子来具体解说下:
RULE = { "cat" | "cataract" | "catastrophe" }该规则永世不会匹配 cataract 和 catastrophe,因为剖析器会将两者的起始 cat- 部分与第一条规则 cat 相匹配,然后它不会尝试匹配任何其他字母。 匹配 cat 后,剖析器会将剩余的输入(-aract 和 -astrophe)通报到下一步,在下一步中它可能不会匹配任何内容并导致剖析失败。
所以在使用有序选择时,请记住始终在开头指定最具体的规则,在结尾指定最通用的规则。 因此,上面的精确表达方式如下:
RULE = { "catastrophe" | "cataract" | "cat" }在这里,cataract 和 catastrophe 的顺序并不重要,因为它们不是彼此的子串,并且匹配一个的输入将不会匹配另一个。 我们再看一个示例案例:
all_queues = { "queue" | "que" | "q" }匹配"queue"及其变体 que 和 q 时使用的顺序在这里很重要,因为后面的字符串是第一个字符串的子字符串。 剖析器将首先尝试将输入匹配到队列; 仅当失败时,剖析器才会尝试将其与 que 匹配。 如果也失败,剖析器最终将尝试匹配 q。
静默和原子规则
下面我们谈谈在pest语法中经常看到的" _" 和 "@" 符号的寄义。
在 Pest 生成的剖析器中,每个匹配的规则都会生成一个 Pair,此中包含匹配的输入及其匹配的规则。 然而,有时,我们可能想要匹配一些规则以确保遵循语法,但又想忽略这些规则的内容,因为它们并不重要。
在这些情况下,我们可以通过在该规则界说中的左大括号 { 之前放置下划线 _ 来将该规则表示为静默,这样就不会产生pair或者token,也就不会出现在我们的剖析结果里。
这个规则最常见的用例是忽略代码中的解释。 尽管我们希望解释遵循语法约定(例如,它们必须以 // 开头并以换行符结尾),但我们不希望它们在处理过程中出现。 因此,为了忽略解释,我们可以这样界说它们:
comments = _{ "//" ~ (!NEWLINE ~ ANY)* ~ NEWLINE }这将尝试匹配任何解释。 如果存在任何语法错误——例如,如果解释仅以一个/开头——则会产生错误。 但是,如果解释匹配成功,剖析器将不会为此规则生成pair。
理论上,解释可以出现在程序的任何地方。 因此,我们需要在每一条规则的后面加上 这样一条规则“comments?”吗?是不是感觉有点太贫苦了。
幸亏,为了解决这个问题,Pest 提供了两个固定的规则名称——COMMENT 或 WHITESPACE——生成的剖析器将自动允许这些表示的解释或空格存在于输入中的任何位置。 如果我们界说任一规则,生成的剖析器将隐式查抄每个 "~" 和所有重复项。
另一方面,我们并不总是想要这种行为。 例如,当我们编写需要具有特定数量空格的规则时,我们就不能让隐式规则生效。
作为解决方法,我们可以界说原子规则(不执行隐式匹配),方法是在界说规则时在左大括号 { 之前添加 at @ 或 $ 符号。 @ 使规则原子化且静默,而 $ 将使规则原子化并像任何其他规则一样生成pair。
内置输入规则的开始和结束
SOI 和 EOI 是两个特殊的内置规则,它们不匹配任何内容,而是表示输入的开始和结束。 当我们想要确保剖析整个输入而不是此中的一部分,或者在规则开头允许空格和解释时,这些非常有用。
以上内容基本上涵盖了编写 PEG 规则的重要底子知识,更多内容,可以在 Pest 官方文档中找到完整的具体信息。
使用 Pest 演示一个简单的剖析器
使用 Pest 构建剖析器,我们可以或许界说息争析任何语法,包括 Rust。 下面我就将构建一个剖析器,该剖析器模拟计算器的功能,可以或许实现算数的加减乘除,逻辑与,逻辑或,位于,位或等一系列运算。
首先,创建一个新项目并添加 pest 和 pest_derive 作为依赖项。
https://img2024.cnblogs.com/blog/986198/202404/986198-20240427184959787-693245097.png
https://img2024.cnblogs.com/blog/986198/202404/986198-20240427185003688-716761623.png
紧接着,就根据我们前面讨论过的pest的规则来界说我们的pest语法文件
https://img2024.cnblogs.com/blog/986198/202404/986198-20240427185736705-908506511.png
一旦pest语法文件生成好了,我们就可以开始通过rust来编写代码实现我们的剖析器的功能
https://img2024.cnblogs.com/blog/986198/202404/986198-20240427185948990-754709119.png
最终我们可以看到剖析器的运行结果
https://img2024.cnblogs.com/blog/986198/202404/986198-20240427190054355-777915109.png
完美的实现了一个计算器的基本运算功能。
作为一个pest的入门者,我从一开始的茫然无措,到渐渐的步入正轨,开始写本身的pest语法文件,到把语法文件通过rust来实现剖析器的功能,中间履历了很多困难,也收获了很多的快乐,希望在未来可以或许和更多的志同道合的朋侪们,一起在pest的蹊径上共同成长。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]