【问题标题】:Is this perl6 grammar broken, or is in exposing a bug?这个 perl6 语法是坏了,还是暴露了一个错误?
【发布时间】:2017-09-09 12:29:02
【问题描述】:

以下是一个基于更大语法的测试用例——目标是解析 Unity3D 资产文件中使用的 YAML 子集。有趣的功能是键控数组匹配器。此匹配器循环,将data[i]: val 匹配为<array-name(index)><indexer-and-value(index, name)><array-name> 被重载,所以第一次调用时,它会匹配任何名称。随后的迭代(当索引不为零时)将只匹配所看到的相同名称。

问题的症结在于,当 index>0 时,总是应该是数组的已知名称,并且应该作为参数传递给匹配器。不是——解释器给出了以下错误:

Cannot resolve caller array-name(Match.new(...): 1, Nil, 1); none of these signatures match:
    (Prefab $: Int $ where { ... }, $prevName, Int $indent, *%_)
    (Prefab $: Int $idx, Match $ (@ (Any $prevName, *@)), Int $indent, *%_)
    (Prefab $: Int $idx, @ (Any $prevName, *@), Int $indent, *%_)

所以索引为 1,但之前没有匹配的名称。那个参数是Nil,没有意义。请注意该函数中的注释块:#{ }。如果未注释,则测试用例将停止失败。没有基于最长匹配的分支(| 运算符或proto 匹配器),因此在匹配器中添加额外内容不应更改解析。

测试输入包含在测试用例中。这里是:

#use Grammar::Tracer;
#use Grammar::Debugger;

grammar Prefab {
    token TOP {
        <key> ':' <value=hash-multiline(1)> \n
    }

    token key { \w+ }

    token kvpair(Int $indent=0) {
        [
        || <key> ':'  <hash-multiline($indent+1)>
        || <keyed-array($indent)>
        || <key> ': ' (\w+)
        ]
    }

    token keyed-array(Int $indent) {
        # Keys are built in to the list:
        # look for arrayname[0] first, then match subsequent lines more strictly, based on name[idx]
        :my $idx = 0;
        [
            <array-name($idx, $<array-name>, $indent)>
            <indexer-and-value($idx++, $indent)>
            #{ } # XXX this fixes it, somehow
        ] +% \n

    }
    multi token array-name(0, $prevName, Int $indent) {
        # the first element doesn't need to match indentation
        \w+
    }

    multi token array-name(Int $idx, Match $ ([$prevName, *@]), Int $indent) {
        <.indent($indent)>
        $prevName
    }
    # todo: Can I remove this overload? In testing, the parameter was sometimes an array, sometimes a Match
    multi token array-name(Int $idx, [$prevName, *@], Int $indent) {
        <.indent($indent)>
        $prevName
    }

    # arr[2]: foo
    #    ^^^^^^^^ match this
    token indexer-and-value(Int $idx, Int $indent) {
        '[' ~ ']' $idx
        [
        || ':'  <hash-multiline($indent+1)>
        || ': ' \w+
        ]
    }


    token hash-multiline(Int $indent=0) {
        # Note: the hash does not need a newline if it's over after the first (inline) kv-pair!
        # optional first line which is on the same line as the previous text:
        [
        || [<kvpair($indent)>]  [ \n <.indent($indent)> <kvpair($indent)> ]*
        ||                      [ \n <.indent($indent)> <kvpair($indent)> ]+
        ]
    }

    multi token indent(0) {
        ^^ <?>
    }
    multi token indent(Int $level) {
        ^^ ' ' ** {2*$level}
    }
}

sub MAIN() {
    say so Prefab.parse($*kv-list);
}

my $*kv-list = q:to/END/;
Renderer:
  m_Color[0]: red
END

【问题讨论】:

    标签: regex parsing raku


    【解决方案1】:

    timotimo 解释了 IRC 上的问题——匹配变量($/$0$1 和命名匹配)不是全局的。当匹配器开始时,匹配变量已经被填充。由于性能问题,它们大多*在匹配器主体的其余部分中根本没有更新。但是,当看到代码块(甚至是空块)时,匹配变量会更新。所以“bug”解决方法实际上是一个有效的解决方案——包括一个空块来强制匹配变量更新。

    * $0 似乎已更新并可立即使用。可能其他编号的匹配项也是如此。

    更新:似乎只有在不使用块的情况下在类似代码的上下文中使用它们时才可以立即使用匹配变量,例如在不同匹配器的参数列表中。这里,匹配变量在上一次匹配之后立即可用:

    my regex word { \w+ };
    say 'hellohello' ~~ /<word> $<word>/
    

    但是这个用作参数的例子失败了:

    my regex repeated($x) { [$x]+ };
    say 'ooxoo' ~~ / ^ <repeated('o')> . <repeated($<repeated>)> $ /
    

    除非你添加一个块来强制更新命名匹配变量:

    my regex repeated($x) { [$x]+ };
    say 'ooxoo' ~~ / ^ <repeated('o')> . {} <repeated($<repeated>)> $ /
    

    【讨论】:

      猜你喜欢
      • 2014-11-14
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-07
      • 2015-12-24
      相关资源
      最近更新 更多