从语法中提取标记

时间:2018-12-10 18:12:02

标签: extract grammar abstract-syntax-tree perl6 parse-tree

今年我一直在研究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}}

这似乎有些混乱,是否有更好的方法?

2 个答案:

答案 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