diff --git a/lib/oga.rb b/lib/oga.rb index 5090321..239ac83 100644 --- a/lib/oga.rb +++ b/lib/oga.rb @@ -1,5 +1,3 @@ -gem 'racc' - require 'ast' require 'set' require 'stringio' diff --git a/lib/oga/css/parser.y b/lib/oga/css/parser.y deleted file mode 100644 index 1fdd105..0000000 --- a/lib/oga/css/parser.y +++ /dev/null @@ -1,604 +0,0 @@ -## -# AST parser for CSS expressions. -# -# This parser does _not_ build a CSS specific AST, instead it directly produces -# an XPath AST. This removes the need to transform the AST or generate -# corresponding XPath expressions as a String. -# -# Similar to {Oga::XPath::Parser} this parser only takes String instances as -# input. -# -class Oga::CSS::Parser - -token T_IDENT T_PIPE T_LBRACK T_RBRACK T_COLON T_SPACE T_LPAREN T_RPAREN T_MINUS -token T_EQ T_SPACE_IN T_STARTS_WITH T_ENDS_WITH T_IN T_HYPHEN_IN -token T_GREATER T_TILDE T_PLUS -token T_NTH T_INT T_STRING T_ODD T_EVEN T_DOT T_HASH - -options no_result_var - -prechigh - left T_COLON T_HASH T_DOT - left T_GREATER T_TILDE T_PLUS -preclow - -rule - css - : selectors { val[0] } - | /* none */ { nil } - ; - - selectors - : selector - { - # a single "+ y" selector - if val[0].is_a?(Array) - return s(:path, *val[0]) - else - return val[0] - end - } - | selectors_ { s(:path, *val[0].flatten) } - ; - - selectors_ - : selectors_ T_SPACE selector { val[0] << val[2] } - | selector T_SPACE selector { [val[0], val[2]] } - ; - - selector - # .foo, :bar, etc - : predicates - { - s(:predicate, s(:axis, 'descendant', on_test(nil, '*')), val[0]) - } - - # foo - | descendant_or_self - - # foo.bar - | descendant_or_self predicates { s(:predicate, val[0], val[1]) } - - # > foo - | axis - - # > foo.bar - | axis predicates { s(:predicate, val[0], val[1]) } - ; - - descendant_or_self - : node_test { s(:axis, 'descendant', val[0]) } - ; - - axis - # > foo - : T_GREATER axis_selector - { - s(:axis, 'child', val[1]) - } - - # ~ foo - | T_TILDE axis_selector - { - s(:axis, 'following-sibling', val[1]) - } - - # + foo - | T_PLUS axis_selector - { - [ - s( - :predicate, - s(:axis, 'following-sibling', on_test(nil, '*')), - s(:int, 1) - ), - s(:axis, 'self', val[1]) - ] - } - ; - - axis_selector - | node_test - | axis - ; - - node_test - # foo - : node_name { on_test(*val[0]) } - ; - - node_name - # foo - : T_IDENT { [nil, val[0]] } - - # ns|foo - | T_IDENT T_PIPE T_IDENT { [val[0], val[2]] } - ; - - predicates - : predicates predicate { s(:and, val[0], val[1]) } - | predicate - ; - - predicate - : class - | id - | pseudo_class - | attribute_predicate - ; - - attribute_predicate - : T_LBRACK attribute_predicate_members T_RBRACK { val[1] } - ; - - attribute_predicate_members - : attribute - | operator - ; - - attribute - : node_name { s(:axis, 'attribute', on_test(*val[0])) } - ; - - # The AST of these operators is mostly based on what - # `Nokogiri::CSS.xpath_for('...')` returns. - operator - # a="b" - : attribute T_EQ string - { - s(:eq, val[0], val[2]) - } - - # a~="b" - | attribute T_SPACE_IN string - { - s( - :call, - 'contains', - s(:call, 'concat', s(:string, ' '), val[0], s(:string, ' ')), - s(:call, 'concat', s(:string, ' '), val[2], s(:string, ' ')) - ) - } - - # a^="b" - | attribute T_STARTS_WITH string - { - s(:call, 'starts-with', val[0], val[2]) - } - - # a$="b" - | attribute T_ENDS_WITH string - { - s( - :eq, - s( - :call, - 'substring', - val[0], - s( - :add, - s( - :sub, - s(:call, 'string-length', val[0]), - s(:call, 'string-length', val[2]) - ), - s(:int, 1) - ), - s(:call, 'string-length', val[2]) - ), - val[2] - ) - } - - # a*="b" - | attribute T_IN string - { - s(:call, 'contains', val[0], val[2]) - } - - # a|="b" - | attribute T_HYPHEN_IN string - { - s( - :or, - s(:eq, val[0], val[2]), - s( - :call, - 'starts-with', - val[0], - s(:call, 'concat', val[2], s(:string, '-')) - ) - ) - } - ; - - class - : T_DOT T_IDENT - { - axis = s(:axis, 'attribute', s(:test, nil, 'class')) - - s( - :call, - 'contains', - s(:call, 'concat', s(:string, ' '), axis, s(:string, ' ')), - s(:string, " #{val[1]} ") - ) - } - ; - - id - : T_HASH T_IDENT - { - s( - :eq, - s(:axis, 'attribute', s(:test, nil, 'id')), - s(:string, val[1]) - ) - } - ; - - pseudo_class - # :root - : pseudo_name { on_pseudo_class(val[0]) } - - # :nth-child(2) - | pseudo_name pseudo_args { on_pseudo_class(val[0], val[1]) } - ; - - pseudo_name - : T_COLON T_IDENT { val[1] } - ; - - pseudo_args - : T_LPAREN pseudo_arg T_RPAREN { val[1] } - ; - - pseudo_arg - : integer - | odd - | even - | nth - | selector - ; - - string - : T_STRING { s(:string, val[0]) } - ; - - integer - : T_INT { s(:int, val[0].to_i) } - ; - - # These AST nodes are _not_ the final AST nodes. Instead they are used by - # on_pseudo_class_nth_child() to determine what the final AST should be. - - nth - # n - : T_NTH { s(:nth, s(:int, 1)) } - - # n+2 - | T_NTH integer { s(:nth, s(:int, 1), val[1]) } - - # -n - | T_MINUS T_NTH { s(:nth, s(:int, 1)) } - - # -n+2, -n-2 - | T_MINUS T_NTH integer { s(:nth, s(:int, -1), val[2]) } - - # 2n - | integer T_NTH { s(:nth, val[0]) } - - # 2n+1, 2n-1 - | integer T_NTH integer - { - a = val[0] - b = val[2] - - # 2n-1 gets turned into 2n+1 - if b.children[0] < 0 - b = s(:int, a.children[0] - (b.children[0] % a.children[0])) - end - - s(:nth, a, b) - } - ; - - odd - : T_ODD { s(:nth, s(:int, 2), s(:int, 1)) } - ; - - even - : T_EVEN { s(:nth, s(:int, 2)) } - ; -end - ----- inner - ## - # @param [String] data The input to parse. - # - def initialize(data) - @lexer = Lexer.new(data) - end - - ## - # Resets the internal state of the parser. - # - def reset - @current_element = nil - end - - ## - # @param [Symbol] type - # @param [Array] children - # @return [AST::Node] - # - def s(type, *children) - return AST::Node.new(type, children) - end - - ## - # Yields the next token from the lexer. - # - # @yieldparam [Array] - # - def yield_next_token - @lexer.advance do |*args| - yield args - end - - yield [false, false] - end - - ## - # Returns the node test for the current element. - # - # @return [AST::Node] - # - def current_element - return @current_element ||= s(:test, nil, '*') - end - - ## - # Parses the input and returns the corresponding AST. - # - # @example - # parser = Oga::CSS::Parser.new('foo.bar') - # ast = parser.parse - # - # @return [AST::Node] - # - def parse - reset - - ast = yyparse(self, :yield_next_token) - - return ast - end - - ## - # Generates the AST for a node test. - # - # @param [String] namespace - # @param [String] name - # @return [AST::Node] - # - def on_test(namespace, name) - @current_element = s(:test, namespace, name) - - return @current_element - end - - ## - # @param [String] name - # @param [AST::Node] arg - # @return [AST::Node] - # - def on_pseudo_class(name, arg = nil) - handler = "on_pseudo_class_#{name.gsub('-', '_')}" - - return arg ? send(handler, arg) : send(handler) - end - - ## - # Generates the AST for the `root` pseudo class. - # - # @return [AST::Node] - # - def on_pseudo_class_root - return s(:call, 'not', s(:axis, 'parent', s(:test, nil, '*'))) - end - - ## - # Generates the AST for the `nth-child` pseudo class. - # - # @param [AST::Node] arg - # @return [AST::Node] - # - def on_pseudo_class_nth_child(arg) - return generate_nth_child('preceding-sibling', arg) - end - - ## - # Generates the AST for the `nth-last-child` pseudo class. - # - # @param [AST::Node] arg - # @return [AST::Node] - # - def on_pseudo_class_nth_last_child(arg) - return generate_nth_child('following-sibling', arg) - end - - ## - # Generates the AST for the `nth-of-type` pseudo class. - # - # @param [AST::Node] arg - # @return [AST::Node] - # - def on_pseudo_class_nth_of_type(arg) - return generate_nth_child('preceding-sibling', arg, current_element) - end - - ## - # Generates the AST for the `nth-last-of-type` pseudo class. - # - # @param [AST::Node] arg - # @return [AST::Node] - # - def on_pseudo_class_nth_last_of_type(arg) - return generate_nth_child('following-sibling', arg, current_element) - end - - ## - # Generates the AST for the `:first-child` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_first_child - return generate_no_siblings('preceding-sibling') - end - - ## - # Generates the AST for the `:last-child` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_last_child - return generate_no_siblings('following-sibling') - end - - ## - # Generates the AST for the `:first-of-type` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_first_of_type - return generate_no_siblings('preceding-sibling', current_element) - end - - ## - # Generates the AST for the `:last-of-type` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_last_of_type - return generate_no_siblings('following-sibling', current_element) - end - - ## - # Generates the AST for the `:only-child` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_only_child - return s(:and, on_pseudo_class_first_child, on_pseudo_class_last_child) - end - - ## - # Generates the AST for the `:only-of-type` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_only_of_type - return s(:and, on_pseudo_class_first_of_type, on_pseudo_class_last_of_type) - end - - ## - # Generates the AST for the `:empty` selector. - # - # @return [AST::Node] - # - def on_pseudo_class_empty - return s(:call, 'not', s(:axis, 'child', s(:type_test, 'node'))) - end - - private - - ## - # @param [String] count_axis - # @param [AST::Node] arg - # @param [AST::Node] count_test - # @return [AST::Node] - # - def generate_nth_child(count_axis, arg, count_test = s(:test, nil, '*')) - count_call = s(:call, 'count', s(:axis, count_axis, count_test)) - - # literal 2, 4, etc - if int_node?(arg) - node = s(:eq, count_call, s(:int, arg.children[0] - 1)) - else - step, offset = *arg - before_count = s(:add, count_call, s(:int, 1)) - compare = step_comparison(step) - - # 2n+2, 2n-4, etc - if offset - mod_val = step_modulo_value(step) - node = s( - :and, - s(compare, before_count, offset), - s(:eq, s(:mod, s(:sub, before_count, offset), mod_val), s(:int, 0)) - ) - - # 2n, n, -2n - else - node = s(:eq, s(:mod, before_count, step), s(:int, 0)) - end - end - - return node - end - - ## - # @param [String] axis - # @param [AST::Node] test - # @return [AST::Node] - # - def generate_no_siblings(axis, test = s(:test, nil, '*')) - return s(:eq, s(:call, 'count', s(:axis, axis, test)), s(:int, 0)) - end - - ## - # @param [AST::Node] node - # @return [TrueClass|FalseClass] - # - def int_node?(node) - return node.type == :int - end - - ## - # @param [AST::Node] node - # @return [TrueClass|FalseClass] - # - def non_positive_number?(node) - return node.children[0] <= 0 - end - - ## - # @param [AST::Node] node - # @return [Symbol] - # - def step_comparison(node) - return node.children[0] >= 0 ? :gte : :lte - end - - ## - # @param [AST::Node] step - # @return [AST::Node] - # - def step_modulo_value(step) - # -2n - if step and non_positive_number?(step) - mod_val = s(:int, -step.children[0]) - - # 2n - elsif step - mod_val = step - - else - mod_val = s(:int, 1) - end - - return mod_val - end - -# vim: set ft=racc: diff --git a/oga.gemspec b/oga.gemspec index c0f4894..5c7bd6f 100644 --- a/oga.gemspec +++ b/oga.gemspec @@ -32,7 +32,6 @@ Gem::Specification.new do |s| s.has_rdoc = 'yard' s.required_ruby_version = '>= 1.9.3' - s.add_dependency 'racc', ['~> 1.4', '>= 1.4.12'] s.add_dependency 'ast' s.add_dependency 'ruby-ll', '~> 2.1' diff --git a/task/parser.rake b/task/parser.rake index f5ebe59..8045c31 100644 --- a/task/parser.rake +++ b/task/parser.rake @@ -1,7 +1,3 @@ -rule '.rb' => '.y' do |task| - sh "racc -l -o #{task.name} #{task.source}" -end - rule '.rb' => '.rll' do |task| sh "ruby-ll #{task.source} -o #{task.name}" end