synvert-core 1.2.1 → 1.3.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: 56b98032247ef28e548b34dfd8b2b49bad8744227cd61b4f300c7111b701ff91
4
+ data.tar.gz: ea75a1e4f442041665dbd9babdc2f7e4b55c4c78c87facef856e903f2d8c96d2
5
5
  SHA512:
6
- metadata.gz: 1b864e1e350d3467fe12d2c5e26a5c5a0b685752e21802e3773edfbbb8fca8f7dec60fb545475a630a3f00c09359c66859b228cf7cb3d19aa6bf044df4848408
7
- data.tar.gz: c8c5641b85080f03307fed835c05132488d140bb19b694c2b5b0958cb3d6377785ef1447c8b7fec59e7529f2ec610f6c78d72357b60f5fe4285d43d5071750fb
6
+ metadata.gz: 905c1f393a1702df43300432e210784790a30dff9a7bb0c798c742ec372a65ad8487b2097333f57afb1798c566f554ab9096ae92db8cd2541e5bb03769009882
7
+ data.tar.gz: bd03f1c285c5b5b42628df0b3c8777d6167d980c8c3e3b2f92badfc040b2bef5fe2bef3b848b5a6a166002a5e68f8f4f287ac6ee8fadc72a3ef6706fe191c821
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.3.0 (2022-05-12)
4
+
5
+ * Support `*=`, `^=` and `$=` operators
6
+ * Simplify RELATIONSHIP parser
7
+ * Rewrite compiler, let the selector to query nodes
8
+
3
9
  ## 1.2.1 (2022-05-01)
4
10
 
5
11
  * 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}"
@@ -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,78 +18,27 @@ 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
24
  # @param descendant_match [Boolean] whether to match in descendant node
34
25
  # @return [Array<Parser::AST::Node>] matching nodes.
35
26
  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)
27
+ matching_nodes = find_nodes_by_selector(node, descendant_match)
41
28
  return matching_nodes if @rest.nil?
42
29
 
43
- matching_nodes.map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
44
- .flatten
30
+ matching_nodes.flat_map { |matching_node| find_nodes_by_rest(matching_node, descendant_match) }
45
31
  end
46
32
 
47
33
  def to_s
48
- return @selector.to_s unless @rest
49
-
50
34
  result = []
51
35
  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
36
+ result << @rest.to_s if @rest
61
37
  result.join(' ')
62
38
  end
63
39
 
64
40
  private
65
41
 
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
42
  # Find nodes by @rest
98
43
  # @param node [Parser::AST::Node] node to match
99
44
  # @param descendant_match [Boolean] whether to match in descendant node
@@ -104,23 +49,10 @@ module Synvert::Core::NodeQuery::Compiler
104
49
  # Find nodes with nil relationship.
105
50
  # @param node [Parser::AST::Node] node to match
106
51
  # @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
52
+ def find_nodes_by_selector(node, descendant_match = true)
53
+ return Array(node) if !@selector
115
54
 
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)
55
+ @selector.query_nodes(node, descendant_match)
124
56
  end
125
57
  end
126
58
  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,39 +4,68 @@ 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 simple_selector [Synvert::Core::NodeQuery::Compiler::SimpleSelector] the simple selector
8
11
  # @param attribute_list [Synvert::Core::NodeQuery::Compiler::AttributeList] the attribute list
9
12
  # @param index [Integer] the index
10
13
  # @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
14
+ # @param pseudo_selector [Synvert::Core::NodeQuery::Compiler::Expression] the pseudo selector
15
+ def initialize(goto_scope: nil, relationship: nil, rest: nil, simple_selector: nil, index: nil, pseudo_class: nil, pseudo_selector: nil)
16
+ @goto_scope = goto_scope
17
+ @relationship = relationship
18
+ @rest = rest
19
+ @simple_selector = simple_selector
15
20
  @index = index
16
21
  @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]] : []
22
+ @pseudo_selector = pseudo_selector
25
23
  end
26
24
 
27
25
  # Check if node matches the selector.
28
26
  # @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)))
27
+ def match?(node)
28
+ node.is_a?(::Parser::AST::Node) &&
29
+ (!@simple_selector || @simple_selector.match?(node)) &&
30
+ match_pseudo_class?(node)
31
+ end
32
+
33
+ # Query nodes by the selector.
34
+ #
35
+ # * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
36
+ # * If relationship is decendant, it will match in all recursive child nodes.
37
+ # * If relationship is child, it will match in direct child nodes.
38
+ # * If relationship is next sibling, it try to match next sibling node.
39
+ # * If relationship is subsequent sibling, it will match in all sibling nodes.
40
+ # @param node [Parser::AST::Node] node to match
41
+ # @param descendant_match [Boolean] whether to match in descendant node
42
+ # @return [Array<Parser::AST::Node>] matching nodes.
43
+ def query_nodes(node, descendant_match = true)
44
+ return find_nodes_by_relationship(node) if @relationship
45
+
46
+ if node.is_a?(::Array)
47
+ return node.flat_map { |child_node| query_nodes(child_node, descendant_match) }
48
+ end
49
+
50
+ return find_nodes_by_goto_scope(node) if @goto_scope
51
+
52
+ nodes = []
53
+ nodes << node if match?(node)
54
+ if descendant_match && @simple_selector
55
+ node.recursive_children do |child_node|
56
+ nodes << child_node if match?(child_node)
57
+ end
58
+ end
59
+ filter(nodes)
33
60
  end
34
61
 
35
62
  def to_s
36
63
  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
64
+ result << "<#{@goto_scope}> " if @goto_scope
65
+ result << "#{@relationship} " if @relationship
66
+ result << @rest.to_s if @rest
67
+ result << @simple_selector.to_s if @simple_selector
68
+ result << ":#{@pseudo_class}(#{@pseudo_selector})" if @pseudo_class
40
69
  if @index
41
70
  result <<
42
71
  case @index
@@ -52,5 +81,58 @@ module Synvert::Core::NodeQuery::Compiler
52
81
  end
53
82
  result.join('')
54
83
  end
84
+
85
+ private
86
+
87
+ # Find nodes by @goto_scope
88
+ # @param node [Parser::AST::Node] node to match
89
+ def find_nodes_by_goto_scope(node)
90
+ @goto_scope.split('.').each { |scope| node = node.send(scope) }
91
+ @rest.query_nodes(node, false)
92
+ end
93
+
94
+ # Find ndoes by @relationship
95
+ # @param node [Parser::AST::Node] node to match
96
+ def find_nodes_by_relationship(node)
97
+ nodes = []
98
+ case @relationship
99
+ when '>'
100
+ if node.is_a?(::Array)
101
+ node.each do |child_node|
102
+ nodes << child_node if @rest.match?(child_node)
103
+ end
104
+ else
105
+ node.children.each do |child_node|
106
+ nodes << child_node if @rest.match?(child_node)
107
+ end
108
+ end
109
+ when '+'
110
+ next_sibling = node.siblings.first
111
+ nodes << next_sibling if @rest.match?(next_sibling)
112
+ when '~'
113
+ node.siblings.each do |sibling_node|
114
+ nodes << sibling_node if @rest.match?(sibling_node)
115
+ end
116
+ end
117
+ return filter(nodes)
118
+ end
119
+
120
+ def match_pseudo_class?(node)
121
+ case @pseudo_class
122
+ when 'has'
123
+ !@pseudo_selector.query_nodes(node).empty?
124
+ when 'not_has'
125
+ @pseudo_selector.query_nodes(node).empty?
126
+ else
127
+ true
128
+ end
129
+ end
130
+
131
+ # Filter nodes by index.
132
+ def filter(nodes)
133
+ return nodes if @index.nil?
134
+
135
+ nodes[@index] ? [nodes[@index]] : []
136
+ end
55
137
  end
56
138
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synvert::Core::NodeQuery::Compiler
4
+ # SimpleSelector used to match nodes, it combines by node type and/or attribute list.
5
+ class SimpleSelector
6
+ # Initialize a SimpleSelector.
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 = []
24
+ result << ".#{@node_type}" if @node_type
25
+ result << @attribute_list.to_s if @attribute_list
26
+ result.join('')
27
+ end
28
+ end
29
+ 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 :SimpleSelector, 'synvert/core/node_query/compiler/simple_selector'
11
12
  autoload :AttributeList, 'synvert/core/node_query/compiler/attribute_list'
12
13
  autoload :Attribute, 'synvert/core/node_query/compiler/attribute'
13
14
 
@@ -22,8 +22,8 @@ macros
22
22
  REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
23
23
  SYMBOL /:[\w!\?<>=]+/
24
24
  TRUE /true/
25
- SINGLE_QUOTE_STRING /'(.*?)'/
26
- DOUBLE_QUOTE_STRING /"(.*?)"/
25
+ SINGLE_QUOTE_STRING /'.*?'/
26
+ DOUBLE_QUOTE_STRING /".*?"/
27
27
 
28
28
  rules
29
29
 
@@ -36,9 +36,9 @@ rules
36
36
  /:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
37
37
  /:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
38
38
  /#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
39
- />/ { [:tCHILD, text] }
40
- /~/ { [:tSUBSEQUENT_SIBLING, text] }
41
- /\+/ { [:tNEXT_SIBLING, text] }
39
+ />/ { [:tRELATIONSHIP, text] }
40
+ /~/ { [:tRELATIONSHIP, text] }
41
+ /\+/ { [:tRELATIONSHIP, text] }
42
42
  /#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
43
43
  /#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
44
44
  /#{OPEN_GOTO_SCOPE}/ { @state = :GOTO_SCOPE; [:tOPEN_GOTO_SCOPE, text] }
@@ -47,17 +47,20 @@ rules
47
47
  :GOTO_SCOPE /#{IDENTIFIER}/ { [:tIDENTIFIER, text] }
48
48
  :GOTO_SCOPE /#{CLOSE_GOTO_SCOPE}/ { @state = nil; [:tCLOSE_GOTO_SCOPE, text] }
49
49
  :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] }
50
+ :KEY /\^=/ { @state = :VALUE; [:tOPERATOR, '^='] }
51
+ :KEY /\$=/ { @state = :VALUE; [:tOPERATOR, '$='] }
52
+ :KEY /\*=/ { @state = :VALUE; [:tOPERATOR, '*='] }
53
+ :KEY /!=/ { @state = :VALUE; [:tOPERATOR, '!='] }
54
+ :KEY /=~/ { @state = :VALUE; [:tOPERATOR, '=~'] }
55
+ :KEY /!~/ { @state = :VALUE; [:tOPERATOR, '!~'] }
56
+ :KEY />=/ { @state = :VALUE; [:tOPERATOR, '>='] }
57
+ :KEY /<=/ { @state = :VALUE; [:tOPERATOR, '<='] }
58
+ :KEY />/ { @state = :VALUE; [:tOPERATOR, '>'] }
59
+ :KEY /</ { @state = :VALUE; [:tOPERATOR, '<'] }
60
+ :KEY /=/ { @state = :VALUE; [:tOPERATOR, '=='] }
61
+ :KEY /includes/i { @state = :VALUE; [:tOPERATOR, 'includes'] }
62
+ :KEY /not in/i { @state = :VALUE; [:tOPERATOR, 'not_in'] }
63
+ :KEY /in/i { @state = :VALUE; [:tOPERATOR, 'in'] }
61
64
  :KEY /#{IDENTIFIER}/ { [:tKEY, text] }
62
65
  :VALUE /\s+/
63
66
  :VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
@@ -35,8 +35,8 @@ class Synvert::Core::NodeQuery::Lexer
35
35
  REGEXP = /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
36
36
  SYMBOL = /:[\w!\?<>=]+/
37
37
  TRUE = /true/
38
- SINGLE_QUOTE_STRING = /'(.*?)'/
39
- DOUBLE_QUOTE_STRING = /"(.*?)"/
38
+ SINGLE_QUOTE_STRING = /'.*?'/
39
+ DOUBLE_QUOTE_STRING = /".*?"/
40
40
  # :startdoc:
41
41
  # :stopdoc:
42
42
  class LexerError < StandardError ; end
@@ -142,11 +142,11 @@ class Synvert::Core::NodeQuery::Lexer
142
142
  when text = ss.scan(/#{NODE_TYPE}/) then
143
143
  action { [:tNODE_TYPE, text[1..]] }
144
144
  when text = ss.scan(/>/) then
145
- action { [:tCHILD, text] }
145
+ action { [:tRELATIONSHIP, text] }
146
146
  when text = ss.scan(/~/) then
147
- action { [:tSUBSEQUENT_SIBLING, text] }
147
+ action { [:tRELATIONSHIP, text] }
148
148
  when text = ss.scan(/\+/) then
149
- action { [:tNEXT_SIBLING, text] }
149
+ action { [:tRELATIONSHIP, text] }
150
150
  when text = ss.scan(/#{OPEN_SELECTOR}/) then
151
151
  action { [:tOPEN_SELECTOR, text] }
152
152
  when text = ss.scan(/#{CLOSE_SELECTOR}/) then
@@ -175,28 +175,34 @@ class Synvert::Core::NodeQuery::Lexer
175
175
  case
176
176
  when ss.skip(/\s+/) then
177
177
  # do nothing
178
- when text = ss.scan(/!=/) then
179
- action { @state = :VALUE; [:tNOT_EQUAL, text] }
180
- when text = ss.scan(/=~/) then
181
- action { @state = :VALUE; [:tMATCH, text] }
182
- when text = ss.scan(/!~/) then
183
- action { @state = :VALUE; [:tNOT_MATCH, text] }
184
- when text = ss.scan(/>=/) then
185
- action { @state = :VALUE; [:tGREATER_THAN_OR_EQUAL, text] }
186
- when text = ss.scan(/<=/) then
187
- action { @state = :VALUE; [:tLESS_THAN_OR_EQUAL, text] }
188
- when text = ss.scan(/>/) then
189
- action { @state = :VALUE; [:tGREATER_THAN, text] }
190
- when text = ss.scan(/</) then
191
- action { @state = :VALUE; [:tLESS_THAN, text] }
192
- when text = ss.scan(/=/) then
193
- action { @state = :VALUE; [:tEQUAL, text] }
194
- when text = ss.scan(/includes/i) then
195
- action { @state = :VALUE; [:tINCLUDES, text] }
196
- when text = ss.scan(/not in/i) then
197
- action { @state = :VALUE; [:tNOT_IN, text] }
198
- when text = ss.scan(/in/i) then
199
- action { @state = :VALUE; [:tIN, text] }
178
+ when ss.skip(/\^=/) then
179
+ action { @state = :VALUE; [:tOPERATOR, '^='] }
180
+ when ss.skip(/\$=/) then
181
+ action { @state = :VALUE; [:tOPERATOR, '$='] }
182
+ when ss.skip(/\*=/) then
183
+ action { @state = :VALUE; [:tOPERATOR, '*='] }
184
+ when ss.skip(/!=/) then
185
+ action { @state = :VALUE; [:tOPERATOR, '!='] }
186
+ when ss.skip(/=~/) then
187
+ action { @state = :VALUE; [:tOPERATOR, '=~'] }
188
+ when ss.skip(/!~/) then
189
+ action { @state = :VALUE; [:tOPERATOR, '!~'] }
190
+ when ss.skip(/>=/) then
191
+ action { @state = :VALUE; [:tOPERATOR, '>='] }
192
+ when ss.skip(/<=/) then
193
+ action { @state = :VALUE; [:tOPERATOR, '<='] }
194
+ when ss.skip(/>/) then
195
+ action { @state = :VALUE; [:tOPERATOR, '>'] }
196
+ when ss.skip(/</) then
197
+ action { @state = :VALUE; [:tOPERATOR, '<'] }
198
+ when ss.skip(/=/) then
199
+ action { @state = :VALUE; [:tOPERATOR, '=='] }
200
+ when ss.skip(/includes/i) then
201
+ action { @state = :VALUE; [:tOPERATOR, 'includes'] }
202
+ when ss.skip(/not in/i) then
203
+ action { @state = :VALUE; [:tOPERATOR, 'not_in'] }
204
+ when ss.skip(/in/i) then
205
+ action { @state = :VALUE; [:tOPERATOR, 'in'] }
200
206
  when text = ss.scan(/#{IDENTIFIER}/) then
201
207
  action { [:tKEY, text] }
202
208
  else