从语法中提取标记

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)

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

rai*_*iph 8

对于你正在解决的特殊挑战,使用语法就像使用大锤来破解坚果.

就像@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>使用规则名称前面的点编写,以关闭该规则的默认自动捕获.