[Templates] Correct replace vmethod
Sergey Martynoff
sergey@martynoff.info
Thu, 2 Feb 2006 07:35:42 +0300
> I have created a simple test, which compares results of native
> perl s/// (via eval) and proposed template 'replace' vmethod.
After thinking a bit, I understand that trying to recreate perl
s/// behavior in details is a bad idea. I think that reasonable
level of details is simulating perl's $1, $2 etc backreferences,
with possibility to escape $ with backslash, and to escape
backslash itself before $1 (etc). So I throw out \1, \2 tests from
my vmeth_replace.t and added some other tests. You can find it
here: http://martynoff.info/tt2/
Here is my version of 'replace' vmethod, which satisfies the test:
'replace' => sub {
my ($text, $pattern, $replace, $global) = @_;
my ($matched, $after, @start, @end);
my $result = '';
$global = 1 unless defined $global;
while ($text =~ m/$pattern/) {
if($#- == 0) {
# no captured groups so do a simple search and
replace
if($global) { $text =~ s/$pattern/$replace/g }
else { $text =~ s/$pattern/$replace/ }
last;
}
# extract the bit before the match, the match itself,
the
# bit after and the positions of all subgroups
$result .= substr($text, 0, $-[0]) if $-[0];
$after = substr($text, $+[0]);
@start = @-;
@end = @+;
# first, collect all matched groups to array
my @backrefs;
for (my $i = 0; $i < @start; $i++) {
push @backrefs,
substr( $text, $start[$i], $end[$i] -
$start[$i] );
}
# just replace whole match :)
$matched = $replace;
# replace the $1, $2, etc. placeholders
# in reverse order (to ensure we do $10 before $1)
$matched =~ s{
\\(\\|\$) # an escaped backslash (\\) or $
sign (\$)
|
\$(\d+) # backreference
}{
$1 || $backrefs[$2]
}gxoe;
# add the modified $matched output to the result and
loop if global
$result .= $matched;
$text = $after;
last unless $global && length $text;
}
return $result . $text;
},
(It is also available in separate file)
--
Sergey Martynoff