perlboy_emeritus has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks;

I've been asked to review and refactor/optimize a large collection of perl5 scripts, and a great many that include regular expressions would appear to benefit from s///e, the eval modifier, as in s/PATTERN/CODE/e, iff I can substitute ANY regex for CODE. Programming Perl 4th, pg 254 says, to wit: "Applications of this technique are limitless", and I agree if I can generalize the use of any regular expression in CODE. However, things are not going according to plan in my perl (v5.36.1) built for darwin-2level. A very simple case in point (I have much more complex instances once I can get this basic example to work.

my $str = 'This ia a real number, 123456.56'; say $str; (my $res = $str) =~ s{ (\d+) }{ sprintf("%s", 4*$1) }xe; # $1*4, just +to show /e works say $res; say "\$1 = $1"; (my $adj = $1) =~ s/(\d)/3/g; # replace each \d with '3', same reason say $adj; ($res = $str) =~ s{ (\d+) }{ ($adj = $1) =~ s/(\d)/3/g }xe; # replace +'123456' with '333333', proof of concept say $res;

This yields:

   This ia a real number, 123456.56
   This ia a real number, 493824.56
   $1 = 123456
   333333
   This ia a real number, 6.56

The resulting string after substitution should be 333333.56. There are, indeed 6 substitutions, so it looks like the boolean value of each iteration is being substituted. The fragment '($adj = $1) =~' I believe is required because $1 is immutable, and the result is the same with /ee. Any suggestions re patterns of regex expressions in the CODE part of S/PATTERN/CODE/ would be very appreciated.

Thanks Monks for your help.

Replies are listed 'Best First'.
Re: regex in REPLACEMENT in s///
by tybalt89 (Monsignor) on Sep 12, 2023 at 19:51 UTC
    ($res = $str) =~ s{ (\d+) }{ ($adj = $1) =~ s/(\d)/3/g; $adj }xe;

    or

    ($res = $str) =~ s{ (\d+) }{ $1 =~ s/(\d)/3/gr }xe;

      Aye, /r (introduced in 5.14) is key here.

      You can use /r on the s/// too.

      my $res = $str =~ s{ (\d+) }{ $1 =~ s/(\d)/3/gr }xer;
      And we don't need those captures.
      my $res = $str =~ s{ \d+ }{ $& =~ s/\d/3/gr }xer;

        I probably would have gone for

        $res = $str =~ s{ \d+ }{ $& =~ tr//3/cr }xer;

        or

        $res = $str =~ s{ \d+ }{ 3 x length $& }xer;

        TMTOWTDI gone wild :)

      Thank you tybalt89; I get it :-) The REPLACEMENT expression is a perl script, and the last expression evaluated is the script's return value. I did $adj; to be grammatically correct, although plain $adj works. As I said in my post, I have much use for this idiom. The /r is also spot on with $1. Thanks again.

Re: regex in REPLACEMENT in s///
by hippo (Archbishop) on Sep 12, 2023 at 19:57 UTC

    It returns the result rather than the value being amended. Explicitly give that value to return the new string.

    use strict; use warnings; use feature 'say'; my $str = 'This ia a real number, 123456.56'; say $str; (my $res = $str) =~ s{ (\d+) }{ sprintf("%s", 4*$1) }xe; # $1*4, just +to show /e works say $res; say "\$1 = $1"; (my $adj = $1) =~ s/(\d)/3/g; # replace each \d with '3', same reason say $adj; ($res = $str) =~ s{ (\d+) }{ ($adj = $1) =~ s/(\d)/3/g; $adj }xe; # re +place '123456' with '333333', proof of concept say $res;

    🦛

      Just a nitpick: you only need parentheses when there's a capture, so you can remove them most of the times.
      (my $adj = $1) =~ s/\d/3/g; # No parentheses needed. ($res = $str) =~ s{ (\d+) }{ ($adj = $1) =~ s/\d/3/g; $adj }xe; # Par +entheses only needed in the outer substitution.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

        Indeed so. That would be just one of quite a few changes I would make to perlboy_emeritus's code if I were writing it myself. Instead, I tried to make the smallest number of changes to his original code to achieve what should be his desired outcome so that it is clearest that these are the changes needed to address the stated problem.

        If writing it myself but still keeping s/// as the operator under test it would probably look like this:

        use strict; use warnings; use 5.020; # for best efficiency on $& use Test::More tests => 4; my $orig = 'This is a real number, 123456.56'; my $want = 'This is a real number, 493824.56'; (my $have = $orig) =~ s/\d+/sprintf "%i", 4 * $&/e; my $intpart = $&; is $intpart, 123456, 'int part captured'; is $have, $want, 'Multiply int part by 4'; ($have = $intpart) =~ s/./3/g; is $have, 333333, 'Int part digits set to "3" trivially'; $want = 'This is a real number, 333333.56'; ($have = $orig) =~ s/\d+/(my $int = $&) =~ s#.#3#g; $int/e; is $have, $want, 'Replace int part in string with all 3s';

        Look, Ma - no capture groups! :-)


        🦛

        you only need parentheses when there's a capture

        Are they not also needed where there is optionality?

        Could this be (sensibly) rewritten without the parenthesis?

        $example =~ s/hiss(es)?/leak/;
        To substitute hiss/hisses for leak but not substitute hisse or hisss.

      Thank you hippo; tybalt89 also answered in like fashion, both spot on. Very much appreciated as I expect to make much use of this idiom with much more complex regex.

Re: regex in REPLACEMENT in s///
by Anonymous Monk on Sep 13, 2023 at 10:52 UTC
    s/.../foo($1,$2)/e