从语法中提取标记

Extract tokens from grammar

今年我一直在研究 Perl6 中的 Advent of Code 问题,并尝试使用语法来解析第 3 天的输入。

以这种形式给出的输入:#1 @ 1,3: 4x4 和我创建的语法:

grammar Claim {
  token TOP {
    '#' <id> \s* '@' \s* <coordinates> ':' \s* <dimensions>
  }

  token digits {
    <digit>+
  }

  token id {
    <digits>
  }

  token coordinates {
    <digits> ',' <digits>
  }

  token dimensions {
    <digits> 'x' <digits>
  }
}

say Claim.parse('#1 @ 1,3: 4x4');

我有兴趣提取匹配的实际标记,即来自坐标的 id、x + y,以及来自结果解析的维度的高度 + 宽度。我知道我可以从 Claim.parse(<input>) 的结果 Match 对象中提取它们,但我必须深入挖掘每个语法产生式以获得我需要的值,例如

say $match<id>.hash<digits>.<digit>;

这看起来有点乱,有没有更好的方法?

您可以直接引用您命名的每个部分。因此,要获取坐标,您可以访问:

say $match.<coordinates>.<digits>

这将 return 你 digits 匹配的数组。如果您只想要这些值,最简单的方法可能是:

say $match.<coordinates>.<digits>.map( *.Int)say $match.<coordinates>.<digits>>>.Int 甚至 say $match.<coordinates>.<digits>».Int

将它们转换为 Ints

对于 id 字段,您可以更轻松地将 <id> 匹配转换为 Int :

say $match.<id>.Int

对于the particular challenge you're solving,使用语法就像用大锤敲坚果。

就像@Scimon 所说的那样,一个正则表达式就可以了。您可以通过适当地布局来保持它的可读性。您可以命名捕获并将它们全部放在顶层:

/ ^
  '#' $<id>=(\d+) ' '
  '@ ' $<x>=(\d+) ',' $<y>=(\d+)
  ': ' $<w>=(\d+)  x  $<d>=(\d+)
  $
/;

say ~$<id x y w d>; # 1 1 3 4 4

(前缀 ~ 在其右侧的值上调用 .Str。在 Match object 上调用它会字符串化为匹配的字符串。)

除此之外,您的问题仍然非常棘手,因为重要的是要了解 P6 在这方面如何从简单的正则表达式(如上面的正则表达式)扩展到最大和最复杂的解析任务。这就是这个答案的其余部分,以您的示例为起点。

挖得不那么乱

say $match<id>.hash<digits>.<digit>; # [「1」]

this seems a little messy, is there a better way?

您的 say 包含不必要的代码和输出嵌套。您可以简化为:

say ~$match<id> # 1

挖得更深一点,不那么乱

I am interested in extracting the actual tokens that were matched i.e. id, x + y from coordinates, and height + width from the dimensions from the resulting parse.

对于 多个 标记的匹配,您不再有依赖 Perl 6 猜测您的意思的奢侈。 (当只有一个的时候,猜猜它猜的是哪一个。:))

一种写 say 以获得 y 坐标的方法:

say ~$match<coordinates><digits>[1] # 3

如果您想删除 <digits>,您可以标记模式的哪些部分应存储在编号捕获列表中。一种方法是在这些部分周围加上括号:

token coordinates { (<digits>) ',' (<digits>) }

现在您无需提及 <digits>:

say ~$match<coordinates>[1] # 3

您还可以命名新的带括号的捕获:

token coordinates { $<x>=(<digits>) ',' $<y>=(<digits>) }

say ~$match<coordinates><y> # 3

Pre-digging

I have to dig down through each grammar production to get the value I need

以上技术仍然深入到自动生成的解析树中,默认情况下,它精确对应于规则调用的语法层次结构中隐含的树。上面的技巧只是让你的挖掘方式显得浅了一些。

另一个步骤是将挖掘工作作为解析过程的一部分,以便 say 变得简单。

可以 将一些代码直接内联到 TOP 令牌中以仅存储您制作的有趣数据。只需在适当的位置插入一个 {...} 块(对于这种事情,这意味着令牌的结尾,因为您需要令牌模式已经完成其匹配工作):

my $made;
grammar Claim {
  token TOP {
    '#' <id> \s* '@' \s* <coordinates> ':' \s* <dimensions>
     { $made = ~($<id>, $<coordinatess><x y>, $<dimensions><digits>[0,1]) }
  }
...

现在你可以只写:

say $made # 1 1 3 4 4

这说明您可以在任何规则的任何点编写任意代码——这对于大多数解析形式及其相关工具来说是不可能的——并且代码可以访问此时的解析状态.

Pre-digging不那么乱

内联代码既快又脏。使用变量也是如此。

存储数据的正常做法是使用 make 函数。这会将根据给定规则构建的匹配 object 中的数据挂起。然后可以使用 .made 方法检索它。所以而不是 $make = 你有:

{ make ~($<id>, $<coordinatess><x y>, $<dimensions><digits>[0,1]) }

现在你可以写:

say $match.made # 1 1 3 4 4

这样更整洁。但还有更多。

解析树的稀疏子树

.oO(在想象中的 2019 年的第一天 Perl 6 Christmas Advent calendar Whosebug 标题对我说...)

在上面的示例中,我只为 TOP 节点构造了一个 .made 有效载荷。对于较大的语法,通常会形成 sparse subtree(我为此创造了一个术语,因为我找不到标准的现有术语)。

此稀疏子树由 TOP.made 有效负载组成,该数据结构引用较低级别规则的 .made 有效负载,而较低级别规则又引用较低级别规则等继续,跳过无趣的中间规则。

这个的规范用例是在解析一些编程代码后形成一个 Abstract Syntax Tree

其实.made还有一个别名,即.ast:

say $match.ast # 1 1 3 4 4

虽然这使用起来很简单,但也非常通用。 P6 使用 P6 语法来解析 P6 代码——然后使用这种机制构建 AST。

让一切变得优雅

为了可维护性和可重用性,您可以并且通常应该 在规则末尾插入内联代码,而应该使用 Action objects.

总结

有一系列通用机制可以从简单场景扩展到复杂场景,并且可以组合成最适合任何给定用例的机制。

按照我上面的解释添加括号,命名那些括号为零的捕获,如果这是深入分析树的一个很好的简化。

内联您希望在解析规则期间执行的任何操作。此时您可以完全访问解析状态。这对于从解析中轻松提取所需数据非常有用,因为您可以使用 make 便利函数。您可以从语法中抽象出在成功匹配规则结束时要采取的所有操作,en确保这是一个干净的解决方案 code-wise 并且单个语法仍然可重复用于多个操作。

最后一件事。您可能希望修剪解析树以省略不必要的叶细节(以减少内存消耗 and/or 简化解析树显示)。为此,请在规则名称前加上一个点 <.foo>,以切换该规则的默认自动捕获 off