Disable capturing in already-compiled regex? (e.g., Perl 5.22+, /n modifier)

Viewed 201

Environment: Perl 5.26.2 x64 on Cygwin x64.

Question: after my $re = qr/...(capturing group).../, is there any way to use $re without capturing into its capturing groups?

X: I am matching lines that could either be:

#define FOO(X,Y) SomeComplicatedStuff

or

#define FOO(X,Y) BAR(X,Y)

I have a compiled regex $re that matches FOO(X,Y) and includes numbered capturing groups to split the match into FOO and X,Y. I would like to match lines of the second form without having to define a separate regex, e.g., using m/$re.+$re/. This works fine, but I get all the capturing groups of FOO when I all I really want are the groups of BAR.

Y: I thought I could do this in 5.22+ with the /n modifier, but I can't get it to work. MCVE:

$ perl -E 'my $re=qr/(foo|bar)/; "foobar" =~ m/$re$re/; say $1, " ", $2;'
foo bar     # as expected

$ perl -E 'my $re=qr/(foo|bar)/; "foobar" =~ m/(?n:$re)$re/; say $1, " ", $2;'
            # I think this should turn off      ^^^  capturing of `foo`
foo bar     # oops - I was hoping for `bar`

$ perl -E 'my $re=qr/(foo|bar)/; "foobar" =~ m/(?n:(foo|bar))$re/; say $1, " ", $2;'
bar         # This works, but I had to inline $re within (?n:...).

Note: I also tried \K:

$ perl -E 'my $re=qr/(foo|bar)/; "foobar" =~ m/$re\K$re/; say $1, " ", $2, " ", $&;'
foo bar bar      # was hoping for `bar  bar`

Edit Forgot to mention — I did look at this related question, but it's not the same problem statement.

2 Answers

The stringification of $re is (?^u:(foo|bar)). In other words, it sets the flags to those used when the pattern was compiled, and thus turning /n off.

You could use any of the following:

my $re = qq/(foo|bar)/;    # Note: Gotta escape `\` that are part of regex escapes.
/(?n:$re)$re/

(example of escapes: qr{(fo\w|ba\w)} becomes qq{(fo\\w|ba\\w)} when using this technique.)

my $re = qr/foo|bar/;
/$re($re)/

my $re = qr/
   (?<foo_or_bar>) ((?<foo_or_bar>))
   (?(DEFINE)
      (?<foo_or_bar>foo|bar)
   )
/x;
/$re/

Well, I found a workaround, but it's certainly not an answer! I'm posting it here anyway, in case it helps anyone else. In my particular use case, the match is split in two. Therefore, using /g on the first match and \G in the second match does the trick. Example:

$ perl -E '
    my $re=qr/(foo|bar)/;
    my $str = "foo bar";

    $str =~ m/$re/g;         # Match `foo`, and set `pos` (because of /g)
    say "Expecting foo: ", $1;

    $str =~ m/\G.+$re/g;     # \G => skip past `foo`, and check ` bar` against `.+$re`
    say "Hoping for bar: ";
'
Expecting foo: foo
Hoping for bar: bar          # Hooray!
Related