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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +98 -0
- data/Guardfile +16 -0
- data/README.md +37 -0
- data/Rakefile +22 -0
- data/lib/node_query/adapter.rb +32 -0
- data/lib/node_query/compiler/array_value.rb +35 -0
- data/lib/node_query/compiler/attribute.rb +39 -0
- data/lib/node_query/compiler/attribute_list.rb +25 -0
- data/lib/node_query/compiler/basic_selector.rb +29 -0
- data/lib/node_query/compiler/boolean.rb +24 -0
- data/lib/node_query/compiler/comparable.rb +107 -0
- data/lib/node_query/compiler/evaluated_value.rb +50 -0
- data/lib/node_query/compiler/expression.rb +40 -0
- data/lib/node_query/compiler/expression_list.rb +28 -0
- data/lib/node_query/compiler/float.rb +24 -0
- data/lib/node_query/compiler/identifier.rb +41 -0
- data/lib/node_query/compiler/integer.rb +24 -0
- data/lib/node_query/compiler/invalid_operator_error.rb +7 -0
- data/lib/node_query/compiler/nil.rb +24 -0
- data/lib/node_query/compiler/parse_error.rb +7 -0
- data/lib/node_query/compiler/regexp.rb +38 -0
- data/lib/node_query/compiler/selector.rb +117 -0
- data/lib/node_query/compiler/string.rb +24 -0
- data/lib/node_query/compiler/symbol.rb +24 -0
- data/lib/node_query/compiler.rb +26 -0
- data/lib/node_query/helper.rb +35 -0
- data/lib/node_query/parser_adapter.rb +20 -0
- data/lib/node_query/version.rb +5 -0
- data/lib/node_query.rb +38 -0
- data/lib/node_query_lexer.rex +98 -0
- data/lib/node_query_parser.y +63 -0
- data/node_query.gemspec +36 -0
- data/sig/node_query.rbs +11 -0
- metadata +95 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Identifier represents a ruby identifier value.
|
5
|
+
# e.g. code is `class Synvert; end`, `Synvert` is an identifier.
|
6
|
+
class Identifier
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
# Initialize an Identifier.
|
10
|
+
# @param value [String] the identifier value
|
11
|
+
def initialize(value:)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the actual value.
|
16
|
+
# @param node [Node] the node
|
17
|
+
# @return [String|Array]
|
18
|
+
# If the node is a {Node}, return the node source code,
|
19
|
+
# if the node is an Array, return the array of each element's actual value,
|
20
|
+
# otherwise, return the String value.
|
21
|
+
def actual_value(node)
|
22
|
+
if node.is_a?(::Parser::AST::Node)
|
23
|
+
NodeQuery.get_adapter.get_source(node)
|
24
|
+
elsif node.is_a?(::Array)
|
25
|
+
node.map { |n| actual_value(n) }
|
26
|
+
else
|
27
|
+
node.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get valid operators.
|
32
|
+
# @return [Array] valid operators
|
33
|
+
def valid_operators
|
34
|
+
STRING_VALID_OPERATORS
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Integer represents a ruby integer value.
|
5
|
+
class Integer
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize a Integer.
|
9
|
+
# @param value [Integer] the integer 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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Nil represents a ruby nil value.
|
5
|
+
class Nil
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize a Nil.
|
9
|
+
# @param value [nil] the nil 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
|
+
'nil'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Regexp represents a ruby regexp value.
|
5
|
+
class Regexp
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize a Regexp.
|
9
|
+
# @param value [Regexp] the regexp value
|
10
|
+
def initialize(value:)
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check if the regexp value matches the node value.
|
15
|
+
# @param node [Node] the node
|
16
|
+
# @param operator [String] the operator
|
17
|
+
# @return [Boolean] true if the regexp value matches the node value, otherwise, false.
|
18
|
+
def match?(node, operator = '=~')
|
19
|
+
match =
|
20
|
+
if node.is_a?(::Parser::AST::Node)
|
21
|
+
@value.match(NodeQuery.get_adapter.get_source(node))
|
22
|
+
else
|
23
|
+
@value.match(node.to_s)
|
24
|
+
end
|
25
|
+
operator == '=~' ? match : !match
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get valid operators.
|
29
|
+
# @return [Array] valid operators
|
30
|
+
def valid_operators
|
31
|
+
REGEXP_VALID_OPERATORS
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@value.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Selector used to match nodes, it combines by node type and/or attribute list, plus index or has expression.
|
5
|
+
class Selector
|
6
|
+
# Initialize a Selector.
|
7
|
+
# @param goto_scope [String] goto scope
|
8
|
+
# @param relationship [String] 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 [NodeQuery::Compiler::Selector] the rest selector
|
10
|
+
# @param basic_selector [NodeQuery::Compiler::BasicSelector] the simple selector
|
11
|
+
# @param attribute_list [NodeQuery::Compiler::AttributeList] the attribute list
|
12
|
+
# @param pseudo_class [String] the pseudo class, can be <code>has</code> or <code>not_has</code>
|
13
|
+
# @param pseudo_selector [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
|
19
|
+
@pseudo_class = pseudo_class
|
20
|
+
@pseudo_selector = pseudo_selector
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if node matches the selector.
|
24
|
+
# @param node [Parser::AST::Node] the 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
|
+
# * If relationship is nil, it will match in all recursive child nodes and return matching nodes.
|
31
|
+
# * If relationship is decendant, it will match in all recursive child nodes.
|
32
|
+
# * If relationship is child, it will match in direct child nodes.
|
33
|
+
# * If relationship is next sibling, it try to match next sibling node.
|
34
|
+
# * If relationship is subsequent sibling, it will match in all sibling nodes.
|
35
|
+
# @param node [Node] node to match
|
36
|
+
# @return [Array<Node>] matching nodes.
|
37
|
+
def query_nodes(node)
|
38
|
+
return find_nodes_by_relationship(node) if @relationship
|
39
|
+
|
40
|
+
if node.is_a?(::Array)
|
41
|
+
return node.flat_map { |child_node| query_nodes(child_node) }
|
42
|
+
end
|
43
|
+
|
44
|
+
return find_nodes_by_goto_scope(node) if @goto_scope
|
45
|
+
|
46
|
+
nodes = []
|
47
|
+
nodes << node if match?(node)
|
48
|
+
if @basic_selector
|
49
|
+
NodeQuery::Helper.handle_recursive_child(node) do |child_node|
|
50
|
+
nodes << child_node if match?(child_node)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
nodes
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
result = []
|
58
|
+
result << "#{@goto_scope} " if @goto_scope
|
59
|
+
result << "#{@relationship} " if @relationship
|
60
|
+
result << @rest.to_s if @rest
|
61
|
+
result << @basic_selector.to_s if @basic_selector
|
62
|
+
result << ":#{@pseudo_class}(#{@pseudo_selector})" if @pseudo_class
|
63
|
+
result.join('')
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Find nodes by @goto_scope
|
69
|
+
# @param node [Node] node to match
|
70
|
+
# @return [Array<Node>] matching nodes
|
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 nodes by @relationship
|
77
|
+
# @param node [Node] node to match
|
78
|
+
# @return [Array<Node>] matching nodes
|
79
|
+
def find_nodes_by_relationship(node)
|
80
|
+
nodes = []
|
81
|
+
case @relationship
|
82
|
+
when '>'
|
83
|
+
if node.is_a?(::Array)
|
84
|
+
node.each do |child_node|
|
85
|
+
nodes << child_node if @rest.match?(child_node)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
node.children.each do |child_node|
|
89
|
+
nodes << child_node if @rest.match?(child_node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
when '+'
|
93
|
+
next_sibling = node.siblings.first
|
94
|
+
nodes << next_sibling if @rest.match?(next_sibling)
|
95
|
+
when '~'
|
96
|
+
node.siblings.each do |sibling_node|
|
97
|
+
nodes << sibling_node if @rest.match?(sibling_node)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
nodes
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if it matches pseudo class.
|
104
|
+
# @param node [Node] node to match
|
105
|
+
# @return [Boolean]
|
106
|
+
def match_pseudo_class?(node)
|
107
|
+
case @pseudo_class
|
108
|
+
when 'has'
|
109
|
+
!@pseudo_selector.query_nodes(node).empty?
|
110
|
+
when 'not_has'
|
111
|
+
@pseudo_selector.query_nodes(node).empty?
|
112
|
+
else
|
113
|
+
true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# String represents a ruby string value.
|
5
|
+
class String
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initialize a String.
|
9
|
+
# @param value [String] the string 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
|
+
STRING_VALID_OPERATORS
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"\"#{@value}\""
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
# Symbol represents a ruby symbol value.
|
5
|
+
class Symbol
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# Initliaze a Symobol.
|
9
|
+
# @param value [Symbol] the symbol 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}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NodeQuery::Compiler
|
4
|
+
autoload :InvalidOperatorError, 'node_query/compiler/invalid_operator_error'
|
5
|
+
autoload :ParseError, 'node_query/compiler/parse_error'
|
6
|
+
|
7
|
+
autoload :Comparable, 'node_query/compiler/comparable'
|
8
|
+
|
9
|
+
autoload :ExpressionList, 'node_query/compiler/expression_list'
|
10
|
+
autoload :Expression, 'node_query/compiler/expression'
|
11
|
+
autoload :Selector, 'node_query/compiler/selector'
|
12
|
+
autoload :BasicSelector, 'node_query/compiler/basic_selector'
|
13
|
+
autoload :AttributeList, 'node_query/compiler/attribute_list'
|
14
|
+
autoload :Attribute, 'node_query/compiler/attribute'
|
15
|
+
|
16
|
+
autoload :ArrayValue, 'node_query/compiler/array_value'
|
17
|
+
autoload :Boolean, 'node_query/compiler/boolean'
|
18
|
+
autoload :EvaluatedValue, 'node_query/compiler/evaluated_value'
|
19
|
+
autoload :Float, 'node_query/compiler/float'
|
20
|
+
autoload :Identifier, 'node_query/compiler/identifier'
|
21
|
+
autoload :Integer, 'node_query/compiler/integer'
|
22
|
+
autoload :Nil, 'node_query/compiler/nil'
|
23
|
+
autoload :Regexp, 'node_query/compiler/regexp'
|
24
|
+
autoload :String, 'node_query/compiler/string'
|
25
|
+
autoload :Symbol, 'node_query/compiler/symbol'
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NodeQuery::Helper
|
4
|
+
# Get target node by the keys.
|
5
|
+
# @param node [Node] ast node
|
6
|
+
# @param keys [String] keys of child node.
|
7
|
+
# @return [Node|] the target node.
|
8
|
+
def self.get_target_node(node, keys)
|
9
|
+
return unless node
|
10
|
+
|
11
|
+
first_key, rest_keys = keys.to_s.split('.', 2)
|
12
|
+
if (node.is_a?(Array) && first_key === "*")
|
13
|
+
return node.map { |child_node| get_target_node(child_node, rest_keys) }
|
14
|
+
end
|
15
|
+
|
16
|
+
if node.respond_to?(first_key)
|
17
|
+
child_node = node.send(first_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
return child_node unless rest_keys
|
21
|
+
|
22
|
+
return get_target_node(child_node, rest_keys)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Recursively handle child nodes.
|
26
|
+
# @param node [Node] ast node
|
27
|
+
# @yield [child] Gives a child node.
|
28
|
+
# @yieldparam child [Parser::AST::Node] child node
|
29
|
+
def self.handle_recursive_child(node, &block)
|
30
|
+
NodeQuery.get_adapter.get_children(node).each do |child|
|
31
|
+
block.call(child)
|
32
|
+
handle_recursive_child(child, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class NodeQuery::ParserAdapter
|
4
|
+
def get_node_type(node)
|
5
|
+
node.type
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_source(node)
|
9
|
+
node.loc.expression.source
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_children(node)
|
13
|
+
node.is_a?(Parser::AST::Node) ? node.children : []
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_siblings(node)
|
17
|
+
index = node.parent.children.index(node)
|
18
|
+
node.parent.children[index + 1..]
|
19
|
+
end
|
20
|
+
end
|
data/lib/node_query.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/array'
|
4
|
+
|
5
|
+
require_relative "node_query/version"
|
6
|
+
require_relative "node_query/parser_adapter"
|
7
|
+
require_relative "node_query/compiler"
|
8
|
+
require_relative "node_query/helper"
|
9
|
+
require_relative "./node_query_lexer.rex"
|
10
|
+
require_relative "./node_query_parser.racc"
|
11
|
+
|
12
|
+
class NodeQuery
|
13
|
+
# Configure NodeQuery
|
14
|
+
# @param [Hash] options options to configure
|
15
|
+
# @option options [NodeQuery::Adapter] :adapter the adpater
|
16
|
+
def self.configure(options)
|
17
|
+
@adapter = options.adapter
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the adapter
|
21
|
+
# @return [NodeQuery::Adapter] current adapter, by default is {NodeQuery::ParserAdapter}
|
22
|
+
def self.get_adapter
|
23
|
+
@adapter ||= ParserAdapter.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initialize a NodeQuery.
|
27
|
+
# @param nql [String] node query language
|
28
|
+
def initialize(nql)
|
29
|
+
@expression = NodeQueryParser.new.parse(nql)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse ast node.
|
33
|
+
# @param node [Node] ast node
|
34
|
+
# @return [Array<Node>] matching child nodes
|
35
|
+
def parse(node)
|
36
|
+
@expression.query_nodes(node)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class NodeQueryLexer
|
2
|
+
|
3
|
+
macros
|
4
|
+
OPEN_ATTRIBUTE /\[/
|
5
|
+
CLOSE_ATTRIBUTE /\]/
|
6
|
+
OPEN_ARRAY /\(/
|
7
|
+
CLOSE_ARRAY /\)/
|
8
|
+
OPEN_SELECTOR /\(/
|
9
|
+
CLOSE_SELECTOR /\)/
|
10
|
+
OPEN_DYNAMIC_ATTRIBUTE /{{/
|
11
|
+
CLOSE_DYNAMIC_ATTRIBUTE /}}/
|
12
|
+
NODE_TYPE /\.[a-z]+/
|
13
|
+
IDENTIFIER /[\.\w]+/
|
14
|
+
IDENTIFIER_VALUE /[\.\w!&:\?<>=]+/
|
15
|
+
FALSE /false/
|
16
|
+
FLOAT /\d+\.\d+/
|
17
|
+
INTEGER /\d+/
|
18
|
+
NIL /nil/
|
19
|
+
REGEXP_BODY /(?:[^\/]|\\\/)*/
|
20
|
+
REGEXP /\/(#{REGEXP_BODY})(?<!\\)\/([imxo]*)/
|
21
|
+
SYMBOL /:[\w!\?<>=]+/
|
22
|
+
TRUE /true/
|
23
|
+
SINGLE_QUOTE_STRING /'.*?'/
|
24
|
+
DOUBLE_QUOTE_STRING /".*?"/
|
25
|
+
|
26
|
+
rules
|
27
|
+
|
28
|
+
# [:state] pattern [actions]
|
29
|
+
/\s+/
|
30
|
+
/,/ { [:tCOMMA, text] }
|
31
|
+
/:has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
32
|
+
/:not_has/ { [:tPSEUDO_CLASS, text[1..-1]] }
|
33
|
+
/#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
34
|
+
/#{IDENTIFIER}/ { [:tGOTO_SCOPE, text] }
|
35
|
+
/>/ { [:tRELATIONSHIP, text] }
|
36
|
+
/~/ { [:tRELATIONSHIP, text] }
|
37
|
+
/\+/ { [:tRELATIONSHIP, text] }
|
38
|
+
/#{OPEN_SELECTOR}/ { [:tOPEN_SELECTOR, text] }
|
39
|
+
/#{CLOSE_SELECTOR}/ { [:tCLOSE_SELECTOR, text] }
|
40
|
+
/#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
41
|
+
:KEY /\s+/
|
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 /=/ { @state = :VALUE; [:tOPERATOR, '=='] }
|
53
|
+
:KEY /includes/i { @state = :VALUE; [:tOPERATOR, 'includes'] }
|
54
|
+
:KEY /not in/i { @state = :VALUE; [:tOPERATOR, 'not_in'] }
|
55
|
+
:KEY /in/i { @state = :VALUE; [:tOPERATOR, 'in'] }
|
56
|
+
:KEY /#{IDENTIFIER}/ { [:tKEY, text] }
|
57
|
+
:VALUE /\s+/
|
58
|
+
:VALUE /\[\]=/ { [:tIDENTIFIER_VALUE, text] }
|
59
|
+
:VALUE /\[\]/ { [:tIDENTIFIER_VALUE, text] }
|
60
|
+
:VALUE /:\[\]=/ { [:tSYMBOL, text[1..-1].to_sym] }
|
61
|
+
:VALUE /:\[\]/ { [:tSYMBOL, text[1..-1].to_sym] }
|
62
|
+
:VALUE /{{#{IDENTIFIER}}}/ { [:tDYNAMIC_ATTRIBUTE, text[2..-3]] }
|
63
|
+
:VALUE /#{OPEN_ARRAY}/ { @state = :ARRAY_VALUE; [:tOPEN_ARRAY, text] }
|
64
|
+
:VALUE /#{CLOSE_ATTRIBUTE}/ { @nested_count -= 1; @state = @nested_count == 0 ? nil : :VALUE; [:tCLOSE_ATTRIBUTE, text] }
|
65
|
+
:VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
66
|
+
:VALUE /#{NIL}/ { [:tNIL, nil] }
|
67
|
+
:VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
68
|
+
:VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|
69
|
+
:VALUE /#{SYMBOL}/ { [:tSYMBOL, text[1..-1].to_sym] }
|
70
|
+
:VALUE /#{FLOAT}/ { [:tFLOAT, text.to_f] }
|
71
|
+
:VALUE /#{INTEGER}/ { [:tINTEGER, text.to_i] }
|
72
|
+
:VALUE /#{REGEXP}/ { [:tREGEXP, eval(text)] }
|
73
|
+
:VALUE /#{DOUBLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
74
|
+
:VALUE /#{SINGLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
75
|
+
:VALUE /#{NODE_TYPE}/ { [:tNODE_TYPE, text[1..]] }
|
76
|
+
:VALUE /#{OPEN_ATTRIBUTE}/ { @nested_count += 1; @state = :KEY; [:tOPEN_ATTRIBUTE, text] }
|
77
|
+
:VALUE /#{IDENTIFIER_VALUE}/ { [:tIDENTIFIER_VALUE, text] }
|
78
|
+
:ARRAY_VALUE /\s+/
|
79
|
+
:ARRAY_VALUE /#{CLOSE_ARRAY}/ { @state = :VALUE; [:tCLOSE_ARRAY, text] }
|
80
|
+
:ARRAY_VALUE /#{NIL}\?/ { [:tIDENTIFIER_VALUE, text] }
|
81
|
+
:ARRAY_VALUE /#{NIL}/ { [:tNIL, nil] }
|
82
|
+
:ARRAY_VALUE /#{TRUE}/ { [:tBOOLEAN, true] }
|
83
|
+
:ARRAY_VALUE /#{FALSE}/ { [:tBOOLEAN, false] }
|
84
|
+
:ARRAY_VALUE /#{SYMBOL}/ { [:tSYMBOL, text[1..-1].to_sym] }
|
85
|
+
:ARRAY_VALUE /#{FLOAT}/ { [:tFLOAT, text.to_f] }
|
86
|
+
:ARRAY_VALUE /#{INTEGER}/ { [:tINTEGER, text.to_i] }
|
87
|
+
:ARRAY_VALUE /#{REGEXP}/ { [:tREGEXP, eval(text)] }
|
88
|
+
:ARRAY_VALUE /#{DOUBLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
89
|
+
:ARRAY_VALUE /#{SINGLE_QUOTE_STRING}/ { [:tSTRING, text[1...-1]] }
|
90
|
+
:ARRAY_VALUE /#{IDENTIFIER_VALUE}/ { [:tIDENTIFIER_VALUE, text] }
|
91
|
+
|
92
|
+
inner
|
93
|
+
def initialize
|
94
|
+
@nested_count = 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_parse; end
|
98
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class NodeQueryParser
|
2
|
+
options no_result_var
|
3
|
+
token tCOMMA tNODE_TYPE tGOTO_SCOPE tATTRIBUTE tKEY tIDENTIFIER tIDENTIFIER_VALUE tPSEUDO_CLASS tRELATIONSHIP
|
4
|
+
tOPEN_ATTRIBUTE tCLOSE_ATTRIBUTE tOPEN_ARRAY tCLOSE_ARRAY tOPEN_SELECTOR tCLOSE_SELECTOR
|
5
|
+
tOPERATOR tARRAY_VALUE tDYNAMIC_ATTRIBUTE tBOOLEAN tFLOAT tINTEGER tNIL tREGEXP tSTRING tSYMBOL
|
6
|
+
rule
|
7
|
+
expression_list
|
8
|
+
: expression tCOMMA expression_list { NodeQuery::Compiler::ExpressionList.new(expression: val[0], rest: val[2]) }
|
9
|
+
| expression { NodeQuery::Compiler::ExpressionList.new(expression: val[0]) }
|
10
|
+
|
11
|
+
expression
|
12
|
+
: selector expression { NodeQuery::Compiler::Expression.new(selector: val[0], rest: val[1]) }
|
13
|
+
| selector { NodeQuery::Compiler::Expression.new(selector: val[0]) }
|
14
|
+
|
15
|
+
selector
|
16
|
+
: basic_selector { NodeQuery::Compiler::Selector.new(basic_selector: val[0]) }
|
17
|
+
| tPSEUDO_CLASS tOPEN_SELECTOR selector tCLOSE_SELECTOR { NodeQuery::Compiler::Selector.new(pseudo_class: val[0], pseudo_selector: val[2]) }
|
18
|
+
| tRELATIONSHIP selector { NodeQuery::Compiler::Selector.new(relationship: val[0], rest: val[1]) }
|
19
|
+
| tGOTO_SCOPE selector { NodeQuery::Compiler::Selector.new(goto_scope: val[0], rest: val[1]) }
|
20
|
+
|
21
|
+
basic_selector
|
22
|
+
: tNODE_TYPE { NodeQuery::Compiler::BasicSelector.new(node_type: val[0]) }
|
23
|
+
| tNODE_TYPE attribute_list { NodeQuery::Compiler::BasicSelector.new(node_type: val[0], attribute_list: val[1]) }
|
24
|
+
|
25
|
+
attribute_list
|
26
|
+
: tOPEN_ATTRIBUTE attribute tCLOSE_ATTRIBUTE attribute_list { NodeQuery::Compiler::AttributeList.new(attribute: val[1], rest: val[3]) }
|
27
|
+
| tOPEN_ATTRIBUTE attribute tCLOSE_ATTRIBUTE { NodeQuery::Compiler::AttributeList.new(attribute: val[1]) }
|
28
|
+
|
29
|
+
attribute
|
30
|
+
: tKEY tOPERATOR value { NodeQuery::Compiler::Attribute.new(key: val[0], value: val[2], operator: val[1]) }
|
31
|
+
| tKEY tOPERATOR tOPEN_ARRAY tCLOSE_ARRAY { NodeQuery::Compiler::Attribute.new(key: val[0], value: NodeQuery::Compiler::Array.new, operator: val[1]) }
|
32
|
+
| tKEY tOPERATOR tOPEN_ARRAY array_value tCLOSE_ARRAY { NodeQuery::Compiler::Attribute.new(key: val[0], value: val[3], operator: val[1]) }
|
33
|
+
|
34
|
+
array_value
|
35
|
+
: value array_value { NodeQuery::Compiler::ArrayValue.new(value: val[0], rest: val[1]) }
|
36
|
+
| value { NodeQuery::Compiler::ArrayValue.new(value: val[0]) }
|
37
|
+
|
38
|
+
value
|
39
|
+
: basic_selector
|
40
|
+
| tDYNAMIC_ATTRIBUTE { NodeQuery::Compiler::EvaluatedValue.new(value: val[0]) }
|
41
|
+
| tBOOLEAN { NodeQuery::Compiler::Boolean.new(value: val[0]) }
|
42
|
+
| tFLOAT { NodeQuery::Compiler::Float.new(value: val[0]) }
|
43
|
+
| tINTEGER { NodeQuery::Compiler::Integer.new(value: val[0])}
|
44
|
+
| tNIL { NodeQuery::Compiler::Nil.new(value: val[0]) }
|
45
|
+
| tREGEXP { NodeQuery::Compiler::Regexp.new(value: val[0]) }
|
46
|
+
| tSTRING { NodeQuery::Compiler::String.new(value: val[0]) }
|
47
|
+
| tSYMBOL { NodeQuery::Compiler::Symbol.new(value: val[0]) }
|
48
|
+
| tIDENTIFIER_VALUE { NodeQuery::Compiler::Identifier.new(value: val[0]) }
|
49
|
+
end
|
50
|
+
|
51
|
+
---- inner
|
52
|
+
def initialize
|
53
|
+
@lexer = NodeQueryLexer.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse string
|
57
|
+
@lexer.parse string
|
58
|
+
do_parse
|
59
|
+
end
|
60
|
+
|
61
|
+
def next_token
|
62
|
+
@lexer.next_token
|
63
|
+
end
|
data/node_query.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/node_query/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "node_query"
|
7
|
+
spec.version = NodeQuery::VERSION
|
8
|
+
spec.authors = ["Richard Huang"]
|
9
|
+
spec.email = ["flyerhzm@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "ast node query language"
|
12
|
+
spec.description = "ast node query language"
|
13
|
+
spec.homepage = "https://github.com/xinminlabs/node-query-ruby"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/xinminlabs/node-query-ruby"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/xinminlabs/node-query-ruby/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
spec.add_dependency "activesupport"
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, check out our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
data/sig/node_query.rbs
ADDED