【问题标题】:Extract tokens from grammar从语法中提取标记
【发布时间】:2018-12-10 18:12:01
【问题描述】:

今年我一直在解决 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');

我有兴趣从结果解析中提取匹配的实际标记,即 id、x + y 坐标和 height + width 从结果解析的维度。我知道我可以从Claim.parse(&lt;input&gt;) 的结果Match 对象中提取它们,但我必须深入挖掘每个语法生成以获得我需要的值,例如

say $match<id>.hash<digits>.<digit>;

这似乎有点乱,有没有更好的方法?

【问题讨论】:

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


    【解决方案1】:

    对于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 坐标和 height + width 从结果解析的维度。

    对于 多个 标记的匹配,您不再有依赖 Perl 6 猜测您的意思的奢侈。 (当只有一个时,猜猜它猜你指的是哪一个。:))

    一种编写say 以获得y 坐标的方法:

    say ~$match<coordinates><digits>[1] # 3
    

    如果您想删除&lt;digits&gt;,您可以标记模式的哪些部分应存储在编号捕获的列表中。一种方法是在这些部分周围加上括号:

    token coordinates { (<digits>) ',' (<digits>) }
    

    现在您无需提及&lt;digits&gt;

    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
    

    这说明您可以在任何规则的任何点编写任意代码——这是大多数解析形式及其相关工具所无法实现的——并且代码可以访问当时的解析状态。

    预挖不那么混乱

    内联代码又快又脏。使用变量也是如此。

    存储数据的正常做法是使用make 函数。这会将数据挂在与给定规则相对应的正在构建的匹配对象上。然后可以使用.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? StackOverflow 标题对我说...)

    在上面的示例中,我仅为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 便利功能。您可以从语法中抽象出在成功匹配规则结束时要执行的所有操作,确保这是一个干净的代码解决方案,并且单个语法仍然可用于多个操作。

    最后一件事。您可能希望修剪解析树以省略不必要的叶子细节(以减少内存消耗和/或简化解析树显示)。为此,请编写&lt;.foo&gt;,并在规则名称前加上一个点,以切换该规则的默认自动捕获关闭

    【讨论】:

    • 可能需要我一点时间来消化所有这些,但这更多是我所希望的。谢谢@raiph。
    【解决方案2】:

    您可以直接引用每个命名的部分。因此,要获取您可以访问的坐标:

    say $match.&lt;coordinates&gt;.&lt;digits&gt;

    这将返回digits 匹配的数组。 ig 你只想要这些值,最简单的方法可能是:

    say $match.&lt;coordinates&gt;.&lt;digits&gt;.map( *.Int)say $match.&lt;coordinates&gt;.&lt;digits&gt;&gt;&gt;.Int 甚至say $match.&lt;coordinates&gt;.&lt;digits&gt;».Int

    将它们投射到Ints

    对于 id 字段,您可以更轻松地将 &lt;id&gt; 匹配转换为 Int :

    say $match.&lt;id&gt;.Int

    【讨论】:

    • $match&lt;coordinates&gt;&lt;digits&gt; 应该也能正常工作。但是为了清楚起见,您可能希望使用带有句点的版本。它不应该对性能产生影响(注意这里使用了“应该”这个词:-)
    • 是的,我的意思是这与我上面所说的并没有什么不同。我想我很困惑,因为我不知道为什么 Grammar.parse() 的调用者需要了解语法中的内部产生,即&lt;digits&gt;
    • 我认为,如果您不想要那种粒度级别,您可能只想将其设为正则表达式。 :)
    猜你喜欢
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 2013-11-14
    • 2014-04-17
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多