janeway-jsonpath 1.0.0 → 1.2.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 +4 -4
- data/README.md +1 -1
- data/bin/janeway +4 -2
- data/lib/janeway/ast/array_slice_selector.rb +6 -90
- data/lib/janeway/ast/binary_operator.rb +57 -51
- data/lib/janeway/ast/child_segment.rb +2 -5
- data/lib/janeway/ast/current_node.rb +2 -15
- data/lib/janeway/ast/descendant_segment.rb +0 -3
- data/lib/janeway/ast/expression.rb +34 -5
- data/lib/janeway/ast/function.rb +15 -10
- data/lib/janeway/ast/name_selector.rb +12 -7
- data/lib/janeway/ast/root_node.rb +2 -15
- data/lib/janeway/ast/selector.rb +5 -6
- data/lib/janeway/ast/string_type.rb +4 -0
- data/lib/janeway/ast/unary_operator.rb +12 -2
- data/lib/janeway/ast/wildcard_selector.rb +2 -2
- data/lib/janeway/enumerator.rb +2 -4
- data/lib/janeway/error.rb +30 -1
- data/lib/janeway/functions/count.rb +1 -8
- data/lib/janeway/functions/length.rb +1 -12
- data/lib/janeway/functions/match.rb +9 -18
- data/lib/janeway/functions/search.rb +11 -9
- data/lib/janeway/functions/value.rb +1 -7
- data/lib/janeway/functions.rb +130 -30
- data/lib/janeway/interpreter.rb +50 -31
- data/lib/janeway/interpreters/array_slice_selector_delete_if.rb +10 -13
- data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +56 -13
- data/lib/janeway/interpreters/binary_operator_interpreter.rb +18 -13
- data/lib/janeway/interpreters/descendant_segment_interpreter.rb +32 -13
- data/lib/janeway/interpreters/filter_selector_interpreter.rb +26 -34
- data/lib/janeway/interpreters/function_interpreter.rb +5 -1
- data/lib/janeway/interpreters/index_selector_delete_if.rb +0 -1
- data/lib/janeway/interpreters/iteration_helper.rb +19 -5
- data/lib/janeway/interpreters/root_node_delete_if.rb +57 -16
- data/lib/janeway/interpreters/tree_constructor.rb +0 -20
- data/lib/janeway/interpreters/wildcard_selector_delete_if.rb +16 -0
- data/lib/janeway/interpreters/yielder.rb +8 -2
- data/lib/janeway/iregexp.rb +402 -0
- data/lib/janeway/lexer.rb +64 -79
- data/lib/janeway/location.rb +3 -1
- data/lib/janeway/normalized_path.rb +11 -2
- data/lib/janeway/parser.rb +47 -35
- data/lib/janeway/query.rb +47 -2
- data/lib/janeway/token.rb +40 -5
- data/lib/janeway/version.rb +1 -1
- metadata +2 -8
- data/lib/janeway/interpreters/array_slice_selector_deleter.rb +0 -41
- data/lib/janeway/interpreters/child_segment_deleter.rb +0 -19
- data/lib/janeway/interpreters/filter_selector_deleter.rb +0 -65
- data/lib/janeway/interpreters/index_selector_deleter.rb +0 -26
- data/lib/janeway/interpreters/name_selector_deleter.rb +0 -27
- data/lib/janeway/interpreters/root_node_deleter.rb +0 -34
- data/lib/janeway/interpreters/wildcard_selector_deleter.rb +0 -32
|
@@ -11,6 +11,15 @@ module Janeway
|
|
|
11
11
|
# Characters that do not need escaping, defined by hexadecimal range
|
|
12
12
|
NORMAL_UNESCAPED_RANGES = [(0x20..0x26), (0x28..0x5B), (0x5D..0xD7FF), (0xE000..0x10FFFF)].freeze
|
|
13
13
|
|
|
14
|
+
# Fast-path check for `escape`: a single anchored regex covering the same
|
|
15
|
+
# ranges as NORMAL_UNESCAPED_RANGES. Avoids allocating a `chars` array and
|
|
16
|
+
# scanning four Range objects per character on the common all-normal case.
|
|
17
|
+
NORMAL_UNESCAPED_REGEX = Regexp.new(
|
|
18
|
+
'\A[' +
|
|
19
|
+
NORMAL_UNESCAPED_RANGES.map { |r| "\\u{#{r.min.to_s(16)}}-\\u{#{r.max.to_s(16)}}" }.join +
|
|
20
|
+
']*\z'
|
|
21
|
+
).freeze
|
|
22
|
+
|
|
14
23
|
def self.normalize(value)
|
|
15
24
|
case value
|
|
16
25
|
when String then normalize_name(value)
|
|
@@ -33,8 +42,8 @@ module Janeway
|
|
|
33
42
|
end
|
|
34
43
|
|
|
35
44
|
def self.escape(str)
|
|
36
|
-
# Common case, all chars are normal.
|
|
37
|
-
return str if
|
|
45
|
+
# Common case, all chars are normal — one regex match, no allocations.
|
|
46
|
+
return str if NORMAL_UNESCAPED_REGEX.match?(str)
|
|
38
47
|
|
|
39
48
|
# Some escaping must be done
|
|
40
49
|
str.chars.map { |char| escape_char(char) }.join
|
data/lib/janeway/parser.rb
CHANGED
|
@@ -16,6 +16,32 @@ module Janeway
|
|
|
16
16
|
UNARY_OPERATORS = %w[! -].freeze
|
|
17
17
|
BINARY_OPERATORS = %w[== != > < >= <= ,].freeze
|
|
18
18
|
LOGICAL_OPERATORS = %w[&& ||].freeze
|
|
19
|
+
|
|
20
|
+
# O(1)-lookup Hash companions to the arrays above, plus the union used by
|
|
21
|
+
# determine_infix_function. Building these inline in the hot path
|
|
22
|
+
# allocated a fresh Array per token; hoisting eliminates it.
|
|
23
|
+
UNARY_OPERATOR_SET = UNARY_OPERATORS.each_with_object({}) { |op, h| h[op] = true }.freeze
|
|
24
|
+
BINARY_OPERATOR_SET = BINARY_OPERATORS.each_with_object({}) { |op, h| h[op] = true }.freeze
|
|
25
|
+
INFIX_OPERATOR_SET =
|
|
26
|
+
(BINARY_OPERATORS + LOGICAL_OPERATORS).each_with_object({}) { |op, h| h[op] = true }.freeze
|
|
27
|
+
PARSE_METHOD_TYPES =
|
|
28
|
+
%I[identifier number string true false nil function if while root current_node]
|
|
29
|
+
.each_with_object({}) { |t, h| h[t] = true }.freeze
|
|
30
|
+
TERMINATOR_TYPES = %I[child_end union eof].each_with_object({}) { |t, h| h[t] = true }.freeze
|
|
31
|
+
|
|
32
|
+
# O(1) dispatch table for determine_parsing_function's type-driven cases.
|
|
33
|
+
# Sits next to the PARSE_METHOD_TYPES set (the other type branch) so both
|
|
34
|
+
# are visible when adding a new token type.
|
|
35
|
+
PARSE_DISPATCH_BY_TYPE = {
|
|
36
|
+
group_start: :parse_grouped_expr,
|
|
37
|
+
:"\n" => :parse_terminator,
|
|
38
|
+
eof: :parse_terminator,
|
|
39
|
+
child_start: :parse_child_segment,
|
|
40
|
+
dot: :parse_dot_notation,
|
|
41
|
+
descendants: :parse_descendant_segment,
|
|
42
|
+
filter: :parse_filter_selector,
|
|
43
|
+
null: :parse_null,
|
|
44
|
+
}.freeze
|
|
19
45
|
LOWEST_PRECEDENCE = 0
|
|
20
46
|
OPERATOR_PRECEDENCE = {
|
|
21
47
|
',' => 0,
|
|
@@ -65,12 +91,8 @@ module Janeway
|
|
|
65
91
|
|
|
66
92
|
private
|
|
67
93
|
|
|
68
|
-
def pending_tokens?
|
|
69
|
-
@next_p < tokens.length
|
|
70
|
-
end
|
|
71
|
-
|
|
72
94
|
def next_not_terminator?
|
|
73
|
-
next_token && next_token.type != :
|
|
95
|
+
next_token && next_token.type != :eof
|
|
74
96
|
end
|
|
75
97
|
|
|
76
98
|
# Make "next" token become "current" by moving the pointer
|
|
@@ -135,33 +157,21 @@ module Janeway
|
|
|
135
157
|
end
|
|
136
158
|
|
|
137
159
|
def determine_parsing_function
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
:parse_child_segment
|
|
149
|
-
elsif current.type == :dot # .
|
|
150
|
-
:parse_dot_notation
|
|
151
|
-
elsif current.type == :descendants # ..
|
|
152
|
-
:parse_descendant_segment
|
|
153
|
-
elsif current.type == :filter # ?
|
|
154
|
-
:parse_filter_selector
|
|
155
|
-
elsif current.type == :null # null
|
|
156
|
-
:parse_null
|
|
157
|
-
else
|
|
158
|
-
raise err("Don't know how to parse #{current}")
|
|
159
|
-
end
|
|
160
|
+
# Two type-driven tables plus a lexeme-driven check for unary operators.
|
|
161
|
+
# PARSE_METHOD_TYPES synthesizes the method name (`:parse_<type>`);
|
|
162
|
+
# PARSE_DISPATCH_BY_TYPE maps to a fixed method name.
|
|
163
|
+
return :"parse_#{current.type}" if PARSE_METHOD_TYPES.include?(current.type)
|
|
164
|
+
|
|
165
|
+
dispatch = PARSE_DISPATCH_BY_TYPE[current.type]
|
|
166
|
+
return dispatch if dispatch
|
|
167
|
+
return :parse_unary_operator if UNARY_OPERATOR_SET.include?(current.lexeme)
|
|
168
|
+
|
|
169
|
+
raise err("Don't know how to parse #{current}")
|
|
160
170
|
end
|
|
161
171
|
|
|
162
172
|
# @return [nil, Symbol]
|
|
163
173
|
def determine_infix_function(token = current)
|
|
164
|
-
return unless
|
|
174
|
+
return unless INFIX_OPERATOR_SET.include?(token.lexeme)
|
|
165
175
|
|
|
166
176
|
:parse_binary_operator
|
|
167
177
|
end
|
|
@@ -174,7 +184,7 @@ module Janeway
|
|
|
174
184
|
end
|
|
175
185
|
|
|
176
186
|
def parse_string
|
|
177
|
-
AST::StringType.new(current.literal)
|
|
187
|
+
AST::StringType.new(current.literal).tap { |node| node.token = current }
|
|
178
188
|
end
|
|
179
189
|
|
|
180
190
|
def parse_number
|
|
@@ -198,14 +208,16 @@ module Janeway
|
|
|
198
208
|
end
|
|
199
209
|
|
|
200
210
|
# '-' must be followed by a number token.
|
|
201
|
-
#
|
|
211
|
+
# Consume the minus, then replace the following number token in the
|
|
212
|
+
# token stream with a copy that has the negated literal. Replacing —
|
|
213
|
+
# rather than mutating in place — keeps Token immutable.
|
|
202
214
|
consume
|
|
203
|
-
parse_number
|
|
204
215
|
unless current.literal.is_a?(Numeric)
|
|
205
216
|
raise err("Minus operator \"-\" must be followed by number, got #{current.lexeme.inspect}")
|
|
206
217
|
end
|
|
207
218
|
|
|
208
|
-
|
|
219
|
+
original = current
|
|
220
|
+
@tokens[@next_p - 1] = Token.new(original.type, original.lexeme, -original.literal, original.location)
|
|
209
221
|
current
|
|
210
222
|
end
|
|
211
223
|
|
|
@@ -481,11 +493,10 @@ module Janeway
|
|
|
481
493
|
# Feed tokens to the FilterSelector until hitting a terminator
|
|
482
494
|
def parse_filter_selector
|
|
483
495
|
selector = AST::FilterSelector.new
|
|
484
|
-
|
|
485
|
-
while next_token && !terminator_types.include?(next_token.type)
|
|
496
|
+
while next_token && !TERMINATOR_TYPES.include?(next_token.type)
|
|
486
497
|
consume
|
|
487
498
|
node =
|
|
488
|
-
if
|
|
499
|
+
if BINARY_OPERATOR_SET.include?(current.lexeme)
|
|
489
500
|
parse_binary_operator(selector.value)
|
|
490
501
|
else
|
|
491
502
|
parse_expr_recursively
|
|
@@ -529,6 +540,7 @@ module Janeway
|
|
|
529
540
|
|
|
530
541
|
consume
|
|
531
542
|
op.right = parse_expr_recursively(op_precedence)
|
|
543
|
+
op.validate!
|
|
532
544
|
|
|
533
545
|
op
|
|
534
546
|
end
|
data/lib/janeway/query.rb
CHANGED
|
@@ -43,6 +43,26 @@ module Janeway
|
|
|
43
43
|
@root.singular_query?
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# Return a cached interpreter tree for the given type, building it via
|
|
47
|
+
# the block on the first call. Only stateless interpreter types
|
|
48
|
+
# (`:finder`, `:deleter`) benefit from caching — `:iterator` and
|
|
49
|
+
# `:delete_if` capture the caller's block, so their trees are
|
|
50
|
+
# per-call.
|
|
51
|
+
#
|
|
52
|
+
# No-op (yields fresh every time) if the Query is frozen — respecting
|
|
53
|
+
# the docstring's "can be frozen" contract.
|
|
54
|
+
#
|
|
55
|
+
# @param type [Symbol] :finder, :deleter, :iterator, :delete_if
|
|
56
|
+
# @yield build the tree if not cached
|
|
57
|
+
# @return [Interpreters::Base]
|
|
58
|
+
def interpreter_tree_for(type)
|
|
59
|
+
return yield unless %i[finder deleter].include?(type)
|
|
60
|
+
return yield if frozen?
|
|
61
|
+
|
|
62
|
+
@interpreter_tree_cache ||= {}
|
|
63
|
+
@interpreter_tree_cache[type] ||= yield
|
|
64
|
+
end
|
|
65
|
+
|
|
46
66
|
# Return a list of the nodes in the AST.
|
|
47
67
|
# The AST of a jsonpath query is a straight line, so this is expressible as an array.
|
|
48
68
|
# The only part of the AST with branches is inside a filter selector, but that doesn't show up here.
|
|
@@ -76,10 +96,23 @@ module Janeway
|
|
|
76
96
|
result.flatten.join("\n")
|
|
77
97
|
end
|
|
78
98
|
|
|
79
|
-
# Deep copy the query
|
|
99
|
+
# Deep copy the query.
|
|
100
|
+
#
|
|
101
|
+
# Only the top-level linked list of segments/selectors is cloned — that is
|
|
102
|
+
# the only part `pop` mutates. Nested contents (filter expressions,
|
|
103
|
+
# function parameters, child-segment members) are shared with the original,
|
|
104
|
+
# which is safe because the interpreter never mutates the AST.
|
|
105
|
+
#
|
|
106
|
+
# The tree cache is intentionally NOT shared with the dup: cached trees
|
|
107
|
+
# reference the original's top-level chain and are invalid for a dup that
|
|
108
|
+
# may later be popped.
|
|
109
|
+
#
|
|
80
110
|
# @return [Query]
|
|
81
111
|
def dup
|
|
82
|
-
|
|
112
|
+
cloned = node_list.map(&:dup)
|
|
113
|
+
cloned.each_cons(2) { |a, b| a.next = b }
|
|
114
|
+
cloned.last.next = nil
|
|
115
|
+
Query.new(cloned.first, @jsonpath)
|
|
83
116
|
end
|
|
84
117
|
|
|
85
118
|
# Delete the last element from the chain of selectors.
|
|
@@ -104,5 +137,17 @@ module Janeway
|
|
|
104
137
|
# Return the last selector
|
|
105
138
|
nodes.last
|
|
106
139
|
end
|
|
140
|
+
|
|
141
|
+
# Return a data-free split of this singular query into a "parent" Query
|
|
142
|
+
# plus the leaf selector — the parent addresses one level up from what
|
|
143
|
+
# `self` addresses. Used by Enumerator#insert to locate the container
|
|
144
|
+
# that a new value should be inserted into.
|
|
145
|
+
#
|
|
146
|
+
# @return [Array(Query, AST::Selector)] [parent_query, leaf_selector]
|
|
147
|
+
def parent_query
|
|
148
|
+
parent = dup
|
|
149
|
+
leaf = parent.pop
|
|
150
|
+
[parent, leaf]
|
|
151
|
+
end
|
|
107
152
|
end
|
|
108
153
|
end
|
data/lib/janeway/token.rb
CHANGED
|
@@ -1,24 +1,59 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'forwardable'
|
|
4
|
+
require_relative 'location'
|
|
4
5
|
|
|
5
6
|
module Janeway
|
|
6
7
|
# Tokens are produced by the lexer, they represent jsonpath query elements in a low-level, non-hierarchical way.
|
|
7
8
|
class Token
|
|
8
9
|
extend Forwardable
|
|
9
10
|
|
|
10
|
-
attr_reader :type, :lexeme, :location
|
|
11
|
+
attr_reader :type, :lexeme, :literal, :location
|
|
11
12
|
|
|
12
|
-
#
|
|
13
|
-
|
|
13
|
+
# For a string token, the index within the lexeme of each character of the decoded literal,
|
|
14
|
+
# so lexeme[offsets[i]] is where literal[i] came from. It differs from i when the string
|
|
15
|
+
# contains escape sequences or the lexeme starts with a quote. Nil for other token types.
|
|
16
|
+
# @return [Array<Integer>, nil]
|
|
17
|
+
attr_reader :offsets
|
|
14
18
|
|
|
15
19
|
def_delegators :@location, :line, :col, :length
|
|
16
20
|
|
|
17
|
-
def initialize(type, lexeme, literal, location)
|
|
21
|
+
def initialize(type, lexeme, literal, location, offsets = nil)
|
|
18
22
|
@type = type
|
|
19
23
|
@lexeme = lexeme
|
|
20
24
|
@literal = literal
|
|
21
25
|
@location = location
|
|
26
|
+
@offsets = offsets
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Find where a character of a string token's decoded value came from in the query.
|
|
30
|
+
#
|
|
31
|
+
# Relies on two invariants: offsets[i] is the index within the lexeme of the start of literal[i],
|
|
32
|
+
# and location.col is the column of the lexeme in the query.
|
|
33
|
+
# @param index [Integer] index of a character in the decoded value
|
|
34
|
+
# @return [Integer, nil] column in the query of that character (or of the backslash that starts its
|
|
35
|
+
# escape); nil when the token has no offsets, or no character at that index
|
|
36
|
+
def column_of(index)
|
|
37
|
+
return nil if @offsets.nil? || !index.between?(0, @offsets.length - 1)
|
|
38
|
+
|
|
39
|
+
offset = @offsets[index]
|
|
40
|
+
offset && (@location.col + offset)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Find where a run of characters of a string token's decoded value came from in the query.
|
|
44
|
+
# @param offset [Integer, nil] index in the decoded value of the first character
|
|
45
|
+
# @param length [Integer, nil] number of decoded characters, treated as 1 if nil or not positive
|
|
46
|
+
# @return [Location] from the first character to the start of the last one; the location of the whole
|
|
47
|
+
# token when offset is nil or the range cannot be found in the token
|
|
48
|
+
def location_of(offset, length = 1)
|
|
49
|
+
return @location unless offset
|
|
50
|
+
|
|
51
|
+
length = 1 unless length&.positive?
|
|
52
|
+
first = column_of(offset)
|
|
53
|
+
last = column_of(offset + length - 1)
|
|
54
|
+
return @location unless first && last
|
|
55
|
+
|
|
56
|
+
Location.new(first, last - first + 1)
|
|
22
57
|
end
|
|
23
58
|
|
|
24
59
|
def to_s
|
|
@@ -31,7 +66,7 @@ module Janeway
|
|
|
31
66
|
when Integer, String then @literal == other
|
|
32
67
|
when Symbol then @type == other
|
|
33
68
|
when Token
|
|
34
|
-
@type == other.type && @lexeme == other.
|
|
69
|
+
@type == other.type && @lexeme == other.lexeme && @literal == other.literal
|
|
35
70
|
else
|
|
36
71
|
raise ArgumentError, "don't know how to compare Token with #{other.inspect}"
|
|
37
72
|
end
|
data/lib/janeway/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: janeway-jsonpath
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fraser Hanson
|
|
@@ -62,35 +62,29 @@ files:
|
|
|
62
62
|
- lib/janeway/functions/value.rb
|
|
63
63
|
- lib/janeway/interpreter.rb
|
|
64
64
|
- lib/janeway/interpreters/array_slice_selector_delete_if.rb
|
|
65
|
-
- lib/janeway/interpreters/array_slice_selector_deleter.rb
|
|
66
65
|
- lib/janeway/interpreters/array_slice_selector_interpreter.rb
|
|
67
66
|
- lib/janeway/interpreters/base.rb
|
|
68
67
|
- lib/janeway/interpreters/binary_operator_interpreter.rb
|
|
69
68
|
- lib/janeway/interpreters/child_segment_delete_if.rb
|
|
70
|
-
- lib/janeway/interpreters/child_segment_deleter.rb
|
|
71
69
|
- lib/janeway/interpreters/child_segment_interpreter.rb
|
|
72
70
|
- lib/janeway/interpreters/current_node_interpreter.rb
|
|
73
71
|
- lib/janeway/interpreters/descendant_segment_interpreter.rb
|
|
74
72
|
- lib/janeway/interpreters/filter_selector_delete_if.rb
|
|
75
|
-
- lib/janeway/interpreters/filter_selector_deleter.rb
|
|
76
73
|
- lib/janeway/interpreters/filter_selector_interpreter.rb
|
|
77
74
|
- lib/janeway/interpreters/function_interpreter.rb
|
|
78
75
|
- lib/janeway/interpreters/index_selector_delete_if.rb
|
|
79
|
-
- lib/janeway/interpreters/index_selector_deleter.rb
|
|
80
76
|
- lib/janeway/interpreters/index_selector_interpreter.rb
|
|
81
77
|
- lib/janeway/interpreters/iteration_helper.rb
|
|
82
78
|
- lib/janeway/interpreters/name_selector_delete_if.rb
|
|
83
|
-
- lib/janeway/interpreters/name_selector_deleter.rb
|
|
84
79
|
- lib/janeway/interpreters/name_selector_interpreter.rb
|
|
85
80
|
- lib/janeway/interpreters/root_node_delete_if.rb
|
|
86
|
-
- lib/janeway/interpreters/root_node_deleter.rb
|
|
87
81
|
- lib/janeway/interpreters/root_node_interpreter.rb
|
|
88
82
|
- lib/janeway/interpreters/tree_constructor.rb
|
|
89
83
|
- lib/janeway/interpreters/unary_operator_interpreter.rb
|
|
90
84
|
- lib/janeway/interpreters/wildcard_selector_delete_if.rb
|
|
91
|
-
- lib/janeway/interpreters/wildcard_selector_deleter.rb
|
|
92
85
|
- lib/janeway/interpreters/wildcard_selector_interpreter.rb
|
|
93
86
|
- lib/janeway/interpreters/yielder.rb
|
|
87
|
+
- lib/janeway/iregexp.rb
|
|
94
88
|
- lib/janeway/lexer.rb
|
|
95
89
|
- lib/janeway/location.rb
|
|
96
90
|
- lib/janeway/normalized_path.rb
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'array_slice_selector_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets array slice selector and deletes matching values
|
|
8
|
-
class ArraySliceSelectorDeleter < ArraySliceSelectorInterpreter
|
|
9
|
-
# Delete values matched by the array slice selector
|
|
10
|
-
#
|
|
11
|
-
# @param input [Array, Hash] the results of processing so far
|
|
12
|
-
# @param _parent [Array, Hash] parent of the input object
|
|
13
|
-
# @param _root [Array, Hash] the entire input
|
|
14
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
15
|
-
# @return [Array]
|
|
16
|
-
def interpret(input, _parent, _root, _path)
|
|
17
|
-
return [] unless input.is_a?(Array)
|
|
18
|
-
return [] if selector&.step&.zero? # RFC: When step is 0, no elements are selected.
|
|
19
|
-
|
|
20
|
-
# Calculate the upper and lower indices of the target range
|
|
21
|
-
lower = selector.lower_index(input.size)
|
|
22
|
-
upper = selector.upper_index(input.size)
|
|
23
|
-
|
|
24
|
-
# Convert bounds and step to index values.
|
|
25
|
-
# Omit the final index, since no value is collected for that.
|
|
26
|
-
# Delete indexes from largest to smallest, so that deleting an index does
|
|
27
|
-
# not change the remaining indexes
|
|
28
|
-
results = []
|
|
29
|
-
if selector.step.positive?
|
|
30
|
-
indexes = lower.step(to: upper - 1, by: selector.step).to_a
|
|
31
|
-
indexes.reverse_each { |i| results << input.delete_at(i) }
|
|
32
|
-
results.reverse
|
|
33
|
-
else
|
|
34
|
-
indexes = upper.step(to: lower + 1, by: selector.step).to_a
|
|
35
|
-
indexes.each { |i| results << input.delete_at(i) }
|
|
36
|
-
results
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'child_segment_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Child segment interpreter with selectors that delete matching elements
|
|
8
|
-
class ChildSegmentDeleter < ChildSegmentInterpreter
|
|
9
|
-
# @param child_segment [AST::ChildSegment]
|
|
10
|
-
def initialize(child_segment)
|
|
11
|
-
super
|
|
12
|
-
@selectors =
|
|
13
|
-
child_segment.map do |expr|
|
|
14
|
-
TreeConstructor.ast_node_to_deleter(expr)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'filter_selector_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets a filter selector, and deletes matching values
|
|
8
|
-
class FilterSelectorDeleter < FilterSelectorInterpreter
|
|
9
|
-
# Interpret selector on the input.
|
|
10
|
-
# @param input [Hash] the results of processing so far
|
|
11
|
-
# @param root [Array, Hash] the entire input
|
|
12
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
13
|
-
def interpret_hash(input, root, _path)
|
|
14
|
-
# Apply filter expressions to the input data
|
|
15
|
-
results = []
|
|
16
|
-
input.each do |key, value|
|
|
17
|
-
# Run filter and interpret result
|
|
18
|
-
result = @expr.interpret(value, nil, root, [])
|
|
19
|
-
case result
|
|
20
|
-
when TrueClass then results << value # comparison test - pass
|
|
21
|
-
when FalseClass then next # comparison test - fail
|
|
22
|
-
when Array
|
|
23
|
-
next if result.empty?
|
|
24
|
-
|
|
25
|
-
results << value # existence test - node list
|
|
26
|
-
else
|
|
27
|
-
results << value # existence test. Null values here == success.
|
|
28
|
-
end
|
|
29
|
-
input.delete(key)
|
|
30
|
-
end
|
|
31
|
-
results
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Interpret selector on the input.
|
|
35
|
-
# @param input [Array] the results of processing so far
|
|
36
|
-
# @param root [Array, Hash] the entire input
|
|
37
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
38
|
-
def interpret_array(input, root, _path)
|
|
39
|
-
# Apply filter expressions to the input data
|
|
40
|
-
results = []
|
|
41
|
-
|
|
42
|
-
# Iterate in reverse order so that deletion does not alter the remaining indexes
|
|
43
|
-
i = input.size
|
|
44
|
-
input.reverse_each do |value|
|
|
45
|
-
i -= 1 # calculate array index
|
|
46
|
-
|
|
47
|
-
# Run filter and interpret result
|
|
48
|
-
result = @expr.interpret(value, nil, root, [])
|
|
49
|
-
case result
|
|
50
|
-
when TrueClass then results << value # comparison test - pass
|
|
51
|
-
when FalseClass then next # comparison test - fail
|
|
52
|
-
when Array
|
|
53
|
-
next if result.empty?
|
|
54
|
-
|
|
55
|
-
results << value # existence test - node list
|
|
56
|
-
else
|
|
57
|
-
results << value # existence test. Null values here == success.
|
|
58
|
-
end
|
|
59
|
-
input.delete_at(i)
|
|
60
|
-
end
|
|
61
|
-
results.reverse
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'index_selector_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets an index selector, and deletes the matched value.
|
|
8
|
-
class IndexSelectorDeleter < IndexSelectorInterpreter
|
|
9
|
-
# Interpret selector on the given input.
|
|
10
|
-
# @param input [Array, Hash] the results of processing so far
|
|
11
|
-
# @param _parent [Array, Hash] parent of the input object
|
|
12
|
-
# @param _root [Array, Hash] the entire input
|
|
13
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
14
|
-
def interpret(input, _parent, _root, _path)
|
|
15
|
-
return [] unless input.is_a?(Array)
|
|
16
|
-
|
|
17
|
-
index = selector.value
|
|
18
|
-
result = input.fetch(index) # raises IndexError if no such index
|
|
19
|
-
input.delete_at(index) # returns nil if deleted value is nil, or if no value was deleted
|
|
20
|
-
[result]
|
|
21
|
-
rescue IndexError
|
|
22
|
-
[]
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'name_selector_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets a name selector, and deletes the matched values.
|
|
8
|
-
class NameSelectorDeleter < NameSelectorInterpreter
|
|
9
|
-
# Interpret selector on the given input.
|
|
10
|
-
# @param input [Array, Hash] the results of processing so far
|
|
11
|
-
# @param _parent [Array, Hash] parent of the input object
|
|
12
|
-
# @param _root [Array, Hash] the entire input
|
|
13
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
14
|
-
def interpret(input, _parent, _root, _path)
|
|
15
|
-
return [] unless input.is_a?(Hash) && input.key?(name)
|
|
16
|
-
|
|
17
|
-
[input.delete(name)]
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Return hash representation of this interpreter
|
|
21
|
-
# @return [Hash]
|
|
22
|
-
def as_json
|
|
23
|
-
{ type: type, value: name, next: @next&.as_json }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'root_node_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets the root node for deletion
|
|
8
|
-
class RootNodeDeleter < RootNodeInterpreter
|
|
9
|
-
# Delete all values from the root node.
|
|
10
|
-
#
|
|
11
|
-
# TODO: unclear, for deletion is there a difference between queries '$' and '$.*'?
|
|
12
|
-
#
|
|
13
|
-
# @param _input [Array, Hash] the results of processing so far
|
|
14
|
-
# @param _parent [Array, Hash] parent of the input object
|
|
15
|
-
# @param root [Array, Hash] the entire input
|
|
16
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
17
|
-
# @return [Array] deleted elements
|
|
18
|
-
def interpret(_input, _parent, root, _path)
|
|
19
|
-
case root
|
|
20
|
-
when Array
|
|
21
|
-
results = root.dup
|
|
22
|
-
root.clear
|
|
23
|
-
results
|
|
24
|
-
when Hash
|
|
25
|
-
results = root.values
|
|
26
|
-
root.clear
|
|
27
|
-
results
|
|
28
|
-
else
|
|
29
|
-
[]
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'wildcard_selector_interpreter'
|
|
4
|
-
|
|
5
|
-
module Janeway
|
|
6
|
-
module Interpreters
|
|
7
|
-
# Interprets a wildcard selector, and deletes the results.
|
|
8
|
-
class WildcardSelectorDeleter < WildcardSelectorInterpreter
|
|
9
|
-
# Delete all elements from the input
|
|
10
|
-
#
|
|
11
|
-
# @param input [Array, Hash] the results of processing so far
|
|
12
|
-
# @param _parent [Array, Hash] parent of the input object
|
|
13
|
-
# @param _root [Array, Hash] the entire input
|
|
14
|
-
# @param _path [Array<String>] elements of normalized path to the current input
|
|
15
|
-
# @return [Array] deleted elements
|
|
16
|
-
def interpret(input, _parent, _root, _path)
|
|
17
|
-
case input
|
|
18
|
-
when Array
|
|
19
|
-
results = input.dup
|
|
20
|
-
input.clear
|
|
21
|
-
results
|
|
22
|
-
when Hash
|
|
23
|
-
results = input.values
|
|
24
|
-
input.clear
|
|
25
|
-
results
|
|
26
|
-
else
|
|
27
|
-
[]
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|