如何在递归正则表达式中匹配一个组?

3
我正在编写一个简单的正则表达式,需要接收一对坐标和/或地图名称。
例如:
move 10 15 # should returns [[10, 15]]
move 10 15 map # should returns [[10, 15, 'map']]
move map # should returns [['map']]
move 10 15 mapA mapB # should returns [[10, 15, 'mapA'], ['mapB']] 
move 10 15 mapA mapB 33 44 # should returns [[10, 15, 'mapA'], ['mapB'], [33, 44]]
move 10 15 mapA 33 44 mapB # should returns [[10, 15, 'mapA'], [33, 44, 'mapB']]

然后,我写了这个正则表达式:

/
  (?(DEFINE)
     (?<coord>    (?<x>\d+)\s+(?<y>\d+) )
     (?<map>      (?<mapname>[a-zA-Z]+) )
     (?<commands> \s* (?: (?&coord) | (?&map) ) \s* (?&commands)? )
  )
  move\s+(?&commands)
/six

但是我该如何使用Perl获取groups xymap的值呢?

我尝试了一些方法:

use strict;
use warnings;

my $command = 'move 10 15';

$command =~ /
  (?(DEFINE)
     (?<coord>    (?<x>\d+)\s+(?<y>\d+) )
     (?<map>      (?<mapname>[a-zA-Z]+) )
     (?<commands> \s* (?: (?&coord) | (?&map) ) \s* (?&commands)? )
  )
  move\s+(?&commands)
/six;

while (my ($k,$v) = each %+) { print "$k $v\n" }
print "$+{x}";

你的正则表达式已经正常工作了,你只需要访问这些组吗? - revo
@revo 是的。就我测试的情况来看,我的正则表达式是有效的,但我不知道如何访问这些组。 - macabeus
请查看此链接:https://stackoverflow.com/questions/37369803/how-can-i-access-the-value-in-a-named-capture-group-in-a-regex-in-perl - revo
@revo 我尝试使用它,但是不行。%+ 没有返回任何东西。 - macabeus
@Jan 抱歉。我更新了代码,将第二个 map 重命名为 mapname。但是我仍然遇到了同样的问题。 - macabeus
显示剩余3条评论
3个回答

5
作为提问者,您无法使用这种方法。根据perlre的说明:
请注意,在递归中匹配的捕获组在递归返回后不可访问,因此需要额外一层捕获组。
但是,模式<x>不能通过“额外一层”捕获来实现,因为它仅在语法中使用。您只能使用整个模式。
if ($command =~ /
        move\s+ (?<match>(?&commands))
        (?(DEFINE)
            (?<coord>    (?<x>\d+)\s+(?<y>\d+) )
            (?<map>      (?<mapname>[a-zA-Z]+) )
            (?<commands> \s* (?: (?&coord) | (?&map) ) \s* (?&commands)? )
        )
    /six)
{
    say "got: $+{match}";
}

在模式结束时,我将?(DEFINED)块移动到了建议的位置。

请注意,这也没有意义:在递归匹配中,应该获取哪个<x>?因此,您需要重新构建方法以能够重新捕获所需的匹配;但是,如果您想要一个被嵌套得很深的子模式,我不知道如何做到这一点。

对于所提出的问题,我会编写一个简单的解析器,而不是使用万能的正则表达式。或者,在您的方法中重新处理匹配的部分,一旦您有了它,这样做会更容易。

然后还有一些强大的工具,例如Marpa::R2Parse::RecDescentRegexp::Grammars


非常感谢你的好回答!我只是为了学习目的(和“坚持”),来比较正则表达式和简单解析器之间的限制。 在我的实际项目中,我使用解析器组合器来完成它。 - macabeus

2

由于我还不能发表评论,Stefan Becker的解决方案存在缺陷。

如果坐标为0,则它将失败。

这是修复方法:

原始答案:Original Answer

#!/usr/bin/perl
use warnings;
use strict;

use Data::Dumper;

while (<DATA>) {
    my @row;
    chomp;
    if (/^move/) {
        while (/(?:(\d+)\s+(\d+))?(?:\s+([[:alpha:]]+))?/g) {
            my @match;
            push(@match, +$1, +$2) if defined $1 && defined $2;
            push(@match, $3)       if $3;
            push(@row, \@match) if @match;
        }
    }

    print "$_: ", Dumper(\@row);
}

exit 0;

__DATA__
move 10 15
move 10 15 map
move map
move 10 15 mapA mapB
move 10 15 mapA mapB 33 44
move 10 15 mapA 33 44 mapB
move 0 15 mapA 33 44 mapB

2
也许分而治之比将所有东西都强行塞入一个正则表达式更有效?最初的回答。
#!/usr/bin/perl
use warnings;
use strict;

use Data::Dumper;

while (<DATA>) {
    my @row;
    chomp;
    if (/^move/) {
        while (/(?:(\d+)\s+(\d+))?(?:\s+([[:alpha:]]+))?/g) {
            my @match;
            push(@match, +$1, +$2) if $1 && $2;
            push(@match, $3)       if $3;
            push(@row, \@match) if @match;
        }
    }

    print "$_: ", Dumper(\@row);
}

exit 0;

__DATA__
move 10 15
move 10 15 map
move map
move 10 15 mapA mapB
move 10 15 mapA mapB 33 44
move 10 15 mapA 33 44 mapB

测试运行:

$ perl dummy.pl
move 10 15: $VAR1 = [
          [
            '10',
            '15'
          ]
        ];
move 10 15 map: $VAR1 = [
          [
            '10',
            '15',
            'map'
          ]
        ];
move map: $VAR1 = [
          [
            'map'
          ]
        ];
move 10 15 mapA mapB: $VAR1 = [
          [
            '10',
            '15',
            'mapA'
          ],
          [
            'mapB'
          ]
        ];
move 10 15 mapA mapB 33 44: $VAR1 = [
          [
            '10',
            '15',
            'mapA'
          ],
          [
            'mapB'
          ],
          [
            '33',
            '44'
          ]
        ];
move 10 15 mapA 33 44 mapB: $VAR1 = [
          [
            '10',
            '15',
            'mapA'
          ],
          [
            '33',
            '44',
            'mapB'
          ]
        ];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接