#################################################################### # # This file was generated using Parse::Yapp version 1.05. # # Don't edit this file, use source file instead. # # ANY CHANGE MADE HERE WILL BE LOST ! # #################################################################### package UR::BoolExpr::BxParser; use vars qw ( @ISA ); use strict; @ISA= qw ( Parse::Yapp::Driver ); #Included Parse/Yapp/Driver.pm file---------------------------------------- { # # Module Parse::Yapp::Driver # # This module is part of the Parse::Yapp package available on your # nearest CPAN # # Any use of this module in a standalone parser make the included # text under the same copyright as the Parse::Yapp module itself. # # This notice should remain unchanged. # # (c) Copyright 1998-2001 Francois Desarmenien, all rights reserved. # (see the pod text in Parse::Yapp module for use and distribution rights) # package Parse::Yapp::Driver; require 5.004; use strict; use vars qw ( $VERSION $COMPATIBLE $FILENAME ); $VERSION = '1.05'; $COMPATIBLE = '0.07'; $FILENAME=__FILE__; use Carp; #Known parameters, all starting with YY (leading YY will be discarded) my(%params)=(YYLEX => 'CODE', 'YYERROR' => 'CODE', YYVERSION => '', YYRULES => 'ARRAY', YYSTATES => 'ARRAY', YYDEBUG => ''); #Mandatory parameters my(@params)=('LEX','RULES','STATES'); sub new { my($class)=shift; my($errst,$nberr,$token,$value,$check,$dotpos); my($self)={ ERROR => \&_Error, ERRST => \$errst, NBERR => \$nberr, TOKEN => \$token, VALUE => \$value, DOTPOS => \$dotpos, STACK => [], DEBUG => 0, CHECK => \$check }; _CheckParams( [], \%params, \@_, $self ); exists($$self{VERSION}) and $$self{VERSION} < $COMPATIBLE and croak "Yapp driver version $VERSION ". "incompatible with version $$self{VERSION}:\n". "Please recompile parser module."; ref($class) and $class=ref($class); bless($self,$class); } sub YYParse { my($self)=shift; my($retval); _CheckParams( \@params, \%params, \@_, $self ); if($$self{DEBUG}) { _DBLoad(); $retval = eval '$self->_DBParse()';#Do not create stab entry on compile $@ and die $@; } else { $retval = $self->_Parse(); } $retval } sub YYData { my($self)=shift; exists($$self{USER}) or $$self{USER}={}; $$self{USER}; } sub YYErrok { my($self)=shift; ${$$self{ERRST}}=0; undef; } sub YYNberr { my($self)=shift; ${$$self{NBERR}}; } sub YYRecovering { my($self)=shift; ${$$self{ERRST}} != 0; } sub YYAbort { my($self)=shift; ${$$self{CHECK}}='ABORT'; undef; } sub YYAccept { my($self)=shift; ${$$self{CHECK}}='ACCEPT'; undef; } sub YYError { my($self)=shift; ${$$self{CHECK}}='ERROR'; undef; } sub YYSemval { my($self)=shift; my($index)= $_[0] - ${$$self{DOTPOS}} - 1; $index < 0 and -$index <= @{$$self{STACK}} and return $$self{STACK}[$index][1]; undef; #Invalid index } sub YYCurtok { my($self)=shift; @_ and ${$$self{TOKEN}}=$_[0]; ${$$self{TOKEN}}; } sub YYCurval { my($self)=shift; @_ and ${$$self{VALUE}}=$_[0]; ${$$self{VALUE}}; } sub YYExpect { my($self)=shift; keys %{$self->{STATES}[$self->{STACK}[-1][0]]{ACTIONS}} } sub YYLexer { my($self)=shift; $$self{LEX}; } ################# # Private stuff # ################# sub _CheckParams { my($mandatory,$checklist,$inarray,$outhash)=@_; my($prm,$value); my($prmlst)={}; while(($prm,$value)=splice(@$inarray,0,2)) { $prm=uc($prm); exists($$checklist{$prm}) or croak("Unknow parameter '$prm'"); ref($value) eq $$checklist{$prm} or croak("Invalid value for parameter '$prm'"); $prm=unpack('@2A*',$prm); $$outhash{$prm}=$value; } for (@$mandatory) { exists($$outhash{$_}) or croak("Missing mandatory parameter '".lc($_)."'"); } } sub _Error { print "Parse error.\n"; } sub _DBLoad { { no strict 'refs'; exists(${__PACKAGE__.'::'}{_DBParse})#Already loaded ? and return; } my($fname)=__FILE__; my(@drv); open(DRV,"<$fname") or die "Report this as a BUG: Cannot open $fname"; while() { /^\s*sub\s+_Parse\s*{\s*$/ .. /^\s*}\s*#\s*_Parse\s*$/ and do { s/^#DBG>//; push(@drv,$_); } } close(DRV); $drv[0]=~s/_P/_DBP/; eval join('',@drv); } #Note that for loading debugging version of the driver, #this file will be parsed from 'sub _Parse' up to '}#_Parse' inclusive. #So, DO NOT remove comment at end of sub !!! sub _Parse { my($self)=shift; my($rules,$states,$lex,$error) = @$self{ 'RULES', 'STATES', 'LEX', 'ERROR' }; my($errstatus,$nberror,$token,$value,$stack,$check,$dotpos) = @$self{ 'ERRST', 'NBERR', 'TOKEN', 'VALUE', 'STACK', 'CHECK', 'DOTPOS' }; #DBG> my($debug)=$$self{DEBUG}; #DBG> my($dbgerror)=0; #DBG> my($ShowCurToken) = sub { #DBG> my($tok)='>'; #DBG> for (split('',$$token)) { #DBG> $tok.= (ord($_) < 32 or ord($_) > 126) #DBG> ? sprintf('<%02X>',ord($_)) #DBG> : $_; #DBG> } #DBG> $tok.='<'; #DBG> }; $$errstatus=0; $$nberror=0; ($$token,$$value)=(undef,undef); @$stack=( [ 0, undef ] ); $$check=''; while(1) { my($actions,$act,$stateno); $stateno=$$stack[-1][0]; $actions=$$states[$stateno]; #DBG> print STDERR ('-' x 40),"\n"; #DBG> $debug & 0x2 #DBG> and print STDERR "In state $stateno:\n"; #DBG> $debug & 0x08 #DBG> and print STDERR "Stack:[". #DBG> join(',',map { $$_[0] } @$stack). #DBG> "]\n"; if (exists($$actions{ACTIONS})) { defined($$token) or do { ($$token,$$value)=&$lex($self); #DBG> $debug & 0x01 #DBG> and print STDERR "Need token. Got ".&$ShowCurToken."\n"; }; $act= exists($$actions{ACTIONS}{$$token}) ? $$actions{ACTIONS}{$$token} : exists($$actions{DEFAULT}) ? $$actions{DEFAULT} : undef; } else { $act=$$actions{DEFAULT}; #DBG> $debug & 0x01 #DBG> and print STDERR "Don't need token.\n"; } defined($act) and do { $act > 0 and do { #shift #DBG> $debug & 0x04 #DBG> and print STDERR "Shift and go to state $act.\n"; $$errstatus and do { --$$errstatus; #DBG> $debug & 0x10 #DBG> and $dbgerror #DBG> and $$errstatus == 0 #DBG> and do { #DBG> print STDERR "**End of Error recovery.\n"; #DBG> $dbgerror=0; #DBG> }; }; push(@$stack,[ $act, $$value ]); $$token ne '' #Don't eat the eof and $$token=$$value=undef; next; }; #reduce my($lhs,$len,$code,@sempar,$semval); ($lhs,$len,$code)=@{$$rules[-$act]}; #DBG> $debug & 0x04 #DBG> and $act #DBG> and print STDERR "Reduce using rule ".-$act." ($lhs,$len): "; $act or $self->YYAccept(); $$dotpos=$len; unpack('A1',$lhs) eq '@' #In line rule and do { $lhs =~ /^\@[0-9]+\-([0-9]+)$/ or die "In line rule name '$lhs' ill formed: ". "report it as a BUG.\n"; $$dotpos = $1; }; @sempar = $$dotpos ? map { $$_[1] } @$stack[ -$$dotpos .. -1 ] : (); $semval = $code ? &$code( $self, @sempar ) : @sempar ? $sempar[0] : undef; splice(@$stack,-$len,$len); $$check eq 'ACCEPT' and do { #DBG> $debug & 0x04 #DBG> and print STDERR "Accept.\n"; return($semval); }; $$check eq 'ABORT' and do { #DBG> $debug & 0x04 #DBG> and print STDERR "Abort.\n"; return(undef); }; #DBG> $debug & 0x04 #DBG> and print STDERR "Back to state $$stack[-1][0], then "; $$check eq 'ERROR' or do { #DBG> $debug & 0x04 #DBG> and print STDERR #DBG> "go to state $$states[$$stack[-1][0]]{GOTOS}{$lhs}.\n"; #DBG> $debug & 0x10 #DBG> and $dbgerror #DBG> and $$errstatus == 0 #DBG> and do { #DBG> print STDERR "**End of Error recovery.\n"; #DBG> $dbgerror=0; #DBG> }; push(@$stack, [ $$states[$$stack[-1][0]]{GOTOS}{$lhs}, $semval ]); $$check=''; next; }; #DBG> $debug & 0x04 #DBG> and print STDERR "Forced Error recovery.\n"; $$check=''; }; #Error $$errstatus or do { $$errstatus = 1; &$error($self); $$errstatus # if 0, then YYErrok has been called or next; # so continue parsing #DBG> $debug & 0x10 #DBG> and do { #DBG> print STDERR "**Entering Error recovery.\n"; #DBG> ++$dbgerror; #DBG> }; ++$$nberror; }; $$errstatus == 3 #The next token is not valid: discard it and do { $$token eq '' # End of input: no hope and do { #DBG> $debug & 0x10 #DBG> and print STDERR "**At eof: aborting.\n"; return(undef); }; #DBG> $debug & 0x10 #DBG> and print STDERR "**Dicard invalid token ".&$ShowCurToken.".\n"; $$token=$$value=undef; }; $$errstatus=3; while( @$stack and ( not exists($$states[$$stack[-1][0]]{ACTIONS}) or not exists($$states[$$stack[-1][0]]{ACTIONS}{error}) or $$states[$$stack[-1][0]]{ACTIONS}{error} <= 0)) { #DBG> $debug & 0x10 #DBG> and print STDERR "**Pop state $$stack[-1][0].\n"; pop(@$stack); } @$stack or do { #DBG> $debug & 0x10 #DBG> and print STDERR "**No state left on stack: aborting.\n"; return(undef); }; #shift the error token #DBG> $debug & 0x10 #DBG> and print STDERR "**Shift \$error token and go to state ". #DBG> $$states[$$stack[-1][0]]{ACTIONS}{error}. #DBG> ".\n"; push(@$stack, [ $$states[$$stack[-1][0]]{ACTIONS}{error}, undef ]); } #never reached croak("Error in driver logic. Please, report it as a BUG"); }#_Parse #DO NOT remove comment 1; } #End of include-------------------------------------------------- sub new { my($class)=shift; ref($class) and $class=ref($class); my($self)=$class->SUPER::new( yyversion => '1.05', yystates => [ {#State 0 ACTIONS => { 'LEFT_PAREN' => 4, 'BETWEEN_WORD' => 11, 'ASC_WORD' => 1, 'DESC_WORD' => 2, 'IDENTIFIER' => 3, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, DEFAULT => -1, GOTOS => { 'boolexpr' => 5, 'expr' => 10, 'keyword_as_value' => 7, 'property' => 15, 'condition' => 14 } }, {#State 1 DEFAULT => -66 }, {#State 2 DEFAULT => -65 }, {#State 3 DEFAULT => -29 }, {#State 4 ACTIONS => { 'ASC_WORD' => 1, 'LEFT_PAREN' => 4, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'expr' => 16, 'keyword_as_value' => 7, 'condition' => 14, 'property' => 15 } }, {#State 5 ACTIONS => { '' => 17, 'OFFSET' => 18, 'ORDER_BY' => 20, 'GROUP_BY' => 19, 'LIMIT' => 21 } }, {#State 6 DEFAULT => -64 }, {#State 7 DEFAULT => -30 }, {#State 8 DEFAULT => -62 }, {#State 9 DEFAULT => -68 }, {#State 10 ACTIONS => { 'AND' => 22, 'OR' => 23 }, DEFAULT => -2 }, {#State 11 DEFAULT => -63 }, {#State 12 DEFAULT => -67 }, {#State 13 DEFAULT => -61 }, {#State 14 DEFAULT => -7 }, {#State 15 ACTIONS => { 'DOUBLEEQUAL_SIGN' => 24, 'NOT_BANG' => 33, 'NOT_WORD' => 34, 'COLON' => 25, 'LIKE_WORD' => 35, 'OPERATORS' => 26, 'EQUAL_SIGN' => 36, 'FALSE_WORD' => 37, 'BETWEEN_WORD' => 29, 'TILDE' => 38, 'TRUE_WORD' => 40, 'IN_WORD' => 41, 'IS_NOT_NULL' => 44, 'IS_NULL' => 32 }, GOTOS => { 'operator' => 28, 'like_operator' => 30, 'an_operator' => 31, 'in_operator' => 39, 'null_op_word' => 27, 'boolean_op_word' => 43, 'between_operator' => 42, 'negation' => 45 } }, {#State 16 ACTIONS => { 'AND' => 22, 'OR' => 23, 'RIGHT_PAREN' => 46 } }, {#State 17 DEFAULT => 0 }, {#State 18 ACTIONS => { 'INTEGER' => 47 } }, {#State 19 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'keyword_as_value' => 7, 'group_by_list' => 48, 'property' => 49 } }, {#State 20 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'MINUS' => 50, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'order_by_list' => 53, 'order_by_property' => 51, 'keyword_as_value' => 7, 'property' => 52 } }, {#State 21 ACTIONS => { 'INTEGER' => 54 } }, {#State 22 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'LEFT_PAREN' => 4, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'expr' => 55, 'keyword_as_value' => 7, 'condition' => 14, 'property' => 15 } }, {#State 23 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'LEFT_PAREN' => 4, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'expr' => 56, 'keyword_as_value' => 7, 'condition' => 14, 'property' => 15 } }, {#State 24 DEFAULT => -45 }, {#State 25 ACTIONS => { 'WHITESPACE' => 58 }, DEFAULT => -27, GOTOS => { 'optional_spaces' => 59, 'spaces' => 57 } }, {#State 26 DEFAULT => -43 }, {#State 27 DEFAULT => -20 }, {#State 28 ACTIONS => { 'WHITESPACE' => 58 }, DEFAULT => -27, GOTOS => { 'optional_spaces' => 60, 'spaces' => 57 } }, {#State 29 DEFAULT => -58 }, {#State 30 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'number' => 64, 'value' => 71, 'keyword_as_value' => 70, 'single_value' => 72, 'like_value' => 66, 'subsequent_value_part' => 65 } }, {#State 31 DEFAULT => -39 }, {#State 32 DEFAULT => -23 }, {#State 33 DEFAULT => -42 }, {#State 34 DEFAULT => -41 }, {#State 35 DEFAULT => -46 }, {#State 36 DEFAULT => -44 }, {#State 37 DEFAULT => -22 }, {#State 38 DEFAULT => -48 }, {#State 39 ACTIONS => { 'LEFT_BRACKET' => 76 }, GOTOS => { 'set' => 77 } }, {#State 40 DEFAULT => -21 }, {#State 41 DEFAULT => -51 }, {#State 42 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'between_value' => 78, 'number' => 64, 'keyword_as_value' => 70, 'single_value' => 79, 'subsequent_value_part' => 65 } }, {#State 43 DEFAULT => -19 }, {#State 44 DEFAULT => -25 }, {#State 45 ACTIONS => { 'DOUBLEEQUAL_SIGN' => 24, 'COLON' => 80, 'LIKE_WORD' => 84, 'OPERATORS' => 26, 'EQUAL_SIGN' => 36, 'BETWEEN_WORD' => 81, 'TILDE' => 85, 'IN_WORD' => 86, 'IS_NULL' => 83 }, GOTOS => { 'an_operator' => 82 } }, {#State 46 DEFAULT => -10 }, {#State 47 DEFAULT => -6 }, {#State 48 DEFAULT => -4 }, {#State 49 ACTIONS => { 'AND' => 87 }, DEFAULT => -37 }, {#State 50 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'keyword_as_value' => 7, 'property' => 88 } }, {#State 51 ACTIONS => { 'AND' => 89 }, DEFAULT => -35 }, {#State 52 ACTIONS => { 'ASC_WORD' => 90, 'DESC_WORD' => 91 }, DEFAULT => -31 }, {#State 53 DEFAULT => -3 }, {#State 54 DEFAULT => -5 }, {#State 55 ACTIONS => { 'AND' => 22 }, DEFAULT => -8 }, {#State 56 ACTIONS => { 'AND' => 22, 'OR' => 23 }, DEFAULT => -9 }, {#State 57 DEFAULT => -28 }, {#State 58 DEFAULT => -26 }, {#State 59 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'between_value' => 93, 'number' => 64, 'keyword_as_value' => 70, 'old_syntax_in_value' => 92, 'single_value' => 94, 'subsequent_value_part' => 65 } }, {#State 60 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'number' => 64, 'value' => 95, 'keyword_as_value' => 70, 'single_value' => 72, 'subsequent_value_part' => 65 } }, {#State 61 DEFAULT => -73 }, {#State 62 DEFAULT => -74 }, {#State 63 ACTIONS => { 'INTEGER' => 97, 'REAL' => 96 } }, {#State 64 DEFAULT => -72 }, {#State 65 DEFAULT => -81 }, {#State 66 DEFAULT => -12 }, {#State 67 DEFAULT => -85 }, {#State 68 DEFAULT => -84 }, {#State 69 DEFAULT => -71 }, {#State 70 DEFAULT => -76 }, {#State 71 DEFAULT => -50 }, {#State 72 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'WHITESPACE' => 58, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'SINGLEQUOTE_STRING' => 75 }, DEFAULT => -70, GOTOS => { 'number' => 64, 'keyword_as_value' => 70, 'subsequent_values_list' => 100, 'spaces' => 99, 'subsequent_value_part' => 98 } }, {#State 73 DEFAULT => -82 }, {#State 74 DEFAULT => -83 }, {#State 75 DEFAULT => -75 }, {#State 76 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'set_body' => 101, 'number' => 64, 'value' => 102, 'keyword_as_value' => 70, 'single_value' => 72, 'subsequent_value_part' => 65 } }, {#State 77 DEFAULT => -13 }, {#State 78 DEFAULT => -16 }, {#State 79 ACTIONS => { 'MINUS' => 103 } }, {#State 80 ACTIONS => { 'WHITESPACE' => 58 }, DEFAULT => -27, GOTOS => { 'optional_spaces' => 104, 'spaces' => 57 } }, {#State 81 DEFAULT => -59 }, {#State 82 DEFAULT => -40 }, {#State 83 DEFAULT => -24 }, {#State 84 DEFAULT => -47 }, {#State 85 DEFAULT => -49 }, {#State 86 DEFAULT => -52 }, {#State 87 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'keyword_as_value' => 7, 'group_by_list' => 105, 'property' => 49 } }, {#State 88 DEFAULT => -32 }, {#State 89 ACTIONS => { 'ASC_WORD' => 1, 'IDENTIFIER' => 3, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'MINUS' => 50, 'LIKE_WORD' => 8, 'FALSE_WORD' => 9, 'BETWEEN_WORD' => 11, 'TRUE_WORD' => 12, 'IN_WORD' => 13 }, GOTOS => { 'order_by_list' => 106, 'order_by_property' => 51, 'keyword_as_value' => 7, 'property' => 52 } }, {#State 90 DEFAULT => -34 }, {#State 91 DEFAULT => -33 }, {#State 92 DEFAULT => -14 }, {#State 93 DEFAULT => -17 }, {#State 94 ACTIONS => { 'IN_DIVIDER' => 107, 'MINUS' => 103 } }, {#State 95 DEFAULT => -11 }, {#State 96 DEFAULT => -87 }, {#State 97 DEFAULT => -86 }, {#State 98 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'WHITESPACE' => 58, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'SINGLEQUOTE_STRING' => 75 }, DEFAULT => -77, GOTOS => { 'number' => 64, 'keyword_as_value' => 70, 'subsequent_values_list' => 108, 'spaces' => 99, 'subsequent_value_part' => 98 } }, {#State 99 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'WHITESPACE' => 58, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'SINGLEQUOTE_STRING' => 75 }, DEFAULT => -80, GOTOS => { 'number' => 64, 'keyword_as_value' => 70, 'subsequent_values_list' => 109, 'spaces' => 99, 'subsequent_value_part' => 98 } }, {#State 100 DEFAULT => -69 }, {#State 101 ACTIONS => { 'RIGHT_BRACKET' => 110 } }, {#State 102 ACTIONS => { 'SET_SEPARATOR' => 111 }, DEFAULT => -57 }, {#State 103 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'number' => 64, 'keyword_as_value' => 70, 'single_value' => 112, 'subsequent_value_part' => 65 } }, {#State 104 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'between_value' => 114, 'number' => 64, 'keyword_as_value' => 70, 'old_syntax_in_value' => 113, 'single_value' => 94, 'subsequent_value_part' => 65 } }, {#State 105 DEFAULT => -38 }, {#State 106 DEFAULT => -36 }, {#State 107 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'number' => 64, 'keyword_as_value' => 70, 'old_syntax_in_value' => 115, 'single_value' => 116, 'subsequent_value_part' => 65 } }, {#State 108 DEFAULT => -78 }, {#State 109 DEFAULT => -79 }, {#State 110 DEFAULT => -55 }, {#State 111 ACTIONS => { 'WORD' => 61, 'DOUBLEQUOTE_STRING' => 62, 'MINUS' => 63, 'BETWEEN_WORD' => 11, 'REAL' => 67, 'INTEGER' => 68, 'ASC_WORD' => 1, 'IDENTIFIER' => 69, 'DESC_WORD' => 2, 'NOT_WORD' => 6, 'LIKE_WORD' => 8, 'AND' => 73, 'FALSE_WORD' => 9, 'TRUE_WORD' => 12, 'IN_WORD' => 13, 'OR' => 74, 'SINGLEQUOTE_STRING' => 75 }, GOTOS => { 'set_body' => 117, 'number' => 64, 'value' => 102, 'keyword_as_value' => 70, 'single_value' => 72, 'subsequent_value_part' => 65 } }, {#State 112 DEFAULT => -60 }, {#State 113 DEFAULT => -15 }, {#State 114 DEFAULT => -18 }, {#State 115 DEFAULT => -53 }, {#State 116 ACTIONS => { 'IN_DIVIDER' => 107 }, DEFAULT => -54 }, {#State 117 DEFAULT => -56 } ], yyrules => [ [#Rule 0 '$start', 2, undef ], [#Rule 1 'boolexpr', 0, sub #line 10 "BxParser.yp" { [] } ], [#Rule 2 'boolexpr', 1, sub #line 11 "BxParser.yp" { UR::BoolExpr::BxParser->_simplify($_[1]) } ], [#Rule 3 'boolexpr', 3, sub #line 12 "BxParser.yp" { [@{$_[1]}, '-order', $_[3]] } ], [#Rule 4 'boolexpr', 3, sub #line 13 "BxParser.yp" { [@{$_[1]}, '-group', $_[3]] } ], [#Rule 5 'boolexpr', 3, sub #line 14 "BxParser.yp" { [@{$_[1]}, '-limit', $_[3]] } ], [#Rule 6 'boolexpr', 3, sub #line 15 "BxParser.yp" { [@{$_[1]}, '-offset', $_[3]] } ], [#Rule 7 'expr', 1, sub #line 18 "BxParser.yp" { $_[1] } ], [#Rule 8 'expr', 3, sub #line 19 "BxParser.yp" { UR::BoolExpr::BxParser->_and($_[1], $_[3]) } ], [#Rule 9 'expr', 3, sub #line 20 "BxParser.yp" { UR::BoolExpr::BxParser->_or($_[1], $_[3]) } ], [#Rule 10 'expr', 3, sub #line 21 "BxParser.yp" { $_[2] } ], [#Rule 11 'condition', 4, sub #line 24 "BxParser.yp" { [ "$_[1] $_[2]" => $_[4] ] } ], [#Rule 12 'condition', 3, sub #line 25 "BxParser.yp" { [ "$_[1] $_[2]" => $_[3] ] } ], [#Rule 13 'condition', 3, sub #line 26 "BxParser.yp" { [ "$_[1] $_[2]" => $_[3] ] } ], [#Rule 14 'condition', 4, sub #line 27 "BxParser.yp" { [ "$_[1] in" => $_[4] ] } ], [#Rule 15 'condition', 5, sub #line 28 "BxParser.yp" { [ "$_[1] $_[2] in" => $_[5] ] } ], [#Rule 16 'condition', 3, sub #line 29 "BxParser.yp" { [ "$_[1] $_[2]" => $_[3] ] } ], [#Rule 17 'condition', 4, sub #line 30 "BxParser.yp" { [ "$_[1] between" => $_[4] ] } ], [#Rule 18 'condition', 5, sub #line 31 "BxParser.yp" { [ "$_[1] $_[2] between" => $_[5] ] } ], [#Rule 19 'condition', 2, sub #line 32 "BxParser.yp" { [ "$_[1] $_[2]" => 1 ] } ], [#Rule 20 'condition', 2, sub #line 33 "BxParser.yp" { [ "$_[1] $_[2]" => undef ] } ], [#Rule 21 'boolean_op_word', 1, sub #line 36 "BxParser.yp" { $_[1] } ], [#Rule 22 'boolean_op_word', 1, sub #line 37 "BxParser.yp" { $_[1] } ], [#Rule 23 'null_op_word', 1, sub #line 40 "BxParser.yp" { '=' } ], [#Rule 24 'null_op_word', 2, sub #line 41 "BxParser.yp" { "!=" } ], [#Rule 25 'null_op_word', 1, sub #line 42 "BxParser.yp" { '!=' } ], [#Rule 26 'spaces', 1, sub #line 45 "BxParser.yp" { $_[1] } ], [#Rule 27 'optional_spaces', 0, sub #line 48 "BxParser.yp" { undef } ], [#Rule 28 'optional_spaces', 1, sub #line 49 "BxParser.yp" { undef } ], [#Rule 29 'property', 1, sub #line 52 "BxParser.yp" { $_[1] } ], [#Rule 30 'property', 1, sub #line 53 "BxParser.yp" { $_[1] } ], [#Rule 31 'order_by_property', 1, sub #line 56 "BxParser.yp" { $_[1 ] } ], [#Rule 32 'order_by_property', 2, sub #line 57 "BxParser.yp" { '-'.$_[2] } ], [#Rule 33 'order_by_property', 2, sub #line 58 "BxParser.yp" { '-'.$_[1] } ], [#Rule 34 'order_by_property', 2, sub #line 59 "BxParser.yp" { $_[1] } ], [#Rule 35 'order_by_list', 1, sub #line 62 "BxParser.yp" { [ $_[1]] } ], [#Rule 36 'order_by_list', 3, sub #line 63 "BxParser.yp" { [$_[1], @{$_[3]}] } ], [#Rule 37 'group_by_list', 1, sub #line 66 "BxParser.yp" { [ $_[1] ] } ], [#Rule 38 'group_by_list', 3, sub #line 67 "BxParser.yp" { [$_[1], @{$_[3]}] } ], [#Rule 39 'operator', 1, sub #line 70 "BxParser.yp" { $_[1] } ], [#Rule 40 'operator', 2, sub #line 71 "BxParser.yp" { "$_[1] $_[2]" } ], [#Rule 41 'negation', 1, sub #line 74 "BxParser.yp" { 'not' } ], [#Rule 42 'negation', 1, sub #line 75 "BxParser.yp" { 'not' } ], [#Rule 43 'an_operator', 1, sub #line 78 "BxParser.yp" { $_[1] } ], [#Rule 44 'an_operator', 1, sub #line 79 "BxParser.yp" { '=' } ], [#Rule 45 'an_operator', 1, sub #line 80 "BxParser.yp" { '=' } ], [#Rule 46 'like_operator', 1, sub #line 83 "BxParser.yp" { 'like' } ], [#Rule 47 'like_operator', 2, sub #line 84 "BxParser.yp" { "$_[1] like" } ], [#Rule 48 'like_operator', 1, sub #line 85 "BxParser.yp" { 'like' } ], [#Rule 49 'like_operator', 2, sub #line 86 "BxParser.yp" { "$_[1] like" } ], [#Rule 50 'like_value', 1, sub #line 89 "BxParser.yp" { $_[1] =~ m/\%/ ? $_[1] : '%' . $_[1] . '%' } ], [#Rule 51 'in_operator', 1, sub #line 92 "BxParser.yp" { 'in' } ], [#Rule 52 'in_operator', 2, sub #line 93 "BxParser.yp" { "$_[1] in" } ], [#Rule 53 'old_syntax_in_value', 3, sub #line 96 "BxParser.yp" { [ $_[1], @{$_[3]} ] } ], [#Rule 54 'old_syntax_in_value', 3, sub #line 97 "BxParser.yp" { [ $_[1], $_[3] ] } ], [#Rule 55 'set', 3, sub #line 100 "BxParser.yp" { $_[2] } ], [#Rule 56 'set_body', 3, sub #line 103 "BxParser.yp" { [ $_[1], @{$_[3]} ] } ], [#Rule 57 'set_body', 1, sub #line 104 "BxParser.yp" { [ $_[1] ] } ], [#Rule 58 'between_operator', 1, sub #line 107 "BxParser.yp" { 'between' } ], [#Rule 59 'between_operator', 2, sub #line 108 "BxParser.yp" { "$_[1] between" } ], [#Rule 60 'between_value', 3, sub #line 111 "BxParser.yp" { [ $_[1], $_[3] ] } ], [#Rule 61 'keyword_as_value', 1, sub #line 114 "BxParser.yp" { $_[1] } ], [#Rule 62 'keyword_as_value', 1, sub #line 115 "BxParser.yp" { $_[1] } ], [#Rule 63 'keyword_as_value', 1, sub #line 116 "BxParser.yp" { $_[1] } ], [#Rule 64 'keyword_as_value', 1, sub #line 117 "BxParser.yp" { $_[1] } ], [#Rule 65 'keyword_as_value', 1, sub #line 118 "BxParser.yp" { $_[1] } ], [#Rule 66 'keyword_as_value', 1, sub #line 119 "BxParser.yp" { $_[1] } ], [#Rule 67 'keyword_as_value', 1, sub #line 120 "BxParser.yp" { $_[1] } ], [#Rule 68 'keyword_as_value', 1, sub #line 121 "BxParser.yp" { $_[1] } ], [#Rule 69 'value', 2, sub #line 124 "BxParser.yp" { $_[1].$_[2] } ], [#Rule 70 'value', 1, sub #line 125 "BxParser.yp" { $_[1] } ], [#Rule 71 'subsequent_value_part', 1, sub #line 128 "BxParser.yp" { $_[1] } ], [#Rule 72 'subsequent_value_part', 1, sub #line 129 "BxParser.yp" { $_[1] } ], [#Rule 73 'subsequent_value_part', 1, sub #line 130 "BxParser.yp" { $_[1] } ], [#Rule 74 'subsequent_value_part', 1, sub #line 131 "BxParser.yp" { ($_[1] =~ m/^"(.*?)"$/)[0]; } ], [#Rule 75 'subsequent_value_part', 1, sub #line 132 "BxParser.yp" { ($_[1] =~ m/^'(.*?)'$/)[0]; } ], [#Rule 76 'subsequent_value_part', 1, sub #line 133 "BxParser.yp" { $_[1] } ], [#Rule 77 'subsequent_values_list', 1, sub #line 136 "BxParser.yp" { $_[1] } ], [#Rule 78 'subsequent_values_list', 2, sub #line 137 "BxParser.yp" { $_[1].$_[2] } ], [#Rule 79 'subsequent_values_list', 2, sub #line 138 "BxParser.yp" { $_[1].$_[2] } ], [#Rule 80 'subsequent_values_list', 1, sub #line 139 "BxParser.yp" { '' } ], [#Rule 81 'single_value', 1, sub #line 142 "BxParser.yp" { $_[1] } ], [#Rule 82 'single_value', 1, sub #line 143 "BxParser.yp" { $_[1] } ], [#Rule 83 'single_value', 1, sub #line 144 "BxParser.yp" { $_[1] } ], [#Rule 84 'number', 1, sub #line 148 "BxParser.yp" { $_[1] + 0 } ], [#Rule 85 'number', 1, sub #line 149 "BxParser.yp" { $_[1] + 0 } ], [#Rule 86 'number', 2, sub #line 150 "BxParser.yp" { 0 - $_[2] } ], [#Rule 87 'number', 2, sub #line 151 "BxParser.yp" { 0 - $_[2] } ] ], @_); bless($self,$class); } #line 154 "BxParser.yp" package UR::BoolExpr::BxParser; use strict; use warnings; sub _error { my @expect = $_[0]->YYExpect; my $tok = $_[0]->YYData->{INPUT}; my $match = $_[0]->YYData->{MATCH}; my $string = $_[0]->YYData->{STRING}; my $err = qq(Can't parse expression "$string"\n Syntax error near token $tok '$match'); my $rem = $_[0]->YYData->{REMAINING}; $err .= ", remaining text: '$rem'" if $rem; $err .= "\nExpected one of: " . join(", ", @expect) . "\n"; Carp::croak($err); } my %token_states = ( 'DEFAULT' => [ WHITESPACE => qr{\s+}, AND => [ qr{and}i, 'DEFAULT'], OR => [ qr{or}i, 'DEFAULT' ], BETWEEN_WORD => qr{between}, LIKE_WORD => qr{like}, IN_WORD => qr{in}, NOT_WORD => qr{not}, DESC_WORD => qr{desc}, ASC_WORD => qr{asc}, TRUE_WORD => qr{true}, FALSE_WORD => qr{false}, LIMIT => qr{limit}, OFFSET => qr{offset}, IDENTIFIER => qr{[a-zA-Z_][a-zA-Z0-9_.]*}, MINUS => qr{-}, INTEGER => qr{\d+}, REAL => qr{\d*\.\d+|\d+\.\d*}, WORD => qr{[%\+\.\/\w][\+\-\.%\w\/]*}, # also allow / for pathnames, - for hyphenated names, % for like wildcards DOUBLEQUOTE_STRING => qr{"(?:\\.|[^"])*"}, SINGLEQUOTE_STRING => qr{'(?:\\.|[^'])*'}, LEFT_PAREN => [ qr{\(}, 'DEFAULT' ], RIGHT_PAREN => [ qr{\)}, 'DEFAULT' ], LEFT_BRACKET => [ qr{\[}, 'set_contents'], RIGHT_BRACKET => [qr{\]}, 'DEFAULT' ], NOT_BANG => qr{!}, EQUAL_SIGN => [ qr{=}, 'dont_gobble_spaces' ], DOUBLEEQUAL_SIGN => [ qr{=>}, 'dont_gobble_spaces' ], OPERATORS => [ qr{<=|>=|<|>}, 'dont_gobble_spaces' ], AND => [ qr{,}, 'DEFAULT' ], COLON => [ qr{:}, 'after_colon_value' ], TILDE => qr{~}, ORDER_BY => qr{order by}, GROUP_BY => qr{group by}, IS_NULL => qr{is null|is undef}, IS_NOT_NULL => qr{is not null|is not undef}, ], 'set_contents' => [ SET_SEPARATOR => qr{,}, # Depending on state, can be either AND or SET_SEPARATOR WORD => qr{[%\+\.\w\:][\+\.\:%\w]*}, # also allow / for pathnames, - for hyphenated names, % for like wildcards RIGHT_BRACKET => [qr{\]}, 'DEFAULT' ], ], 'after_colon_value' => [ INTEGER => qr{\d+}, REAL => qr{\d*\.\d+|\d+\.\d*}, IN_DIVIDER => qr{\/}, #WORD => qr{\w+}, # Override WORD in DEFAULT to disallow / WORD => qr{[%\+\.\w\:][\+\.\:%\w]*}, # Override WORD in DEFAULT to disallow / DOUBLEQUOTE_STRING => qr{"(?:\\.|[^"])*"}, SINGLEQUOTE_STRING => qr{'(?:\\.|[^'])*'}, WHITESPACE => [qr{\s+}, 'DEFAULT'], ], 'dont_gobble_spaces' => [ AND => [ qr{and}, 'DEFAULT'], OR => [ qr{or}, 'DEFAULT' ], LIMIT => [qr{limit}, 'DEFAULT'], OFFSET => [qr{offset}, 'DEFAULT'], INTEGER => qr{\d+}, REAL => qr{\d*\.\d+|\d+\.\d*}, WORD => qr{[%\+\.\/\w][\+\-\.\:%\w\/]*}, # also allow / for pathnames, - for hyphenated names, % for like wildcards ORDER_BY => [qr{order by}, 'DEFAULT'], GROUP_BY => [qr{group by}, 'DEFAULT'], ], ); sub parse { my $string = shift; my %params = @_; my $debug = $params{'tokdebug'}; my $yydebug = $params{'yydebug'} || 0; print "\nStarting parse for string $string\n" if $debug; my $parser = UR::BoolExpr::BxParser->new(); $parser->YYData->{STRING} = $string; my $parser_state = 'DEFAULT'; my $get_next_token = sub { if (length($string) == 0) { print "String is empty, we're done!\n" if $debug; return (undef, ''); } GET_NEXT_TOKEN: foreach (1) { my $longest = 0; my $longest_token = ''; my $longest_match = ''; for my $token_list ( $parser_state, 'DEFAULT' ) { print "\nTrying tokens for state $token_list...\n" if $debug; my $tokens = $token_states{$token_list}; for(my $i = 0; $i < @$tokens; $i += 2) { my($tok, $re) = @$tokens[$i, $i+1]; print "Trying token $tok... " if $debug; my($regex,$next_parser_state); if (ref($re) eq 'ARRAY') { ($regex,$next_parser_state) = @$re; } else { $regex = $re; } if ($string =~ m/^($regex)/) { print "Matched >>$1<<" if $debug; my $match_len = length($1); if ($match_len > $longest) { print "\n ** It's now the longest" if $debug; $longest = $match_len; $longest_token = $tok; $longest_match = $1; if ($next_parser_state) { $parser_state = $next_parser_state; } } } print "\n" if $debug; } $string = substr($string, $longest); print "Consuming up to char pos $longest chars, string is now >>$string<<\n" if $debug; if ($longest_token eq 'WHITESPACE' and $parser_state ne 'dont_gobble_spaces') { print "Redoing token extraction after whitespace\n" if $debug; redo GET_NEXT_TOKEN; } $parser->YYData->{REMAINING} = $string; if ($longest) { print "Returning token $longest_token, match $longest_match\n next state is named $parser_state\n" if $debug; $parser->YYData->{INPUT} = $longest_token; $parser->YYData->{MATCH} = $longest_match; return ($longest_token, $longest_match); } last if $token_list eq 'DEFAULT'; # avoid going over it twice if $parser_state is DEFAULT } } print "Didn't match anything, done!\n" if $debug; return (undef, ''); # Didn't match anything }; return ( $parser->YYParse( yylex => $get_next_token, yyerror => \&_error, yydebug => $yydebug), \$string, ); } # Used by the top-level expr production to turn an or-type parse tree with # only a single AND condition into a simple AND-type tree (1-level arrayref). # Or to add the '-or' to the front of a real OR-type tree so it can be passed # directly to UR::BoolExpr::resolve() sub _simplify { my($class, $expr) = @_; if (ref($expr->[0])) { if (@$expr == 1) { # An or-type parse tree, but with only one AND subrule - use as a simple and-type rule $expr = $expr->[0]; } else { $expr = ['-or', $expr]; # an or-type parse tree with multiple subrules } } return $expr; } # Handles the case for "expr AND expr" where one or both exprs can be an # OR-type expr. In that case, it distributes the AND exprs among all the # OR conditions. For example: # (a=1 or b=2) and (c=3 or d=4) # is the same as # (a=1 and c=3) or (a=1 and d=4) or (b=2 and c=3) or (b=2 and d=4) # This is necessary because the BoolExpr resolver can only handle 1-level deep # AND-type rules, or a 1-level deep OR-type rule composed of any number of # 1-level deep AND-type rules sub _and { my($class,$left, $right) = @_; # force them to be [[ "property operator" => value]] instead of just [ "property operator" => value ] $left = [ $left ] unless (ref($left->[0])); $right = [ $right ] unless (ref($right->[0])); my @and; foreach my $left_subexpr ( @$left ) { foreach my $right_subexpr (@$right) { push @and, [@$left_subexpr, @$right_subexpr]; } } \@and; } sub _or { my($class,$left, $right) = @_; # force them to be [[ "property operator" => value]] instead of just [ "property operator" => value ] $left = [ $left ] unless (ref($left->[0])); $right = [ $right ] unless (ref($right->[0])); [ @$left, @$right ]; } 1; 1;