6

我正在尝试使用 Perl 正则表达式在 C 样式代码块之前和之后捕获一些文本。到目前为止,这就是我所拥有的:

use strict;
use warnings;

my $text = << "END";
int max(int x, int y)
{
    if (x > y)
    {
        return x;
    }
    else
    {
        return y;
    }
}
// more stuff to capture
END

# Regex to match a code block
my $code_block = qr/(?&block)
(?(DEFINE)
    (?<block>
        \{                # Match opening brace
            (?:           # Start non-capturing group
                [^{}]++   #     Match non-brace characters without backtracking
                |         #     or
                (?&block) #     Recursively match the last captured group
            )*            # Match 0 or more times
        \}                # Match closing brace
    )
)/x;

# $2 ends up undefined after the match
if ($text =~ m/(.+?)$code_block(.+)/s){
    print $1;
    print $2;
}

我遇到了第二个捕获组在比赛后没有被初始化的问题。有没有办法在一个DEFINE块之后继续一个正则表达式?我认为这应该可以正常工作。

$2应该包含代码块下方的注释,但它没有,我找不到一个很好的理由为什么这不起作用。

4

3 回答 3

4

捕获组按照它们在正则表达式中出现的顺序从左到右编号,而不是按照它们匹配的顺序。这是您的正则表达式的简化视图:

m/
  (.+?)  # group 1
  (?:  # the $code_block regex
    (?&block)
    (?(DEFINE)
      (?<block> ... )  # group 2
    )
  )
  (.+)  # group 3
/xs

命名组也可以作为编号组访问。

第2组是block组。但是,该组仅用作命名子模式,而不用作捕获。因此,$2捕获值是 undef。

因此,代码块之后的文本将存储在 capture 中$3

有两种方法可以解决这个问题:

  • 对于复杂的正则表达式,仅使用命名捕获。一旦您从正则表达式对象组装正则表达式,或者如果捕获是有条件的,则认为正则表达式很复杂。这里:

    if ($text =~ m/(?<before>.+?)$code_block(?<afterwards>.+)/s){
        print $+{before};
        print $+{afterwards};
    }
    
  • 将所有定义放在最后,它们不会弄乱您的捕获编号。例如,您的$code_block正则表达式只会定义一个命名模式,然后您显式调用该模式。

于 2017-09-08T17:35:06.467 回答
2

也有一些现成的工具可以用于此,只需几行代码。

也许要查看的第一个模块是核心Text::Balanced

in 列表上下文返回:匹配的extract_bracketed子字符串、匹配后的剩余字符串以及匹配前的子字符串。然后我们可以在余数中继续匹配

use warnings;
use strict;
use feature 'say';

use Text::Balanced qw/extract_bracketed/;

my $text = 'start {some {stuff} one} and {more {of it} two}, and done';

my ($match, $lead);
while (1) {
    ($match, $text, $lead) = extract_bracketed($text, '{', '[^{]*');
    say $lead // $text;
    last if not defined $match; 
}

打印什么

开始
 和
, 并做了

一旦没有匹配,我们需要打印剩余部分,因此$lead // $text(因为两者都没有$lead)。代码$text直接使用并修改它,直到最后一个余数;如果您想保留原始文本,请先将其保存。

我在上面使用了一个虚构的字符串,但我也在您的代码示例上对其进行了测试。


这也可以使用Regexp::Common来完成。

使用它的正则表达式打破字符串$RE{balanced},然后取奇数元素

use Regexp::Common qw(balanced);

my @parts = split /$RE{balanced}{-parens=>'{}'}/, $text;

my @out_of_blocks = @parts[  grep { $_ & 1 } 1..$#parts ];

say for @out_of_blocks;

如果字符串以分隔符开头,则第一个元素是空字符串,通常使用split.

要清除前导和尾随空格,请通过map { s/(^\s*|\s*$//gr }.

于 2017-09-08T17:18:53.503 回答
1

你很亲近。

(?(DEFINE))将定义您要使用的表达式和部分,但实际上除了定义它们之外没有做任何事情。当您定义变量时,请考虑这个标签(以及它所包含的所有内容)。这很好,很干净,但是定义变量并不意味着变量被使用!

您想在定义代码块后使用它,因此您需要在声明变量后添加表达式(就像在任何编程语言中一样)

(?(DEFINE)
  (?<block>\{(?:[^{}]++|(?&block))*\})
)
(?&block)

这部分定义了你的变量

(?(DEFINE)
  (?<block>\{(?:[^{}]++|(?&block))*\})
)

这部分调用您的变量

(?&block)

编辑

编辑 1

(?(DEFINE)
  (?<block>\{(?:[^{}]++|(?&block))*\})
)
(?&block)\s*(?:\/\/|\/\*)([\s\S]*?)(?:\r\n|\r|\n|$)

上面的正则表达式将在块之后获得注释(正如您已经定义的那样)。

您有一个.将匹配任何字符(换行符除外 - 除非您使用s指定也.应匹配换行符的修饰符)

编辑 2

(?(DEFINE)
  (?<block>\{(?:[^{}]++|(?&block))*\})
)
(?&block)\s*(?:(?:\/\/([\s\S]*?)(?:\r\n|\r|\n|$))|\/\*([\s\S]*?)\*\/)

这个正则表达式在语法上更适合捕获评论。之前的编辑将一直有效,/*直到新行或文件结尾。这将一直工作到结束标记或文件结尾。

编辑 3

至于您的代码不起作用,我不确定。你可以看到你的代码在这里运行,它似乎工作得很好。我会改用上面写的正则表达式之一。

编辑 4

我想我终于明白你在说什么了。使用正则表达式是不可能的。您无法在不捕获组的情况下引用它,因此,唯一真正的解决方案是捕获它。但是,有一种适用于您的情况的替代方法。如果您想在没有第二部分的情况下获取第一部分和最后一部分,您可以使用以下正则表达式,它不会检查正则表达式的第二部分是否有正确的语法(缺点)。如果您确实需要检查语法,您将不得不处理额外的捕获组。

(.+?)\{.*\}\s*(?:(?:\/\/([\s\S]*?)(?:\r\n|\r|\n|$))|\/\*([\s\S]*?)\*\/)

这个正则表达式捕获{字符之前的所有内容,然后匹配它之后的所有内容,直到它遇到}任何空格,最后是//. 但是,如果您在代码块中有注释(在 a 之后}) ,这将中断

于 2017-09-08T16:08:40.890 回答