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