Add "m" modifier to qr pattern passed as parameter

Viewed 148

I would like to be able to add the "m" modifier to a regex that was passed to a function.

The following test script demonstrates what I'm trying to do

#!/usr/bin/env perl

use strict;
use warnings;
use v5.16.3;

use Test::More tests => 3;

my $no_m_modifier_re   = qr{^line1\n^line2};
my $with_m_modifier_re = qr{^line1\n^line2}m;

my $text = <<'EoM';
line1
line2
line3
EoM

unlike( $text, $no_m_modifier_re, 'Text will not match ^ that is equivalent to \A' );
like( $text, $with_m_modifier_re, 'Text will match ^ with m modifier' );

# This fails to add the m modifier to the subexpression
my $add_m_modifier_re = qr{(?m)$no_m_modifier_re};
#my $add_m_modifier_re = qr{(?m:$no_m_modifier_re)};    # Experimented other syntax, with same result
#my $add_m_modifier_re = qr{$no_m_modifier_re}m;
#my $add_m_modifier_re = qr{(?^m:$no_m_modifier_re)};    # suggested by mob, didn't work.

like( $text, $add_m_modifier_re, 'Want this to match, but it fails to add m modifier to subexpression' );

The results are

$ prove -v m_modifier.pl
m_modifier.pl ..
1..3
ok 1 - Text will not match ^ that is equivalent to \A
ok 2 - Text will match ^ with m modifier
not ok 3 - Want this to match, but it fails to add m modifier to subexpression

#   Failed test 'Want this to match, but it fails to add m modifier to subexpression'
#   at m_modifier.pl line 25.
#                   'line1
# line2
# line3
# '
#     doesn't match '(?^:(?m)(?^:^line1\n^line2))'
# Looks like you failed 1 test of 3.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/3 subtests

Test Summary Report
-------------------
m_modifier.t (Wstat: 256 Tests: 3 Failed: 1)
  Failed test:  3
  Non-zero exit status: 1
Files=1, Tests=3,  1 wallclock secs ( 0.04 usr  0.01 sys +  0.14 cusr  0.05 csys =  0.24 CPU)
Result: FAIL

As you can see, I experimented with different syntax for adding the m modifier, but none of them appear to apply to the original pattern.

Any ideas?

This is under Perl 5.16.3. I have not tried more modern versions.

4 Answers

The problem is that you need to change a qr-ed expression, which is a regex object which

...magically differs from a string containing the same characters: ref(qr/x/) returns "Regexp"; however, dereferencing it is not well defined...

I can't find a way to change it, or add a flag to it (other than to edit its stringification).

However, if you can change the approach so to define (non-qr) variables to start with and then later qr-them as needed then it works as desired

use strict;
use warnings;
use v5.16.3;

use Test::More tests => 3;

my $no_m_modifier_re   = q{^line1\n^line2};  # not qr{} but q{}; just a string

my $text = <<'EoM';
line1
line2
line3
EoM

unlike( $text, qr{$no_m_modifier_re}, 'Text does not match ^ equivalent to \A' );

like(   $text, qr{$no_m_modifier_re}m, 'Text matches with the modifier' );

like(   $text, qr{(?m)$no_m_modifier_re}, 'Text matches with the modifier' );

Along with strings one can set up qr-ed variables as well, for convenience, but the main idea is that the user forms a regex pattern along with needed fine-tuning, for example by modifiers.


If either may get passed around they can be told apart by ref

You're almost there. In Perl 5.16.3, it's

qr/(?^m:pattern)/   # equiv to   /pattern/m

In earlier versions, it would be something like

qr/(?m-xis:pattern)/   # equiv to  /pattern/m
qr/(?ix-ms:patterm)/   # equiv to  /pattern/ix

This won't work for all regexp modifiers. In particular, the /g modifier can't be emulated this way.

Demo:

$ perl -E 'say 0 + ("CAT" =~ /cat/)'
0
$ perl -E 'say 0 + ("CAT" =~ /(?^i:cat)/)'
1

Update: found the docs here. Too long to excerpt, but they provide a much broader and deeper understanding of "embedded pattern-match modifiers" than my answer.

I tried qr{(?^m:$no_m_modifier_re)} like you suggested, but it still fails. The test reports doesn't match '(?^u:(?^m:(?^u:^line1\n^line2)))'

You are trying to modify a compiled pattern. For that, you need the following:

use re qw( is_regexp regexp_pattern );

my $re = qr/^line1\n^line2/;

my ($pat, $mods) =
   is_regexp($re)
      ? regexp_pattern($re)
      : ( $re, "" );

$mods .= 'm' if $mods !~ /m/;

$re = eval("qr/\$pat/$mods")
   or die($@);  # Should never happen.

It also works with uncompiled patterns, resulting in a compiled pattern with minimal (?:) nesting.

The result for   "abc"       is   qr/abc/m    which stringifies as   (?^um:abc)
The result for   qr/abc/     is   qr/abc/m    which stringifies as   (?^um:abc)
The result for   qr/abc/m    is   qr/abc/m    which stringifies as   (?^um:abc)
The result for   qr/abc/s    is   qr/abc/sm   which stringifies as   (?^ums:abc)
The result for   qr/abc/sm   is   qr/abc/sm   which stringifies as   (?^ums:abc)

Your approach has been broken since 5.14 inclusive.

Replace:

# This fails to add the m modifier to the subexpression
my $add_m_modifier_re = qr{(?m)$no_m_modifier_re};

(?^u:(?m)(?^u:^line1\n^line2))

With:

my $add_m_modifier = $no_m_modifier_re;
$add_m_modifier =~ s/:/m:/;
my $add_m_modifier_re = qr{$add_m_modifier};

(?^u:(?^um:^line1\n^line2))

I never worked with Perl so don't blame me! ;D

https://perldoc.perl.org/perl5140delta#(?%5E...)-construct-signifies-default-modifiers

Related