synvert-core 1.2.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c530d34ca0e2db5d9a295cb0cb48c375d13760f09290624805d742328a49103
4
- data.tar.gz: 87cc4ef7ab9116bac2cbed78b9f71ab8d4ffa30281554d10319527a9aab94573
3
+ metadata.gz: 741ca9c2f6f29e5bbafbc5b63d94b3cd9a56aa20e8a00f3f70830b73b1fb697e
4
+ data.tar.gz: a8bbf6e0c48a4af42578abb20807f3d86359fc069dc85ff90e12cf011f076998
5
5
  SHA512:
6
- metadata.gz: 1b864e1e350d3467fe12d2c5e26a5c5a0b685752e21802e3773edfbbb8fca8f7dec60fb545475a630a3f00c09359c66859b228cf7cb3d19aa6bf044df4848408
7
- data.tar.gz: c8c5641b85080f03307fed835c05132488d140bb19b694c2b5b0958cb3d6377785ef1447c8b7fec59e7529f2ec610f6c78d72357b60f5fe4285d43d5071750fb
6
+ metadata.gz: 7fcadf632c863b1f3ba410b65f63c741b594255567f337ebc86c51ae45e681484653b45eec01fc5c3e3a4ea170a6bd38e899bdf22f65b2e980d58cac8c11cfcd
7
+ data.tar.gz: 80ebe8f07ef9679c6921233d52b46979a6bfb895e4b63b974672dec193ef52ce08c40454d2a92fd509f12b8f3567065bb7d2dbee33af7bca37a96246e6373381
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.4.0 (2022-05-21)
4
+
5
+ * Drop support `:first-child` and `:last-child`
6
+ * Redefine goto scope in nql
7
+ * Fix shift/reduce conflict
8
+
9
+ ## 1.3.1 (2022-05-14)
10
+
11
+ * Add `add_comma` option to remove extra comma
12
+
13
+ ## 1.3.0 (2022-05-12)
14
+
15
+ * Support `*=`, `^=` and `$=` operators
16
+ * Simplify RELATIONSHIP parser
17
+ * Rewrite compiler, let the selector to query nodes
18
+
3
19
  ## 1.2.1 (2022-05-01)
4
20
 
5
21
  * Selector always after a node type in NQL
data/Gemfile CHANGED
@@ -4,3 +4,12 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in synvert.gemspec
6
6
  gemspec
7
+
8
+ gem "guard"
9
+ gem "guard-rspec"
10
+ gem "guard-rake"
11
+ gem "oedipus_lex"
12
+ gem "racc"
13
+ gem "rake"
14
+ gem "rspec"
15
+ gem "rspec-mocks"
@@ -7,7 +7,7 @@ module Synvert::Core::NodeQuery::Compiler
7
7
  # @param key [String] the key
8
8
  # @param value the value can be any class implement {Synvert::Core::NodeQuery::Compiler::Comparable}
9
9
  # @param operator [Symbol] the operator
10
- def initialize(key:, value:, operator: :==)
10
+ def initialize(key:, value:, operator: '==')
11
11
  @key = key
12
12
  @value = value
13
13
  @operator = operator
@@ -23,25 +23,13 @@ module Synvert::Core::NodeQuery::Compiler
23
23
 
24
24
  def to_s
25
25
  case @operator
26
- when :!=
27
- "#{@key}!=#{@value}"
28
- when :=~
29
- "#{@key}=~#{@value}"
30
- when :!~
31
- "#{@key}!~#{@value}"
32
- when :>
33
- "#{@key}>#{@value}"
34
- when :>=
35
- "#{@key}>=#{@value}"
36
- when :<
37
- "#{@key}<#{@value}"
38
- when :<=
39
- "#{@key}<=#{@value}"
40
- when :in
26
+ when '^=', '$=', '*=', '!=', '=~', '!~', '>=', '>', '<=', '<'
27
+ "#{@key}#{@operator}#{@value}"
28
+ when 'in'
41
29
  "#{@key} in (#{@value})"
42
- when :not_in
30
+ when 'not_in'
43
31
  "#{@key} not in (#{@value})"
44
- when :includes
32
+ when 'includes'
45
33
  "#{@key} includes #{@value}"
46
34
  else
47
35
  "#{@key}=#{@value}"
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synvert::Core::NodeQuery::Compiler
4
+ # BasicSelector used to match nodes, it combines by node type and/or attribute list.
5
+ class BasicSelector
6
+ # Initialize a BasicSelector.
7
+ # @param node_type [String] the node type
8
+ # @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
9
+ def initialize(node_type:, attribute_list: nil)
10
+ @node_type = node_type
11
+ @attribute_list = attribute_list
12
+ end
13
+
14
+ # Check if node matches the selector.
15
+ # @param node [Parser::AST::Node] the node
16
+ def match?(node, _operator = '==')
17
+ return false unless node
18
+
19
+ @node_type.to_sym == node.type && (!@attribute_list || @attribute_list.match?(node))
20
+ end
21
+
22
+ def to_s
23
+ result = [".#{@node_type}"]
24
+ result << @attribute_list.to_s if @attribute_list
25
+ result.join('')
26
+ end
27
+ end
28
+ end
@@ -3,52 +3,59 @@
3
3
  module Synvert::Core::NodeQuery::Compiler
4
4
  # Compare acutal value with expected value.
5
5
  module Comparable
6
- SIMPLE_VALID_OPERATORS = [:==, :!=, :includes]
7
- NUMBER_VALID_OPERATORS = [:==, :!=, :>, :>=, :<, :<=, :includes]
8
- ARRAY_VALID_OPERATORS = [:==, :!=, :in, :not_in]
9
- REGEXP_VALID_OPERATORS = [:=~, :!~]
6
+ SIMPLE_VALID_OPERATORS = ['==', '!=', 'includes']
7
+ STRING_VALID_OPERATORS = ['==', '!=', '^=', '$=', '*=', 'includes']
8
+ NUMBER_VALID_OPERATORS = ['==', '!=', '>', '>=', '<', '<=', 'includes']
9
+ ARRAY_VALID_OPERATORS = ['==', '!=', 'in', 'not_in']
10
+ REGEXP_VALID_OPERATORS = ['=~', '!~']
10
11
 
11
12
  # Check if the actual value matches the expected value.
12
13
  #
13
14
  # @param node [Parser::AST::Node] node to calculate actual value
14
- # @param operator [Symbol] operator to compare with expected value, operator can be <code>:==</code>, <code>:!=</code>, <code>:></code>, <code>:>=</code>, <code>:<</code>, <code>:<=</code>, <code>:includes</code>, <code>:in</code>, <code>:not_in</code>, <code>:=~</code>, <code>!~</code>
15
+ # @param operator [Symbol] operator to compare with expected value, operator can be <code>'=='</code>, <code>'!='</code>, <code>'>'</code>, <code>'>='</code>, <code>'<'</code>, <code>'<='</code>, <code>'includes'</code>, <code>'in'</code>, <code>'not_in'</code>, <code>'=~'</code>, <code>'!~'</code>
15
16
  # @return [Boolean] true if actual value matches the expected value
16
17
  # @raise [Synvert::Core::NodeQuery::Compiler::InvalidOperatorError] if operator is invalid
17
18
  def match?(node, operator)
18
19
  raise InvalidOperatorError, "invalid operator #{operator}" unless valid_operator?(operator)
19
20
 
20
21
  case operator
21
- when :!=
22
+ when '!='
22
23
  if expected_value.is_a?(::Array)
23
24
  actual = actual_value(node)
24
25
  !actual.is_a?(::Array) || actual.size != expected_value.size ||
25
- actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, :!=) }
26
+ actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, '!=') }
26
27
  else
27
28
  actual_value(node) != expected_value
28
29
  end
29
- when :=~
30
+ when '=~'
30
31
  actual_value(node) =~ expected_value
31
- when :!~
32
+ when '!~'
32
33
  actual_value(node) !~ expected_value
33
- when :>
34
+ when '^='
35
+ actual_value(node).start_with?(expected_value)
36
+ when '$='
37
+ actual_value(node).end_with?(expected_value)
38
+ when '*='
39
+ actual_value(node).include?(expected_value)
40
+ when '>'
34
41
  actual_value(node) > expected_value
35
- when :>=
42
+ when '>='
36
43
  actual_value(node) >= expected_value
37
- when :<
44
+ when '<'
38
45
  actual_value(node) < expected_value
39
- when :<=
46
+ when '<='
40
47
  actual_value(node) <= expected_value
41
- when :in
42
- expected_value.any? { |expected| expected.match?(node, :==) }
43
- when :not_in
44
- expected_value.all? { |expected| expected.match?(node, :!=) }
45
- when :includes
48
+ when 'in'
49
+ expected_value.any? { |expected| expected.match?(node, '==') }
50
+ when 'not_in'
51
+ expected_value.all? { |expected| expected.match?(node, '!=') }
52
+ when 'includes'
46
53
  actual_value(node).any? { |actual| actual == expected_value }
47
54
  else
48
55
  if expected_value.is_a?(::Array)
49
56
  actual = actual_value(node)
50
57
  actual.is_a?(::Array) && actual.size == expected_value.size &&
51
- actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, :==) }
58
+ actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, '==') }
52
59
  else
53
60
  actual_value(node) == expected_value
54
61
  end
@@ -5,14 +5,10 @@ module Synvert::Core::NodeQuery::Compiler
5
5
  class Expression
6
6
  # Initialize a Expression.
7
7
  # @param selector [Synvert::Core::NodeQuery::Compiler::Selector] the selector
8
- # @param goto_scope [String] goto scope
9
8
  # @param rest [Synvert::Core::NodeQuery::Compiler::Expression] the rest expression
10
- # @param relationship [Symbol] the relationship between the selector and rest expression, it can be <code>:descendant</code>, <code>:child</code>, <code>:next_sibling</code>, <code>:subsequent_sibling</code> or <code>nil</code>.
11
- def initialize(selector: nil, goto_scope: nil, rest: nil, relationship: nil)
9
+ def initialize(selector: nil, rest: nil)
12
10
  @selector = selector
13
- @goto_scope = goto_scope
14
11
  @rest = rest
15
- @relationship = relationship
16
12
  end
17
13
 
18
14
  # Check if the node matches the expression.
@@ -22,105 +18,24 @@ module Synvert::Core::NodeQuery::Compiler
22
18
  !query_nodes(node).empty?
23
19
  end
24
20
 
25
- # Query nodes by the expression.
21
+ # Query nodes by the selector and the rest expression.
26
22
  #
27
- # * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
28
- # * If relationship is :decendant, it will match in all recursive child nodes.
29
- # * If relationship is :child, it will match in direct child nodes.
30
- # * If relationship is :next_sibling, it try to match next sibling node.
31
- # * If relationship is :subsequent_sibling, it will match in all sibling nodes.
32
23
  # @param node [Parser::AST::Node] node to match
33
- # @param descendant_match [Boolean] whether to match in descendant node
34
24
  # @return [Array<Parser::AST::Node>] matching nodes.
35
- def query_nodes(node, descendant_match = true)
36
- return find_nodes_by_goto_scope(node) if @goto_scope
37
-
38
- return find_nodes_by_relationship(node) if @relationship
39
-
40
- matching_nodes = find_nodes_without_relationship(node, descendant_match)
25
+ def query_nodes(node)
26
+ matching_nodes = @selector.query_nodes(node)
41
27
  return matching_nodes if @rest.nil?
42
28
 
43
- matching_nodes.map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
44
- .flatten
29
+ matching_nodes.flat_map do |matching_node|
30
+ @rest.query_nodes(matching_node)
31
+ end
45
32
  end
46
33
 
47
34
  def to_s
48
- return @selector.to_s unless @rest
49
-
50
35
  result = []
51
36
  result << @selector.to_s if @selector
52
- result << "<#{@goto_scope}>" if @goto_scope
53
- case @relationship
54
- when :child then result << "> #{@rest}"
55
- when :subsequent_sibling then result << "~ #{@rest}"
56
- when :next_sibling then result << "+ #{@rest}"
57
- when :has then result << ":has(#{@rest})"
58
- when :not_has then result << ":not_has(#{@rest})"
59
- else result << @rest.to_s
60
- end
37
+ result << @rest.to_s if @rest
61
38
  result.join(' ')
62
39
  end
63
-
64
- private
65
-
66
- # Find nodes by @goto_scope
67
- # @param node [Parser::AST::Node] node to match
68
- def find_nodes_by_goto_scope(node)
69
- @goto_scope.split('.').each { |scope| node = node.send(scope) }
70
- @rest.query_nodes(node, false)
71
- end
72
-
73
- # Find ndoes by @relationship
74
- # @param node [Parser::AST::Node] node to match
75
- def find_nodes_by_relationship(node)
76
- case @relationship
77
- when :child
78
- if node.is_a?(::Array)
79
- return node.map { |each_node| find_nodes_by_rest(each_node) }
80
- .flatten
81
- else
82
- return node.children.map { |each_node| find_nodes_by_rest(each_node) }
83
- .flatten
84
- end
85
- when :next_sibling
86
- return find_nodes_by_rest(node.siblings.first)
87
- when :subsequent_sibling
88
- return node.siblings.map { |each_node| find_nodes_by_rest(each_node) }
89
- .flatten
90
- when :has
91
- return @rest.match?(node) ? [node] : []
92
- when :not_has
93
- return !@rest.match?(node) ? [node] : []
94
- end
95
- end
96
-
97
- # Find nodes by @rest
98
- # @param node [Parser::AST::Node] node to match
99
- # @param descendant_match [Boolean] whether to match in descendant node
100
- def find_nodes_by_rest(node, descendant_match = false)
101
- @rest.query_nodes(node, descendant_match)
102
- end
103
-
104
- # Find nodes with nil relationship.
105
- # @param node [Parser::AST::Node] node to match
106
- # @param descendant_match [Boolean] whether to match in descendant node
107
- def find_nodes_without_relationship(node, descendant_match = true)
108
- if node.is_a?(::Array)
109
- return node.map { |each_node|
110
- find_nodes_without_relationship(each_node, descendant_match)
111
- }.flatten
112
- end
113
-
114
- return [node] unless @selector
115
-
116
- nodes = []
117
- nodes << node if @selector.match?(node)
118
- if descendant_match
119
- node.recursive_children do |child_node|
120
- nodes << child_node if @selector.match?(child_node)
121
- end
122
- end
123
- @selector.filter(nodes)
124
- end
125
40
  end
126
41
  end
@@ -31,7 +31,7 @@ module Synvert::Core::NodeQuery::Compiler
31
31
 
32
32
  # Get valid operators.
33
33
  def valid_operators
34
- SIMPLE_VALID_OPERATORS
34
+ STRING_VALID_OPERATORS
35
35
  end
36
36
 
37
37
  def to_s
@@ -15,14 +15,14 @@ module Synvert::Core::NodeQuery::Compiler
15
15
  # @param node [Parser::AST::Node] the node
16
16
  # @param operator [Symbol] the operator
17
17
  # @return [Boolean] true if the regexp value matches the node value, otherwise, false.
18
- def match?(node, operator = :=~)
18
+ def match?(node, operator = '=~')
19
19
  match =
20
20
  if node.is_a?(::Parser::AST::Node)
21
21
  @value.match(node.to_source)
22
22
  else
23
23
  @value.match(node.to_s)
24
24
  end
25
- operator == :=~ ? match : !match
25
+ operator == '=~' ? match : !match
26
26
  end
27
27
 
28
28
  # Get valid operators.
@@ -4,53 +4,110 @@ module Synvert::Core::NodeQuery::Compiler
4
4
  # Selector used to match nodes, it combines by node type and/or attribute list, plus index or has expression.
5
5
  class Selector
6
6
  # Initialize a Selector.
7
- # @param node_type [String] the node type
7
+ # @param goto_scope [String] goto scope
8
+ # @param relationship [Symbol] the relationship between the selectors, it can be descendant <code>nil</code>, child <code>></code>, next sibling <code>+</code> or subsequent sibing <code>~</code>.
9
+ # @param rest [Synvert::Core::NodeQuery::Compiler::Selector] the rest selector
10
+ # @param basic_selector [Synvert::Core::NodeQuery::Compiler::BasicSelector] the simple selector
8
11
  # @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
9
- # @param index [Integer] the index
10
12
  # @param pseudo_class [String] the pseudo class, can be <code>has</code> or <code>not_has</code>
11
- # @param pseudo_expression [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo expression
12
- def initialize(node_type: nil, attribute_list: nil, index: nil, pseudo_class: nil, pseudo_expression: nil)
13
- @node_type = node_type
14
- @attribute_list = attribute_list
15
- @index = index
13
+ # @param pseudo_selector [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo selector
14
+ def initialize(goto_scope: nil, relationship: nil, rest: nil, basic_selector: nil, pseudo_class: nil, pseudo_selector: nil)
15
+ @goto_scope = goto_scope
16
+ @relationship = relationship
17
+ @rest = rest
18
+ @basic_selector = basic_selector
16
19
  @pseudo_class = pseudo_class
17
- @pseudo_expression = pseudo_expression
18
- end
19
-
20
- # Filter nodes by index.
21
- def filter(nodes)
22
- return nodes if @index.nil?
23
-
24
- nodes[@index] ? [nodes[@index]] : []
20
+ @pseudo_selector = pseudo_selector
25
21
  end
26
22
 
27
23
  # Check if node matches the selector.
28
24
  # @param node [Parser::AST::Node] the node
29
- def match?(node, _operator = :==)
30
- (!@node_type || (node.is_a?(::Parser::AST::Node) && @node_type.to_sym == node.type)) &&
31
- (!@attribute_list || @attribute_list.match?(node)) &&
32
- (!@pseudo_class || (@pseudo_class == 'has' && @pseudo_expression.match?(node)) || (@pseudo_class == 'not_has' && !@pseudo_expression.match?(node)))
25
+ def match?(node)
26
+ node.is_a?(::Parser::AST::Node) && (!@basic_selector || @basic_selector.match?(node)) && match_pseudo_class?(node)
27
+ end
28
+
29
+ # Query nodes by the selector.
30
+ #
31
+ # * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
32
+ # * If relationship is decendant, it will match in all recursive child nodes.
33
+ # * If relationship is child, it will match in direct child nodes.
34
+ # * If relationship is next sibling, it try to match next sibling node.
35
+ # * If relationship is subsequent sibling, it will match in all sibling nodes.
36
+ # @param node [Parser::AST::Node] node to match
37
+ # @return [Array<Parser::AST::Node>] matching nodes.
38
+ def query_nodes(node)
39
+ return find_nodes_by_relationship(node) if @relationship
40
+
41
+ if node.is_a?(::Array)
42
+ return node.flat_map { |child_node| query_nodes(child_node) }
43
+ end
44
+
45
+ return find_nodes_by_goto_scope(node) if @goto_scope
46
+
47
+ nodes = []
48
+ nodes << node if match?(node)
49
+ if @basic_selector
50
+ node.recursive_children do |child_node|
51
+ nodes << child_node if match?(child_node)
52
+ end
53
+ end
54
+ nodes
33
55
  end
34
56
 
35
57
  def to_s
36
58
  result = []
37
- result << ".#{@node_type}" if @node_type
38
- result << @attribute_list.to_s if @attribute_list
39
- result << ":#{@pseudo_class}(#{@pseudo_expression})" if @pseudo_class
40
- if @index
41
- result <<
42
- case @index
43
- when 0
44
- ':first-child'
45
- when -1
46
- ':last-child'
47
- when (1..)
48
- ":nth-child(#{@index + 1})"
49
- else # ...-1
50
- ":nth-last-child(#{-@index})"
59
+ result << "#{@goto_scope} " if @goto_scope
60
+ result << "#{@relationship} " if @relationship
61
+ result << @rest.to_s if @rest
62
+ result << @basic_selector.to_s if @basic_selector
63
+ result << ":#{@pseudo_class}(#{@pseudo_selector})" if @pseudo_class
64
+ result.join('')
65
+ end
66
+
67
+ private
68
+
69
+ # Find nodes by @goto_scope
70
+ # @param node [Parser::AST::Node] node to match
71
+ def find_nodes_by_goto_scope(node)
72
+ @goto_scope.split('.').each { |scope| node = node.send(scope) }
73
+ @rest.query_nodes(node)
74
+ end
75
+
76
+ # Find ndoes by @relationship
77
+ # @param node [Parser::AST::Node] node to match
78
+ def find_nodes_by_relationship(node)
79
+ nodes = []
80
+ case @relationship
81
+ when '>'
82
+ if node.is_a?(::Array)
83
+ node.each do |child_node|
84
+ nodes << child_node if @rest.match?(child_node)
51
85
  end
86
+ else
87
+ node.children.each do |child_node|
88
+ nodes << child_node if @rest.match?(child_node)
89
+ end
90
+ end
91
+ when '+'
92
+ next_sibling = node.siblings.first
93
+ nodes << next_sibling if @rest.match?(next_sibling)
94
+ when '~'
95
+ node.siblings.each do |sibling_node|
96
+ nodes << sibling_node if @rest.match?(sibling_node)
97
+ end
98
+ end
99
+ nodes
100
+ end
101
+
102
+ def match_pseudo_class?(node)
103
+ case @pseudo_class
104
+ when 'has'
105
+ !@pseudo_selector.query_nodes(node).empty?
106
+ when 'not_has'
107
+ @pseudo_selector.query_nodes(node).empty?
108
+ else
109
+ true
52
110
  end
53
- result.join('')
54
111
  end
55
112
  end
56
113
  end
@@ -13,7 +13,7 @@ module Synvert::Core::NodeQuery::Compiler
13
13
 
14
14
  # Get valid operators.
15
15
  def valid_operators
16
- SIMPLE_VALID_OPERATORS
16
+ STRING_VALID_OPERATORS
17
17
  end
18
18
 
19
19
  def to_s
@@ -8,6 +8,7 @@ module Synvert::Core::NodeQuery::Compiler
8
8
 
9
9
  autoload :Expression, 'synvert/core/node_query/compiler/expression'
10
10
  autoload :Selector, 'synvert/core/node_query/compiler/selector'
11
+ autoload :BasicSelector, 'synvert/core/node_query/compiler/basic_selector'
11
12
  autoload :AttributeList, 'synvert/core/node_query/compiler/attribute_list'
12
13
  autoload :Attribute, 'synvert/core/node_query/compiler/attribute'
13
14
 
@@ -7,8 +7,6 @@ macros
7
7
  CLOSE_ARRAY /\)/
8
8
  OPEN_SELECTOR /\(/
9
9
  CLOSE_SELECTOR /\)/
10
- OPEN_GOTO_SCOPE /</
11
- CLOSE_GOTO_SCOPE />/
12
10
  OPEN_DYNAMIC_ATTRIBUTE /{{/
13
11
  CLOSE_DYNAMIC_ATTRIBUTE /}}/
14
12
  NODE_TYPE /\.[a-z]+/
@@ -22,42 +20,38 @@ macros
22
20
  REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
23
21
  SYMBOL /:[\w!\?<>=]+/
24
22
  TRUE /true/
25
- SINGLE_QUOTE_STRING /'(.*?)'/
26
- DOUBLE_QUOTE_STRING /"(.*?)"/
23
+ SINGLE_QUOTE_STRING /'.*?'/
24
+ DOUBLE_QUOTE_STRING /".*?"/
27
25
 
28
26
  rules
29
27
 
30
28
  # [:state] pattern [actions]
31
29
  /\s+/
32
- /:first-child/ { [:tINDEX, 0] }
33
- /:last-child/ { [:tINDEX, -1] }
34
- /:nth-child\(\d+\)/ { [:tINDEX, text.sub(':nth-child(', '').to_i - 1] }
35
- /:nth-last-child\(\d+\)/ { [:tINDEX, -text.sub(':nth-last-child(', '').to_i] }
36
30
  /:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
37
31
  /:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
38
32
  /#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
39
- />/ { [:tCHILD, text] }
40
- /~/ { [:tSUBSEQUENT_SIBLING, text] }
41
- /\+/ { [:tNEXT_SIBLING, text] }
33
+ /#{IDENTIFIER}/ { [:tGOTO_SCOPE, text] }
34
+ />/ { [:tRELATIONSHIP, text] }
35
+ /~/ { [:tRELATIONSHIP, text] }
36
+ /\+/ { [:tRELATIONSHIP, text] }
42
37
  /#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
43
38
  /#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
44
- /#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
45
39
  /#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
46
- :GOTO_SCOPE /\s+/
47
- :GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
48
- :GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
49
40
  :KEY /\s+/
50
- :KEY /!=/ { @state = :VALUE; [:tNOT_EQUAL, text] }
51
- :KEY /=~/ { @state = :VALUE; [:tMATCH, text] }
52
- :KEY /!~/ { @state = :VALUE; [:tNOT_MATCH, text] }
53
- :KEY />=/ { @state = :VALUE; [:tGREATER_THAN_OR_EQUAL, text] }
54
- :KEY /<=/ { @state = :VALUE; [:tLESS_THAN_OR_EQUAL, text] }
55
- :KEY />/ { @state = :VALUE; [:tGREATER_THAN, text] }
56
- :KEY /</ { @state = :VALUE; [:tLESS_THAN, text] }
57
- :KEY /=/ { @state = :VALUE; [:tEQUAL, text] }
58
- :KEY /includes/i { @state = :VALUE; [:tINCLUDES, text] }
59
- :KEY /not in/i { @state = :VALUE; [:tNOT_IN, text] }
60
- :KEY /in/i { @state = :VALUE; [:tIN, text] }
41
+ :KEY /\^=/ { @state = :VALUE; [:tOPERATOR, '^='] }
42
+ :KEY /\$=/ { @state = :VALUE; [:tOPERATOR, '$='] }
43
+ :KEY /\*=/ { @state = :VALUE; [:tOPERATOR, '*='] }
44
+ :KEY /!=/ { @state = :VALUE; [:tOPERATOR, '!='] }
45
+ :KEY /=~/ { @state = :VALUE; [:tOPERATOR, '=~'] }
46
+ :KEY /!~/ { @state = :VALUE; [:tOPERATOR, '!~'] }
47
+ :KEY />=/ { @state = :VALUE; [:tOPERATOR, '>='] }
48
+ :KEY /<=/ { @state = :VALUE; [:tOPERATOR, '<='] }
49
+ :KEY />/ { @state = :VALUE; [:tOPERATOR, '>'] }
50
+ :KEY /</ { @state = :VALUE; [:tOPERATOR, '<'] }
51
+ :KEY /=/ { @state = :VALUE; [:tOPERATOR, '=='] }
52
+ :KEY /includes/i { @state = :VALUE; [:tOPERATOR, 'includes'] }
53
+ :KEY /not in/i { @state = :VALUE; [:tOPERATOR, 'not_in'] }
54
+ :KEY /in/i { @state = :VALUE; [:tOPERATOR, 'in'] }
61
55
  :KEY /#{IDENTIFIER}/ { [:tKEY, text] }
62
56
  :VALUE /\s+/
63
57
  :VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }