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

If I have a regex:

my $regex = qr(/.../); # ... implies put any pattern here

Can I compile with qr another regex $notregex that is true if and only if $regex is false?

I know I can use $regex with !~ to get this effect, but I situations where it would be more elegant to simply pass in the new regex.

Now, supposing that this is possible. Could one write a subroutine that takes one regex as input and returns the "does not match" regex as output?

Replies are listed 'Best First'.
Re: Negation/Complement of a Regex
by moritz (Cardinal) on May 22, 2008 at 16:36 UTC
    my $regex = qr(/.../);

    I assume you meant just qr(...).

    If $regex is not anchored at the start of the string/line, you can build

    my $negated = qr{^(?!.*?$regex)}s;

    (Update: Thinking a bit more about it I came to the conclusion this might even work with anchored regexes. I should test this and update my post again...)

    Note that this is different in how many characters are consumed by the negation (here always none).

    In theory any regular expression can be negated, but then again perl's "regexes" aren't regular at all.

    Second update: Here's a small test script to find out if the negation works. Of course it needs far more test ;-)

    #!/usr/bin/perl use warnings; use strict; use Test::More qw(no_plan); # first item: regex # second item: list of strings that regex should match # third item: list of strings that negated regex should match my @r = ( [qr{a}, ['a'], ['', 'b']], [qr{^a}, [qw(a ab)], ['', 'b', 'ba']], [qr{a$}, [qw(a ba aba)], ['', 'b', 'ab']], ); for (@r){ my ($re, $pass, $fail) = @$_; my $negated = qr{^(?!.*?$re)}; for (@$pass){ like $_, $re; unlike $_, $negated; } for (@$fail){ like $_, $negated; unlike $_, $re; } }
Re: Negation/Complement of a Regex
by almut (Canon) on May 22, 2008 at 17:18 UTC
Re: Negation/Complement of a Regex
by samtregar (Abbot) on May 22, 2008 at 21:38 UTC
    Instead of passing around regexes, pass around matching subroutines. Then you can do:

    my $match = sub { $_[0] =~ /.../ }; my $negated = sub { not $match->(@_) };

    Now that's some Higher Order Perl I can live with!

    -sam

Re: Negation/Complement of a Regex
by gloryhack (Deacon) on May 23, 2008 at 07:44 UTC
    Elegant (like robust) is frequently a very subjective thing. (Maybe that tells you who I am IRL?)

    Just negate where desired and leave it at that. That's why !~ is included in the language, after all.