node_query 1.0.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +18 -0
  5. data/Gemfile.lock +98 -0
  6. data/Guardfile +16 -0
  7. data/README.md +37 -0
  8. data/Rakefile +22 -0
  9. data/lib/node_query/adapter.rb +32 -0
  10. data/lib/node_query/compiler/array_value.rb +35 -0
  11. data/lib/node_query/compiler/attribute.rb +39 -0
  12. data/lib/node_query/compiler/attribute_list.rb +25 -0
  13. data/lib/node_query/compiler/basic_selector.rb +29 -0
  14. data/lib/node_query/compiler/boolean.rb +24 -0
  15. data/lib/node_query/compiler/comparable.rb +107 -0
  16. data/lib/node_query/compiler/evaluated_value.rb +50 -0
  17. data/lib/node_query/compiler/expression.rb +40 -0
  18. data/lib/node_query/compiler/expression_list.rb +28 -0
  19. data/lib/node_query/compiler/float.rb +24 -0
  20. data/lib/node_query/compiler/identifier.rb +41 -0
  21. data/lib/node_query/compiler/integer.rb +24 -0
  22. data/lib/node_query/compiler/invalid_operator_error.rb +7 -0
  23. data/lib/node_query/compiler/nil.rb +24 -0
  24. data/lib/node_query/compiler/parse_error.rb +7 -0
  25. data/lib/node_query/compiler/regexp.rb +38 -0
  26. data/lib/node_query/compiler/selector.rb +117 -0
  27. data/lib/node_query/compiler/string.rb +24 -0
  28. data/lib/node_query/compiler/symbol.rb +24 -0
  29. data/lib/node_query/compiler.rb +26 -0
  30. data/lib/node_query/helper.rb +35 -0
  31. data/lib/node_query/parser_adapter.rb +20 -0
  32. data/lib/node_query/version.rb +5 -0
  33. data/lib/node_query.rb +38 -0
  34. data/lib/node_query_lexer.rex +98 -0
  35. data/lib/node_query_parser.y +63 -0
  36. data/node_query.gemspec +36 -0
  37. data/sig/node_query.rbs +11 -0
  38. metadata +95 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 133039b40806dc003715b5d04f34bbb78d6f3cfe3969e1d10dc920063e8ebe11
4
+ data.tar.gz: 43d5557d76103476d90c98f2575f0869ed8809abe639ec62d38695cd1131eca7
5
+ SHA512:
6
+ metadata.gz: 0cb0b449357c4238ea7d123e16985a82e8fb4a1d2271359616720d0dd2833eac3a07963ff612c7a71a8afdb88ec5014e5f9f35fb22a51acd188e46295d86cae1
7
+ data.tar.gz: 48ff7474de2c811103c71df2789d81818691e11e8c10303544d15dde4a9362933984ea60b353182fc4b9d16720532756e21c593a15b6a685466275bc880f7de3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.0.0 (2022-06-26)
4
+
5
+ * Abstract from synvert-core
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in node_query.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "guard"
13
+ gem "guard-rspec"
14
+ gem "guard-rake"
15
+ gem "oedipus_lex"
16
+ gem "racc"
17
+ gem "parser"
18
+ gem "parser_node_ext"
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ node_query (1.0.0)
5
+ activesupport
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (7.0.3)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 1.6, < 2)
13
+ minitest (>= 5.1)
14
+ tzinfo (~> 2.0)
15
+ ast (2.4.2)
16
+ coderay (1.1.3)
17
+ concurrent-ruby (1.1.10)
18
+ diff-lcs (1.5.0)
19
+ ffi (1.15.5)
20
+ formatador (1.1.0)
21
+ guard (2.18.0)
22
+ formatador (>= 0.2.4)
23
+ listen (>= 2.7, < 4.0)
24
+ lumberjack (>= 1.0.12, < 2.0)
25
+ nenv (~> 0.1)
26
+ notiffany (~> 0.0)
27
+ pry (>= 0.13.0)
28
+ shellany (~> 0.0)
29
+ thor (>= 0.18.1)
30
+ guard-compat (1.2.1)
31
+ guard-rake (1.0.0)
32
+ guard
33
+ rake
34
+ guard-rspec (4.7.3)
35
+ guard (~> 2.1)
36
+ guard-compat (~> 1.1)
37
+ rspec (>= 2.99.0, < 4.0)
38
+ i18n (1.10.0)
39
+ concurrent-ruby (~> 1.0)
40
+ listen (3.7.1)
41
+ rb-fsevent (~> 0.10, >= 0.10.3)
42
+ rb-inotify (~> 0.9, >= 0.9.10)
43
+ lumberjack (1.2.8)
44
+ method_source (1.0.0)
45
+ minitest (5.16.1)
46
+ nenv (0.3.0)
47
+ notiffany (0.1.3)
48
+ nenv (~> 0.1)
49
+ shellany (~> 0.0)
50
+ oedipus_lex (2.6.0)
51
+ parser (3.1.2.0)
52
+ ast (~> 2.4.1)
53
+ parser_node_ext (0.1.0)
54
+ parser
55
+ pry (0.14.1)
56
+ coderay (~> 1.1)
57
+ method_source (~> 1.0)
58
+ racc (1.6.0)
59
+ rake (13.0.6)
60
+ rb-fsevent (0.11.1)
61
+ rb-inotify (0.10.1)
62
+ ffi (~> 1.0)
63
+ rspec (3.11.0)
64
+ rspec-core (~> 3.11.0)
65
+ rspec-expectations (~> 3.11.0)
66
+ rspec-mocks (~> 3.11.0)
67
+ rspec-core (3.11.0)
68
+ rspec-support (~> 3.11.0)
69
+ rspec-expectations (3.11.0)
70
+ diff-lcs (>= 1.2.0, < 2.0)
71
+ rspec-support (~> 3.11.0)
72
+ rspec-mocks (3.11.1)
73
+ diff-lcs (>= 1.2.0, < 2.0)
74
+ rspec-support (~> 3.11.0)
75
+ rspec-support (3.11.0)
76
+ shellany (0.0.1)
77
+ thor (1.2.1)
78
+ tzinfo (2.0.4)
79
+ concurrent-ruby (~> 1.0)
80
+
81
+ PLATFORMS
82
+ x86_64-darwin-21
83
+ x86_64-linux
84
+
85
+ DEPENDENCIES
86
+ guard
87
+ guard-rake
88
+ guard-rspec
89
+ node_query!
90
+ oedipus_lex
91
+ parser
92
+ parser_node_ext
93
+ racc
94
+ rake (~> 13.0)
95
+ rspec (~> 3.0)
96
+
97
+ BUNDLED WITH
98
+ 2.3.7
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'bundle exec rspec' do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
+ watch('lib/node_query_lexer.rex.rb') { 'spec/node_query_lexer_spec.rb' }
7
+ watch('lib/node_query_compiler.rb') { 'spec/node_query_parser_spec.rb' }
8
+ watch(%r{^lib/node_query/compiler/.*\.rb$}) { 'spec/node_query_parser_spec.rb' }
9
+ watch('lib/node_query/parser.racc.rb') { 'spec/node_query_parser_spec.rb' }
10
+ watch('spec/spec_helper.rb') { "spec" }
11
+ end
12
+
13
+ guard :rake, task: 'generate' do
14
+ watch('lib/node_query_lexer.rex')
15
+ watch('lib/node_query_parser.y')
16
+ end
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # NodeQuery
2
+
3
+ NodeQuery defines an AST node query language, which is a css like syntax for matching nodes.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'node_query'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install node_query
20
+
21
+ ## Usage
22
+
23
+ It provides only one api:
24
+
25
+ ```ruby
26
+ NodeQuery.new(nodeQueryString).parse(node)
27
+ ```
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/xinminlabs/node-query-ruby.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ require 'oedipus_lex'
7
+ Rake.application.rake_require "oedipus_lex"
8
+
9
+ file "lib/node_query_lexer.rex.rb" => "lib/node_query_lexer.rex"
10
+ file "lib/node_query_parser.racc.rb" => "lib/node_query_parser.y"
11
+
12
+ task :lexer => "lib/node_query_lexer.rex.rb"
13
+ task :parser => "lib/node_query_parser.racc.rb"
14
+ task :generate => [:lexer, :parser]
15
+
16
+ rule '.racc.rb' => '.y' do |t|
17
+ cmd = "bundle exec racc -l -v -o #{t.name} #{t.source}"
18
+ sh cmd
19
+ end
20
+
21
+ task :default => :spec
22
+ task :spec => :generate
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abstract Adapter class
4
+ class NodeQuery::Adapter
5
+ # Get the type of node
6
+ # @param node [Node] ast node
7
+ # @return [Symbol] node type
8
+ def get_node_type(node)
9
+ raise NotImplementedError, 'get_node_type is not implemented'
10
+ end
11
+
12
+ # Get the source code of node
13
+ # @param node [Node] ast node
14
+ # @return [String] node source code
15
+ def get_source(node)
16
+ raise NotImplementedError, 'get_source is not implemented'
17
+ end
18
+
19
+ # Get the children of node
20
+ # @param node [Node] ast node
21
+ # @return [Array<Node>] node children
22
+ def get_children(node)
23
+ raise NotImplementedError, 'get_children is not implemented'
24
+ end
25
+
26
+ # Get the siblings of node
27
+ # @param node [Node] ast node
28
+ # @return [Array<Node>] node siblings
29
+ def get_siblings(node)
30
+ raise NotImplementedError, 'get_siblings is not implemented'
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # ArrayValue represents a ruby array value.
5
+ class ArrayValue
6
+ include Comparable
7
+
8
+ # Initialize an Array.
9
+ # @param value the first value of the array
10
+ # @param rest the rest value of the array
11
+ def initialize(value: nil, rest: nil)
12
+ @value = value
13
+ @rest = rest
14
+ end
15
+
16
+ # Get the expected value.
17
+ # @return [Array]
18
+ def expected_value
19
+ expected = []
20
+ expected.push(@value) if @value
21
+ expected += @rest.expected_value if @rest
22
+ expected
23
+ end
24
+
25
+ # Get valid operators.
26
+ # @return [Array] valid operators
27
+ def valid_operators
28
+ ARRAY_VALID_OPERATORS
29
+ end
30
+
31
+ def to_s
32
+ [@value, @rest].compact.join(' ')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # Attribute is a pair of key, value and operator,
5
+ class Attribute
6
+ # Initialize a Attribute.
7
+ # @param key [String] the key
8
+ # @param value the value can be any class implement {NodeQuery::Compiler::Comparable}
9
+ # @param operator [String] the operator
10
+ def initialize(key:, value:, operator: '==')
11
+ @key = key
12
+ @value = value
13
+ @operator = operator
14
+ end
15
+
16
+ # Check if the node matches the attribute.
17
+ # @param node [Node] the node
18
+ # @return [Boolean]
19
+ def match?(node)
20
+ @value.base_node = node if @value.is_a?(EvaluatedValue)
21
+ node && @value.match?(NodeQuery::Helper.get_target_node(node, @key), @operator)
22
+ end
23
+
24
+ def to_s
25
+ case @operator
26
+ when '^=', '$=', '*=', '!=', '=~', '!~', '>=', '>', '<=', '<'
27
+ "#{@key}#{@operator}#{@value}"
28
+ when 'in'
29
+ "#{@key} in (#{@value})"
30
+ when 'not_in'
31
+ "#{@key} not in (#{@value})"
32
+ when 'includes'
33
+ "#{@key} includes #{@value}"
34
+ else
35
+ "#{@key}=#{@value}"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # AttributeList contains one or more {NodeQuery::Compiler::Attribute}.
5
+ class AttributeList
6
+ # Initialize an AttributeList.
7
+ # @param attribute [NodeQuery::Compiler::Attribute] the attribute
8
+ # @param rest [NodeQuery::Compiler::AttributeList] the rest attribute list
9
+ def initialize(attribute:, rest: nil)
10
+ @attribute = attribute
11
+ @rest = rest
12
+ end
13
+
14
+ # Check if the node matches the attribute list.
15
+ # @param node [Node] the node
16
+ # @return [Boolean]
17
+ def match?(node)
18
+ @attribute.match?(node) && (!@rest || @rest.match?(node))
19
+ end
20
+
21
+ def to_s
22
+ "[#{@attribute}]#{@rest}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module 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 [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 [Node] the node
16
+ # @return [Boolean]
17
+ def match?(node, _operator = '==')
18
+ return false unless node
19
+
20
+ @node_type.to_sym == NodeQuery.get_adapter.get_node_type(node) && (!@attribute_list || @attribute_list.match?(node))
21
+ end
22
+
23
+ def to_s
24
+ result = [".#{@node_type}"]
25
+ result << @attribute_list.to_s if @attribute_list
26
+ result.join('')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # Boolean represents a ruby boolean value.
5
+ class Boolean
6
+ include Comparable
7
+
8
+ # Initialize a Boolean.
9
+ # @param value [Boolean] the boolean value
10
+ def initialize(value:)
11
+ @value = value
12
+ end
13
+
14
+ # Get valid operators.
15
+ # @return [Array] valid operators
16
+ def valid_operators
17
+ SIMPLE_VALID_OPERATORS
18
+ end
19
+
20
+ def to_s
21
+ @value.to_s
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # Compare acutal value with expected value.
5
+ module Comparable
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 = ['=~', '!~']
11
+
12
+ # Check if the actual value matches the expected value.
13
+ #
14
+ # @param node [Node] node to calculate actual value
15
+ # @param operator [String] 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>
16
+ # @return [Boolean] true if actual value matches the expected value
17
+ # @raise [NodeQuery::Compiler::InvalidOperatorError] if operator is invalid
18
+ def match?(node, operator)
19
+ raise InvalidOperatorError, "invalid operator #{operator}" unless valid_operator?(operator)
20
+
21
+ case operator
22
+ when '!='
23
+ if expected_value.is_a?(::Array)
24
+ actual = actual_value(node)
25
+ !actual.is_a?(::Array) || actual.size != expected_value.size ||
26
+ actual.zip(expected_value).any? { |actual_node, expected_node| expected_node.match?(actual_node, '!=') }
27
+ else
28
+ actual_value(node) != expected_value
29
+ end
30
+ when '=~'
31
+ actual_value(node) =~ expected_value
32
+ when '!~'
33
+ actual_value(node) !~ expected_value
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 '>'
41
+ actual_value(node) > expected_value
42
+ when '>='
43
+ actual_value(node) >= expected_value
44
+ when '<'
45
+ actual_value(node) < expected_value
46
+ when '<='
47
+ actual_value(node) <= expected_value
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'
53
+ actual_value(node).any? { |actual| actual == expected_value }
54
+ else
55
+ if expected_value.is_a?(::Array)
56
+ actual = actual_value(node)
57
+ actual.is_a?(::Array) && actual.size == expected_value.size &&
58
+ actual.zip(expected_value).all? { |actual_node, expected_node| expected_node.match?(actual_node, '==') }
59
+ else
60
+ actual_value(node) == expected_value
61
+ end
62
+ end
63
+ end
64
+
65
+ # Get the actual value from ast node.
66
+ # @param node [Node] ast node
67
+ # @return the node value, could be integer, float, string, boolean, nil, range, and etc.
68
+ def actual_value(node)
69
+ if node.is_a?(::Parser::AST::Node)
70
+ case NodeQuery.get_adapter.get_node_type(node)
71
+ when :int, :float, :str, :sym
72
+ NodeQuery.get_adapter.get_children(node).last
73
+ when :true
74
+ true
75
+ when :false
76
+ false
77
+ when :nil
78
+ nil
79
+ when :array
80
+ NodeQuery.get_adapter.get_children(node).map { |child_node| actual_value(child_node) }
81
+ when :irange
82
+ actual_value(NodeQuery.get_adapter.get_children(node).first)..actual_value(NodeQuery.get_adapter.get_children(node).last)
83
+ when :erange
84
+ actual_value(NodeQuery.get_adapter.get_children(node).first)...actual_value(NodeQuery.get_adapter.get_children(node).last)
85
+ when :begin
86
+ actual_value(NodeQuery.get_adapter.get_children(node).first)
87
+ else
88
+ node
89
+ end
90
+ else
91
+ node
92
+ end
93
+ end
94
+
95
+ # Get the expected value
96
+ # @return expected value, could be integer, float, string, boolean, nil, range, and etc.
97
+ def expected_value
98
+ @value
99
+ end
100
+
101
+ # Check if the operator is valid.
102
+ # @return [Boolean] true if the operator is valid
103
+ def valid_operator?(operator)
104
+ valid_operators.include?(operator)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # EvaluatedValue represents a ruby dynamic attribute.
5
+ # e.g. code is <code>{ a: a }</code>, query is <code>'.hash > .pair[key={{value}}]'</code>,
6
+ # <code>{{value}}</code> is the dynamic attribute.
7
+ class EvaluatedValue
8
+ include Comparable
9
+
10
+ attr_accessor :base_node
11
+
12
+ # Initialize an EvaluatedValue.
13
+ # @param value [String] the dynamic attribute value
14
+ def initialize(value:)
15
+ @value = value
16
+ end
17
+
18
+ # Get the actual value of a node.
19
+ # @param node [Node] the node
20
+ # @return [String] if node is a {Node}, return the node source code, otherwise return the node itself.
21
+ def actual_value(node)
22
+ if node.is_a?(::Parser::AST::Node)
23
+ NodeQuery.get_adapter.get_source(node)
24
+ else
25
+ node
26
+ end
27
+ end
28
+
29
+ # Get the expected value.
30
+ # @return [String] Query the node by @value from base_node, if the node is a {Node}, return the node source code, otherwise return the node itself.
31
+ def expected_value
32
+ node = NodeQuery::Helper.get_target_node(base_node, @value)
33
+ if node.is_a?(::Parser::AST::Node)
34
+ NodeQuery.get_adapter.get_source(node)
35
+ else
36
+ node
37
+ end
38
+ end
39
+
40
+ # Get valid operators.
41
+ # @return [Array] valid operators
42
+ def valid_operators
43
+ SIMPLE_VALID_OPERATORS
44
+ end
45
+
46
+ def to_s
47
+ "{{#{@value}}}"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # Expression represents a node query expression.
5
+ class Expression
6
+ # Initialize a Expression.
7
+ # @param selector [NodeQuery::Compiler::Selector] the selector
8
+ # @param rest [NodeQuery::Compiler::Expression] the rest expression
9
+ def initialize(selector: nil, rest: nil)
10
+ @selector = selector
11
+ @rest = rest
12
+ end
13
+
14
+ # Check if the node matches the expression.
15
+ # @param node [Node] the node
16
+ # @return [Boolean]
17
+ def match?(node)
18
+ !query_nodes(node).empty?
19
+ end
20
+
21
+ # Query nodes by the selector and the rest expression.
22
+ # @param node [Node] node to match
23
+ # @return [Array<Node>] matching nodes.
24
+ def query_nodes(node)
25
+ matching_nodes = @selector.query_nodes(node)
26
+ return matching_nodes if @rest.nil?
27
+
28
+ matching_nodes.flat_map do |matching_node|
29
+ @rest.query_nodes(matching_node)
30
+ end
31
+ end
32
+
33
+ def to_s
34
+ result = []
35
+ result << @selector.to_s if @selector
36
+ result << @rest.to_s if @rest
37
+ result.join(' ')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # ExpressionList contains one or more {NodeQuery::Compiler::Expression}.
5
+ class ExpressionList
6
+ # Initialize an ExpressionList.
7
+ # @param expression [NodeQuery::Compiler::Expression] the expression
8
+ # @param rest [NodeQuery::Compiler::ExpressionList] the rest expression list
9
+ def initialize(expression:, rest: nil)
10
+ @expression = expression
11
+ @rest = rest
12
+ end
13
+
14
+ # Query nodes by the current and the rest expression.
15
+ # @param node [Node] node to match
16
+ # @return [Array<Node>] matching nodes.
17
+ def query_nodes(node)
18
+ matching_nodes = @expression.query_nodes(node)
19
+ return matching_nodes if @rest.nil?
20
+
21
+ matching_nodes + @rest.query_nodes(node)
22
+ end
23
+
24
+ def to_s
25
+ [@expression, @rest].compact.join(', ')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NodeQuery::Compiler
4
+ # Float represents a ruby float value.
5
+ class Float
6
+ include Comparable
7
+
8
+ # Initialize a Float.
9
+ # @param value [Float] the float value
10
+ def initialize(value:)
11
+ @value = value
12
+ end
13
+
14
+ # Get valid operators.
15
+ # @return [Array] valid operators
16
+ def valid_operators
17
+ NUMBER_VALID_OPERATORS
18
+ end
19
+
20
+ def to_s
21
+ @value.to_s
22
+ end
23
+ end
24
+ end