diff --git a/lib/oga/xpath/parser.y b/lib/oga/xpath/parser.y index 900679c..af8f440 100644 --- a/lib/oga/xpath/parser.y +++ b/lib/oga/xpath/parser.y @@ -6,12 +6,13 @@ class Oga::XPath::Parser token T_AXIS T_COLON T_COMMA T_FLOAT T_INT T_IDENT token T_LBRACK T_RBRACK T_LPAREN T_RPAREN T_SLASH T_STRING token T_PIPE T_AND T_OR T_ADD T_DIV T_MOD T_EQ T_NEQ T_LT T_GT T_LTE T_GTE +token T_SUB T_MUL options no_result_var prechigh - left T_EQ T_AXIS - right T_OR + left T_PIPE T_MOD T_DIV T_MUL T_SUB T_ADD + left T_GT T_GTE T_LT T_LTE T_NEQ T_EQ T_AND T_OR preclow rule @@ -57,15 +58,32 @@ rule ; operator - : expression T_EQ expression { s(:eq, val[0], val[2]) } - | expression T_OR expression { s(:or, val[0], val[2]) } + : expression T_PIPE expression { s(:pipe, val[0], val[2]) } + | expression T_AND expression { s(:and, val[0], val[2]) } + | expression T_OR expression { s(:or, val[0], val[2]) } + | expression T_ADD expression { s(:add, val[0], val[2]) } + | expression T_DIV expression { s(:div, val[0], val[2]) } + | expression T_MOD expression { s(:mod, val[0], val[2]) } + | expression T_EQ expression { s(:eq, val[0], val[2]) } + | expression T_NEQ expression { s(:neq, val[0], val[2]) } + | expression T_LT expression { s(:lt, val[0], val[2]) } + | expression T_GT expression { s(:gt, val[0], val[2]) } + | expression T_LTE expression { s(:lte, val[0], val[2]) } + | expression T_GTE expression { s(:gte, val[0], val[2]) } + | expression T_MUL expression { s(:mul, val[0], val[2]) } + | expression T_SUB expression { s(:sub, val[0], val[2]) } ; axis - : T_AXIS expression { s(:axis, val[0], val[1]) } + : T_AXIS axis_value { s(:axis, val[0], val[1]) } | T_AXIS { s(:axis, val[0]) } ; + axis_value + : node_test + | call + ; + call : T_IDENT T_LPAREN T_RPAREN { s(:call, val[0]) } | T_IDENT T_LPAREN call_args T_RPAREN { s(:call, val[0], *val[2]) } diff --git a/spec/oga/xpath/parser/operator_precedence_spec.rb b/spec/oga/xpath/parser/operator_precedence_spec.rb new file mode 100644 index 0000000..69cd6ce --- /dev/null +++ b/spec/oga/xpath/parser/operator_precedence_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' + +describe Oga::XPath::Parser do + context 'operator precedence' do + example 'parse "A or B or C"' do + parse_xpath('A or B or C').should == s( + :path, + s( + :or, + s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A and B and C"' do + parse_xpath('A and B and C').should == s( + :path, + s( + :and, + s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A = B = C"' do + parse_xpath('A = B = C').should == s( + :path, + s( + :eq, + s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A != B != C"' do + parse_xpath('A != B != C').should == s( + :path, + s( + :neq, + s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A <= B <= C"' do + parse_xpath('A <= B <= C').should == s( + :path, + s( + :lte, + s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A < B < C"' do + parse_xpath('A < B < C').should == s( + :path, + s( + :lt, + s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A >= B >= C"' do + parse_xpath('A >= B >= C').should == s( + :path, + s( + :gte, + s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A > B > C"' do + parse_xpath('A > B > C').should == s( + :path, + s( + :gt, + s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A or B and C"' do + parse_xpath('A or B and C').should == s( + :path, + s( + :and, + s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A and B = C"' do + parse_xpath('A and B = C').should == s( + :path, + s( + :eq, + s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A = B < C"' do + parse_xpath('A = B < C').should == s( + :path, + s( + :lt, + s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + + example 'parse "A < B | C"' do + parse_xpath('A < B | C').should == s( + :path, + s( + :lt, + s(:test, nil, 'A'), + s(:pipe, s(:test, nil, 'B'), s(:test, nil, 'C')) + ) + ) + end + + example 'parse "A > B or C"' do + parse_xpath('A > B or C').should == s( + :path, + s( + :or, + s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')), + s(:test, nil, 'C') + ) + ) + end + end +end diff --git a/spec/oga/xpath/parser/operators_spec.rb b/spec/oga/xpath/parser/operators_spec.rb new file mode 100644 index 0000000..4ec17bd --- /dev/null +++ b/spec/oga/xpath/parser/operators_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe Oga::XPath::Parser do + context 'operators' do + example 'parse the pipe operator' do + parse_xpath('A | B').should == s( + :path, + s(:pipe, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the and operator' do + parse_xpath('A and B').should == s( + :path, + s(:and, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the or operator' do + parse_xpath('A or B').should == s( + :path, + s(:or, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the plus operator' do + parse_xpath('A + B').should == s( + :path, + s(:add, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the div operator' do + parse_xpath('A div B').should == s( + :path, + s(:div, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the mod operator' do + parse_xpath('A mod B').should == s( + :path, + s(:mod, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the equals operator' do + parse_xpath('A = B').should == s( + :path, + s(:eq, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the not-equals operator' do + parse_xpath('A != B').should == s( + :path, + s(:neq, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the lower-than operator' do + parse_xpath('A < B').should == s( + :path, + s(:lt, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the greater-than operator' do + parse_xpath('A > B').should == s( + :path, + s(:gt, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the lower-or-equal operator' do + parse_xpath('A <= B').should == s( + :path, + s(:lte, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the greater-or-equal operator' do + parse_xpath('A >= B').should == s( + :path, + s(:gte, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the mul operator' do + parse_xpath('A * B').should == s( + :path, + s(:mul, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + + example 'parse the subtraction operator' do + parse_xpath('A - B').should == s( + :path, + s(:sub, s(:test, nil, 'A'), s(:test, nil, 'B')) + ) + end + end +end diff --git a/spec/oga/xpath/parser/predicates_spec.rb b/spec/oga/xpath/parser/predicates_spec.rb index ee1da97..1e0edf2 100644 --- a/spec/oga/xpath/parser/predicates_spec.rb +++ b/spec/oga/xpath/parser/predicates_spec.rb @@ -3,21 +3,18 @@ require 'spec_helper' describe Oga::XPath::Parser do context 'predicates' do example 'parse a single predicate' do - parse_xpath('/foo[@class="bar"]').should == s( - :absolute, + parse_xpath('foo[@class="bar"]').should == s( + :path, s( - :path, + :test, + nil, + 'foo', s( - :test, - nil, - 'foo', + :path, s( - :path, - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'bar') - ) + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'bar') ) ) ) @@ -25,28 +22,25 @@ describe Oga::XPath::Parser do end example 'parse a predicate using the or operator' do - parse_xpath('/foo[@class="bar" or @class="baz"]').should == s( - :absolute, + parse_xpath('foo[@class="bar" or @class="baz"]').should == s( + :path, s( - :path, + :test, + nil, + 'foo', s( - :test, - nil, - 'foo', + :path, s( - :path, + :or, s( - :or, - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'bar') - ), - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'class')), - s(:string, 'baz') - ) + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'bar') + ), + s( + :eq, + s(:axis, 'attribute', s(:test, nil, 'class')), + s(:string, 'baz') ) ) )