Hun*_*len 8 grammar extract abstract-syntax-tree parse-tree raku
今年我一直在研究Perl6中的代码问题,并试图用语法来解析第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');
Run Code Online (Sandbox Code Playgroud)
我有兴趣从坐标中提取匹配的实际标记,即id,x + y,以及从结果解析的维度中提取高度+宽度.我知道我可以将它们从生成的Match对象中拉出来Claim.parse(<input>),但我必须深入研究每个语法生成以获得我需要的值,例如
say $match<id>.hash<digits>.<digit>;
Run Code Online (Sandbox Code Playgroud)
这看起来有点乱,是否有更好的方法?
对于你正在解决的特殊挑战,使用语法就像使用大锤来破解坚果.
就像@Scimon说的那样,一个正则表达式就好了.你可以通过适当的方式保持它的可读性.您可以命名捕获并将它们全部保留在顶层:
/ ^
'#' $<id>=(\d+) ' '
'@ ' $<x>=(\d+) ',' $<y>=(\d+)
': ' $<w>=(\d+) x $<d>=(\d+)
$
/;
say ~$<id x y w d>; # 1 1 3 4 4
Run Code Online (Sandbox Code Playgroud)
(前缀~调用.Str右侧的值.在一个Match对象上调用它将字符串化为匹配的字符串.)
有了这个方法,你的问题仍然是完美的,因为重要的是要知道P6在这方面如何从简单的正则表达式(如上面的那个)扩展到最大和最复杂的解析任务.这就是本答案的其余部分所涵盖的内容,以您的示例为出发点.
say $match<id>.hash<digits>.<digit>; # [?1?]
Run Code Online (Sandbox Code Playgroud)
这看起来有点乱,是否有更好的方法?
您say包含不必要的代码和输出嵌套.你可以简化为:
say ~$match<id> # 1
Run Code Online (Sandbox Code Playgroud)
我有兴趣从坐标中提取匹配的实际标记,即id,x + y,以及从结果解析的维度中提取高度+宽度.
对于多个令牌的匹配,你不再依赖于Perl 6来猜测你的意思.(当只有一个,猜猜你猜它是哪一个.:))
写一个say获取y坐标的方法之一:
say ~$match<coordinates><digits>[1] # 3
Run Code Online (Sandbox Code Playgroud)
如果要删除,<digits>可以标记模式的哪些部分应存储在编号的捕获列表中.一种方法是在这些部分周围加上括号:
token coordinates { (<digits>) ',' (<digits>) }
Run Code Online (Sandbox Code Playgroud)
现在你已经不需要提及了<digits>:
say ~$match<coordinates>[1] # 3
Run Code Online (Sandbox Code Playgroud)
您还可以命名新的带括号的捕获:
token coordinates { $<x>=(<digits>) ',' $<y>=(<digits>) }
say ~$match<coordinates><y> # 3
Run Code Online (Sandbox Code Playgroud)
我必须深入研究每个语法的产生,以获得我需要的价值
上述技术仍然深入到自动生成的解析树中,该解析树默认精确地对应于语法规则调用层次结构中隐含的树.上面的技术只是让你挖掘它的方式看起来有点浅.
另一个步骤是将挖掘工作作为解析过程的一部分,以便say简单.
您可以将一些代码内联到TOP令牌中,以便仅存储您所创建的有趣数据.只需{...}在适当的位置插入一个块(对于这种类型的东西,这意味着令牌的结束,因为您需要令牌模式已完成其匹配工作):
my $made;
grammar Claim {
token TOP {
'#' <id> \s* '@' \s* <coordinates> ':' \s* <dimensions>
{ $made = ~($<id>, $<coordinatess><x y>, $<dimensions><digits>[0,1]) }
}
...
Run Code Online (Sandbox Code Playgroud)
现在你可以写:
say $made # 1 1 3 4 4
Run Code Online (Sandbox Code Playgroud)
这说明您可以在任何规则中的任何位置编写任意代码 - 这是大多数解析形式及其相关工具无法实现的 - 并且代码可以访问此时的解析状态.
内联代码快速而脏.所以使用变量.
存储数据的正常做法是改为使用该make功能.这会挂起与给定规则对应的匹配对象的数据.然后可以使用该.made方法检索.而不是$make =你有:
{ make ~($<id>, $<coordinatess><x y>, $<dimensions><digits>[0,1]) }
Run Code Online (Sandbox Code Playgroud)
现在你可以写:
say $match.made # 1 1 3 4 4
Run Code Online (Sandbox Code Playgroud)
那更加整洁.但还有更多.
.oO(在想象的2019年Perl 6圣诞节降临节日历的第一天, StackOverflow标题对我说...)
在上面的例子中,我.made只为TOP节点构建了一个有效载荷.对于较大的语法,通常会形成稀疏子树(我为此创造的术语,因为我找不到标准的现有术语).
这个稀疏子树由.made有效载荷组成,该有效载荷是TOP指.made较低级规则的有效载荷的数据结构,后者又指低级规则等,跳过不感兴趣的中间规则.
对此的规范用例是在解析一些编程代码之后形成抽象语法树.
实际上有一个别名.made,即.ast:
say $match.ast # 1 1 3 4 4
Run Code Online (Sandbox Code Playgroud)
虽然使用起来很简单,但它也是完全一般的.P6使用P6语法来解析P6代码 - 然后使用这种机制构建AST.
对于可维护性和可重用性,你可以和一般应该没有在规则的末尾插入代码内嵌而是应该使用Action对象.
有一系列通用机制可以从简单到复杂的场景进行扩展,并且可以组合成最适合任何给定用例的机制.
添加括号如我上面所解释的,命名捕获,这些括号在零上,如果这是深入挖掘解析树一个很好的简化.
内联在解析规则时要执行的任何操作.此时您可以完全访问解析状态.这非常适合于从解析中轻松提取所需的数据,因为您可以使用make便捷功能.并且您可以抽象出在语法成功匹配规则结束时要采取的所有操作,确保这是一个代码清晰的解决方案,并且单个语法仍然可以重复用于多个操作.
最后一件事.您可能希望修剪解析树以省略不必要的叶子细节(以减少内存消耗和/或简化解析树显示).要执行此操作,请<.foo>使用规则名称前面的点编写,以关闭该规则的默认自动捕获.