从语法中提取标记
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
将它们转换为 Int
s
对于 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。
今年我一直在研究 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
将它们转换为 Int
s
对于 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。