synvert-core 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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] }