node_query 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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