http://www.perlmonks.org?node_id=791138


in reply to AND and OR on Regular Expressions

Thanks to everyone.

It seems that the following code does what I want:

#!perl -w my $test = ( "olive&(popeye|bluto)", "(tom&jerry)|(sylvester&tweety)", "moe&(shemp|curly|joe)&larry", "tom&jerry|sylvester&tweety", # valid "moe ( shemp | curly | joe ) larry", # also valid "moe(&shemp|curly|joe)&larry", # invalid: "(&" instead of "&(" "moe ( shemp | curly | joe", # invalid: missing ")" raises an error )[(shift) - 1]; my $expr = $test; $expr =~ s/(\w+)/"(?=.*\\b($1)\\b)"/ge; $expr =~ s/[\&\s]//g; $expr = "^($expr)"; print "$test\n$expr\n----\n"; while (<DATA>) { print $_ if /$expr/; } __DATA__ tom,jerry jerry,tom jerry,tomas sylvester,tweety tweeter,sylvester tom,sylvester popeye,olive olive,brutus moe,larry shemp,curly,joe larry,moe larry,curly,moe
>re.pl 1 olive&(popeye|bluto) ^((?=.*\b(olive)\b)((?=.*\b(popeye)\b)|(?=.*\b(bluto)\b))) ---- popeye,olive >re.pl 2 (tom&jerry)|(sylvester&tweety) ^(((?=.*\b(tom)\b)(?=.*\b(jerry)\b))|((?=.*\b(sylvester)\b)(?=.*\b(twe +ety)\b))) ---- tom,jerry jerry,tom sylvester,tweety >re.pl 3 moe&(shemp|curly|joe)&larry ^((?=.*\b(moe)\b)((?=.*\b(shemp)\b)|(?=.*\b(curly)\b)|(?=.*\b(joe)\b)) +(?=.*\b(larry)\b)) ---- larry,curly,moe

I know this will raise an error if the expression is invalid, but I'm sure it could be checked first with something "simple" like the following:

die "Invalid expression <$test>\n" if $test =~ /[^a-z\s\&\|\(\)]|^\s*[\&\|]|[\&\|]\s*$|[\&\|]\s*[\&\|]| +[\&\|]\s*\)|\(\s*[\|\&]/;

and I'm sure I can find something more to validate that parenthesis are well paired.

Replies are listed 'Best First'.
Re^2: AND and OR on Regular Expressions
by vitoco (Hermit) on Aug 25, 2009 at 21:18 UTC

    Added another improvement to this converter: "word not in keywords" feature. This is becoming interesting!

    This is an updated code that tries many queries with a simple expression validation included:

    #!perl -w my @data = <DATA>; for my $test ( # ( "olive&(popeye|bluto)", "(tom&jerry)|(sylvester&tweety)", "moe&(shemp|curly|joe)&larry", "moe curly larry", # "&" is optional "moe&!curly&larry", # curly not present "moe ( shemp | curly | joe ) larry", # "|" is required "jerry -tom", # standard way of "AND" and "AND NOT"... "(moe)((shemp)|(curly)|(joe))(larry)", # also this "tom&jerry|sylvester&tweety", # use re's default precedence "moe(&shemp|curly|joe)&larry", # error: "(&" instead of "&(" "moe ( shemp | curly | joe", # error: missing ")" "moe ) curly ( larry", # invalid: bad grouping "(olive)&(popeye|(bluto|brutus)))", # error: extra ")" "(jerry)((tweety))( )", # error: empty group "jerry||tweety", # error: empty word "olive - (bluto | brutus)", # only words can be excluded "olive - bluto - brutus", # Ok, spaces ignored. "(curly|!larry)&!moe", # valid, but senseless OR "moe&!(!curly)&larry", # error: curly present? "tom -!jerry" # error: don't try... # )[(shift)-1] ) { print "\n\n$test\n::\n"; (print("ERROR: Invalid expression\n") , next) if $test =~ /[^a-z\s\&\|\(\)\!\-]|^\s*[\&\|]|[\&\|]\s*$|[\&\|\(]\s +*[\&\|\)]|[\!\-]\s*[^a-z\s]/; # not_valid_chars |op_begins | op_ends | no_consec +utive_ops| negated_operator my $pars = $test; my $i = 0; $i++ while $pars =~ s/\((.*?)\)/$1/; (print("ERROR: Unpaired $1 of other $i pairs found\n") , next) if $pars =~ /([\(\)])/; my $expr = $test; $expr =~ s/([!\-]?)\s*(\w+)/($1?"(?!":"(?=").".*\\b$2\\b)"/ge; $expr =~ s/[\&\s]//g; $expr = "^($expr)"; print "$expr\n::\n"; print grep /$expr/, @data; } __DATA__ tom,jerry jerry,tom jerry,tomas sylvester,tweety tweeter,sylvester tom,sylvester popeye,olive olive,brutus moe,larry shemp,curly,joe larry,moe larry,curly,moe

    To try just one of the queries, remove the comment's chars from the for at the begining, and give a number (starting from 1) as an argument in the command line.

    I also removed the captures while building the regexp because them aren't used. I've just put them there for clarification.

    The validation code is simple because the allowed syntax is simple too, and is very tied to regular expressions.

    BTW, is there a better way to write the lines to check for parity of parentheses?

      I would rather use:

      $i++ while $pars =~ s/\(([^()]*)\)/$1/;

      Better way is to use Regexp::Common and balanced pattern (not tested):

      use Regexp::Common; $pars =~ /^[^()]*$RE{balanced}{-parens=>'()'}[^()]*$/

        The first regexp I thought for the while was the same as yours, which is syntactically correct, since it removes the matching parenteses, starting from the inner ones when nested. But used the other regexp because I think it's faster, and nothing will be done with the remaining string other than to look if there is at least one of the parentheses remaining.

        Using the balanced option of that module sound interesting, but, again, I think that my problem was easy enougth to try an external pattern that calls another subroutine, which is able to manage multiple delimiters.

        I'm still viewing the available regexp modules' docs in CPAN, looking for one that does all I did here at the same time.

        Anyway, thank you for the hints!!!