今年我一直在研究Perl6中代码出现的问题,并试图使用一种语法来分析第3天的输入。
给出以下形式的输入: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');
以及我创建的语法:
Match
我对提取匹配的实际标记感兴趣,即从坐标中提取id,x + y,从维度提取高度+宽度。我知道我可以将它们从Claim.parse(<input>)
的结果say $match<id>.hash<digits>.<digit>;
对象中拉出来,但是我必须深入挖掘每个语法生成,以获得例如所需的值。
{{1}}
这似乎有些混乱,是否有更好的方法?
答案 0 :(得分:8)
对于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
对象上调用时,它会字符串化为匹配的字符串。)
顺便说一句,您的问题依旧完美无缺,因为了解P6在这方面的重要性很重要,从上面的简单正则表达式到最大和最复杂的解析任务,这都是很重要的。因此,以您的示例为起点,这就是其余答案的内容。
say $match<id>.hash<digits>.<digit>; # [「1」]
这似乎有些混乱,是否有更好的方法?
您的say
包括不必要的代码和输出嵌套。您可以将其简化为:
say ~$match<id> # 1
我感兴趣的是提取匹配的实际标记,即从坐标中提取id,x + y,并从结果解析的维度中提取高度+宽度。
对于多个令牌的匹配,您不再需要依靠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
我必须深入挖掘每一个语法产品以获得所需的价值
以上技术仍然全部挖掘到自动生成的解析树中,默认情况下,该解析树恰好对应于规则调用的语法层次结构中隐含的树。以上技术只是使您深入研究它的方式变得更浅。
下一步是在分析过程中进行挖掘工作,以使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
这说明您可以在任何规则中的任意点编写任意代码-这对于大多数解析形式主义及其相关工具而言是不可能的-并且代码可以按当时的状态访问解析状态。 / p>
内联代码又快又脏。所以使用变量。
用于存储数据的正常操作是改为使用make
函数。这会将数据挂起与给定规则相对应构造的匹配对象。然后可以使用.made
方法进行检索。因此,您将拥有$make =
而不是:
{ make ~($<id>, $<coordinatess><x y>, $<dimensions><digits>[0,1]) }
现在您可以编写:
say $match.made # 1 1 3 4 4
那太整齐了。但是还有更多。
.o(在想象中的2019 Perl 6 Christmas Advent calendar的第一天,StackOverflow标题对我说了……)
在上面的示例中,我仅为.made
节点构造了一个TOP
负载。对于较大的语法,通常会形成sparse subtree(我创造了这个术语,因为我找不到标准的现有术语)。
此稀疏子树由.made
的{{1}}有效负载组成,该数据结构是引用较低级别规则的TOP
有效负载的数据结构,而有效负载又依次指向较低级别规则,依此类推,跳过无趣的中间规则。
典型的用法是在解析一些编程代码后形成Abstract Syntax Tree。
实际上.made
有一个别名,即.made
:
.ast
虽然使用起来很简单,但也很通用。 P6使用P6语法解析P6代码-然后使用此机制构建AST。
出于可维护性和可重用性的考虑,您通常可以不在规则末尾插入代码,而应使用Action objects。
有各种各样的通用机制,可以从简单方案扩展到复杂方案,并且可以组合成最适合任何给定用例的方式。
如上文所述,添加括号,将捕获这些括号的位置命名为零,如果这是深入分析树的简化方法。
内联您希望在解析规则期间要执行的任何操作。此时,您可以完全访问分析状态。因为可以使用say $match.ast # 1 1 3 4 4
便捷功能,所以可以很轻松地从解析中仅提取所需的数据,这非常有用。而且,您可以从语法中成功匹配规则之后提取将要采取的所有操作,以确保这是一种从代码角度看是干净的解决方案,并且单个语法仍可重复用于多个操作。
最后一件事。您可能希望修剪解析树以省略不必要的叶子细节(以减少内存消耗和/或简化解析树的显示)。为此,在规则名称前写一个make
,并在其前面加上一个点,以切换该规则的默认自动捕获 off 。
答案 1 :(得分:2)
您可以直接引用每个命名部分。因此,要获取坐标,您可以访问:
say $match.<coordinates>.<digits>
这将返回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