使用 Perl 正则表达式捕获 C 风格代码块前后的文本
Capturing text before and after a C-style code block with a Perl regular expression
我正在尝试使用 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;
# ends up undefined after the match
if ($text =~ m/(.+?)$code_block(.+)/s){
print ;
print ;
}
我遇到了第二个捕获组在匹配后未初始化的问题。在 DEFINE
块之后没有办法继续正则表达式吗?我认为这应该可以正常工作。
应该包含代码块下方的注释,但它没有,而且我找不到一个很好的理由说明它不起作用。
你非常接近。
(?(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
至于你的代码不起作用,我不太确定。您可以看到您的代码 运行 here,它似乎工作正常。我会改用上面写的正则表达式之一。
编辑 4
我想我终于明白你在说什么了。使用正则表达式,您尝试做的事情是不可能的。你不能在不捕获它的情况下引用一个组,因此,唯一真正的解决方案是捕获它。但是,有一种 hack-around 替代方案适用于您的情况。如果您想在没有第二部分的情况下获取第一部分和最后一部分,您可以使用以下正则表达式,它不会检查正则表达式的第二部分的语法是否正确(缺点)。如果您确实需要检查语法,您将不得不处理额外的捕获组。
(.+?)\{.*\}\s*(?:(?:\/\/([\s\S]*?)(?:\r\n|\r|\n|$))|\/\*([\s\S]*?)\*\/)
这个正则表达式捕获 {
字符之前的所有内容,然后匹配它之后的所有内容,直到它遇到 }
后跟任何空格,最后是 //
。但是,如果您在代码块中有注释(在 }
之后)
,这将中断
也有现成的工具可用于此,只需几行代码。
也许第一个要看的模块是核心Text::Balanced。
列表上下文中的extract_bracketed
returns:匹配的子串,匹配后字符串的剩余部分,匹配前的子串。然后我们可以在余数
中继续匹配
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;
}
打印什么
start
and
, and done
一旦没有匹配,我们需要打印余数,因此 $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 }
。
捕获组按照它们在正则表达式中出现的顺序从左到右编号,而不是它们匹配的顺序。这是您的正则表达式的简化视图:
m/
(.+?) # group 1
(?: # the $code_block regex
(?&block)
(?(DEFINE)
(?<block> ... ) # group 2
)
)
(.+) # group 3
/xs
命名组也可以作为编号组访问。
第二组是block
组。但是,该组仅用作命名子模式,而不是捕获。因此,</code> 捕获值是 undef。</p>
<p>因此,代码块之后的文本将存储在捕获中 <code>
。
有两种方法可以解决这个问题:
对于复杂的正则表达式,只使用命名捕获。一旦您从正则表达式对象中 assemble 正则表达式,或者如果捕获是有条件的,就认为正则表达式很复杂。这里:
if ($text =~ m/(?<before>.+?)$code_block(?<afterwards>.+)/s){
print $+{before};
print $+{afterwards};
}
将所有定义放在末尾,这样它们就不会弄乱您的捕获编号。例如,您的 $code_block
正则表达式只会定义一个命名模式,然后您可以显式调用该模式。
我正在尝试使用 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;
# ends up undefined after the match
if ($text =~ m/(.+?)$code_block(.+)/s){
print ;
print ;
}
我遇到了第二个捕获组在匹配后未初始化的问题。在 DEFINE
块之后没有办法继续正则表达式吗?我认为这应该可以正常工作。
应该包含代码块下方的注释,但它没有,而且我找不到一个很好的理由说明它不起作用。
你非常接近。
(?(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
至于你的代码不起作用,我不太确定。您可以看到您的代码 运行 here,它似乎工作正常。我会改用上面写的正则表达式之一。
编辑 4
我想我终于明白你在说什么了。使用正则表达式,您尝试做的事情是不可能的。你不能在不捕获它的情况下引用一个组,因此,唯一真正的解决方案是捕获它。但是,有一种 hack-around 替代方案适用于您的情况。如果您想在没有第二部分的情况下获取第一部分和最后一部分,您可以使用以下正则表达式,它不会检查正则表达式的第二部分的语法是否正确(缺点)。如果您确实需要检查语法,您将不得不处理额外的捕获组。
(.+?)\{.*\}\s*(?:(?:\/\/([\s\S]*?)(?:\r\n|\r|\n|$))|\/\*([\s\S]*?)\*\/)
这个正则表达式捕获 {
字符之前的所有内容,然后匹配它之后的所有内容,直到它遇到 }
后跟任何空格,最后是 //
。但是,如果您在代码块中有注释(在 }
之后)
也有现成的工具可用于此,只需几行代码。
也许第一个要看的模块是核心Text::Balanced。
列表上下文中的extract_bracketed
returns:匹配的子串,匹配后字符串的剩余部分,匹配前的子串。然后我们可以在余数
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;
}
打印什么
start and , and done
一旦没有匹配,我们需要打印余数,因此 $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 }
。
捕获组按照它们在正则表达式中出现的顺序从左到右编号,而不是它们匹配的顺序。这是您的正则表达式的简化视图:
m/
(.+?) # group 1
(?: # the $code_block regex
(?&block)
(?(DEFINE)
(?<block> ... ) # group 2
)
)
(.+) # group 3
/xs
命名组也可以作为编号组访问。
第二组是block
组。但是,该组仅用作命名子模式,而不是捕获。因此,</code> 捕获值是 undef。</p>
<p>因此,代码块之后的文本将存储在捕获中 <code>
。
有两种方法可以解决这个问题:
对于复杂的正则表达式,只使用命名捕获。一旦您从正则表达式对象中 assemble 正则表达式,或者如果捕获是有条件的,就认为正则表达式很复杂。这里:
if ($text =~ m/(?<before>.+?)$code_block(?<afterwards>.+)/s){ print $+{before}; print $+{afterwards}; }
将所有定义放在末尾,这样它们就不会弄乱您的捕获编号。例如,您的
$code_block
正则表达式只会定义一个命名模式,然后您可以显式调用该模式。