janeway-jsonpath 0.1.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/LICENSE +7 -0
- data/README.md +43 -0
- data/bin/janeway +130 -0
- data/lib/janeway/ast/array_slice_selector.rb +104 -0
- data/lib/janeway/ast/binary_operator.rb +101 -0
- data/lib/janeway/ast/boolean.rb +24 -0
- data/lib/janeway/ast/child_segment.rb +48 -0
- data/lib/janeway/ast/current_node.rb +71 -0
- data/lib/janeway/ast/descendant_segment.rb +44 -0
- data/lib/janeway/ast/error.rb +10 -0
- data/lib/janeway/ast/expression.rb +55 -0
- data/lib/janeway/ast/filter_selector.rb +61 -0
- data/lib/janeway/ast/function.rb +40 -0
- data/lib/janeway/ast/helpers.rb +27 -0
- data/lib/janeway/ast/identifier.rb +35 -0
- data/lib/janeway/ast/index_selector.rb +27 -0
- data/lib/janeway/ast/name_selector.rb +52 -0
- data/lib/janeway/ast/null.rb +23 -0
- data/lib/janeway/ast/number.rb +21 -0
- data/lib/janeway/ast/query.rb +41 -0
- data/lib/janeway/ast/root_node.rb +50 -0
- data/lib/janeway/ast/selector.rb +32 -0
- data/lib/janeway/ast/string_type.rb +21 -0
- data/lib/janeway/ast/unary_operator.rb +26 -0
- data/lib/janeway/ast/wildcard_selector.rb +46 -0
- data/lib/janeway/error.rb +23 -0
- data/lib/janeway/functions/count.rb +39 -0
- data/lib/janeway/functions/length.rb +33 -0
- data/lib/janeway/functions/match.rb +69 -0
- data/lib/janeway/functions/search.rb +63 -0
- data/lib/janeway/functions/value.rb +47 -0
- data/lib/janeway/functions.rb +62 -0
- data/lib/janeway/interpreter.rb +644 -0
- data/lib/janeway/lexer.rb +514 -0
- data/lib/janeway/location.rb +3 -0
- data/lib/janeway/parser.rb +608 -0
- data/lib/janeway/token.rb +39 -0
- data/lib/janeway/version.rb +5 -0
- data/lib/janeway.rb +51 -0
- metadata +92 -0
@@ -0,0 +1,644 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Janeway
|
4
|
+
# Tree-walk interpreter to apply the operations from the abstract syntax tree to the input value
|
5
|
+
class Interpreter
|
6
|
+
attr_reader :query, :output, :env, :call_stack
|
7
|
+
|
8
|
+
class Error < Janeway::Error; end
|
9
|
+
|
10
|
+
# Specify the parameter types that built-in JsonPath functions require
|
11
|
+
FUNCTION_PARAMETER_TYPES = {
|
12
|
+
length: [:value_type],
|
13
|
+
count: [:nodes_type],
|
14
|
+
match: %i[value_type value_type],
|
15
|
+
search: %i[value_type value_type],
|
16
|
+
value: [:nodes_type],
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# Functions may accept or return this special value
|
20
|
+
NOTHING = :nothing
|
21
|
+
|
22
|
+
# Interpret a query on the given input, return result
|
23
|
+
# @param input [Hash, Array]
|
24
|
+
# @param query [String]
|
25
|
+
def self.interpret(input, query)
|
26
|
+
raise ArgumentError, "expect query string, got #{query.inspect}" unless query.is_a?(String)
|
27
|
+
|
28
|
+
tokens = Lexer.lex(query)
|
29
|
+
ast = Parser.new(tokens).parse
|
30
|
+
new(input).interpret(ast)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param input [Array,Hash] tree of data which the jsonpath query is addressing
|
34
|
+
def initialize(input)
|
35
|
+
@input = input
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param ast [AST::Query] abstract syntax tree
|
39
|
+
# @return [Object]
|
40
|
+
def interpret(ast)
|
41
|
+
@query = ast
|
42
|
+
raise "expect AST, got #{ast.inspect}" unless ast.is_a?(AST::Query)
|
43
|
+
|
44
|
+
unless @input.is_a?(Hash) || @input.is_a?(Array)
|
45
|
+
return [] # can't query on any other types
|
46
|
+
end
|
47
|
+
|
48
|
+
interpret_node(ast.root, nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Interpret AST::RootNode, which refers to the complete original input.
|
54
|
+
#
|
55
|
+
# Ignore the given _input from the current interpretation state.
|
56
|
+
# RootNode starts from the top level regardless of current state.
|
57
|
+
# @return [Array]
|
58
|
+
def interpret_root_node(node, _input)
|
59
|
+
case node&.value
|
60
|
+
when AST::ChildSegment then interpret_child_segment(node.value, @input)
|
61
|
+
when AST::DescendantSegment then interpret_descendant_segment(node.value, @input)
|
62
|
+
when AST::Selector then interpret_selector(node.value, @input)
|
63
|
+
when nil then [@input]
|
64
|
+
else
|
65
|
+
raise "don't know how to interpret #{node.value.class}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Prepare a single node from an input node list to be sent to a selector.
|
70
|
+
# (Selectors require a node list as input)
|
71
|
+
# Helper method for interpret_child_segment.
|
72
|
+
#
|
73
|
+
# @param node [Object]
|
74
|
+
def as_node_list(node)
|
75
|
+
# FIXME: method still used? Can this be delted?
|
76
|
+
result =
|
77
|
+
case node
|
78
|
+
when Array then node
|
79
|
+
when Hash then node
|
80
|
+
else [node]
|
81
|
+
end
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
# Interpret a list of 1 or more selectors, seperated by the union operator.
|
86
|
+
#
|
87
|
+
# @param child_segment [AST::ChildSegment]
|
88
|
+
# @param input [Array, Hash]
|
89
|
+
# @return [Array]
|
90
|
+
def interpret_child_segment(child_segment, input)
|
91
|
+
# For each node in the input nodelist, the resulting nodelist of a child
|
92
|
+
# segment is the concatenation of the nodelists from each of its
|
93
|
+
# selectors in the order that the selectors appear in the list. Note: Any
|
94
|
+
# node matched by more than one selector is kept as many times in the nodelist.
|
95
|
+
# combine results from all selectors
|
96
|
+
results = nil
|
97
|
+
if child_segment.size == 1
|
98
|
+
selector = child_segment.first
|
99
|
+
results = send(:"interpret_#{selector.type}", selector, input)
|
100
|
+
else
|
101
|
+
results = []
|
102
|
+
child_segment.each do |selector|
|
103
|
+
result = send(:"interpret_#{selector.type}", selector, input)
|
104
|
+
results.concat(result)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Send result to the next node in the AST, if any
|
110
|
+
child = child_segment.child
|
111
|
+
unless child
|
112
|
+
return child_segment.size == 1 ? [results] : results
|
113
|
+
end
|
114
|
+
|
115
|
+
send(:"interpret_#{child.type}", child, results)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Filter the input by returning the key that has the given name.
|
119
|
+
#
|
120
|
+
# Must differentiate between a null value of a key that exists (nil)
|
121
|
+
# and a key that does not exist ([])
|
122
|
+
#
|
123
|
+
# @param selector [NameSelector]
|
124
|
+
# @return [Array]
|
125
|
+
def interpret_name_selector(selector, input)
|
126
|
+
return [] unless input.is_a?(Hash) && input.key?(selector.name)
|
127
|
+
|
128
|
+
result = input[selector.name]
|
129
|
+
|
130
|
+
# early exit, no point continuing the chain with no results
|
131
|
+
|
132
|
+
return [result] unless selector.child
|
133
|
+
|
134
|
+
# Interpret child using output of this name selector, and return result
|
135
|
+
child = selector.child
|
136
|
+
results = send(:"interpret_#{child.type}", child, result)
|
137
|
+
results
|
138
|
+
end
|
139
|
+
|
140
|
+
# Filter the input by returning the array element with the given index.
|
141
|
+
# Return empty list if input is not an array, or if array does not contain index.
|
142
|
+
#
|
143
|
+
# Output is an array because all selectors must return node lists, even if
|
144
|
+
# they only select a single element.
|
145
|
+
#
|
146
|
+
# @param selector [IndexSelector]
|
147
|
+
# @param input [Array]
|
148
|
+
# @return [Array]
|
149
|
+
def interpret_index_selector(selector, input)
|
150
|
+
return [] unless input.is_a?(Array)
|
151
|
+
|
152
|
+
result = input.fetch(selector.value) # raises IndexError if no such index
|
153
|
+
|
154
|
+
# Interpret child using output of this name selector, and return result
|
155
|
+
child = selector.child
|
156
|
+
return [result] unless child
|
157
|
+
|
158
|
+
results = send(:"interpret_#{child.type}", child, result)
|
159
|
+
results
|
160
|
+
rescue IndexError
|
161
|
+
[] # returns empty array if no such index
|
162
|
+
end
|
163
|
+
|
164
|
+
# Return values from the input.
|
165
|
+
# For array, return the array.
|
166
|
+
# For Hash, return hash values.
|
167
|
+
# For anything else, return empty list.
|
168
|
+
#
|
169
|
+
# @param selector [AST::WildcardSelector]
|
170
|
+
# @param input [Object] usually Array or Hash
|
171
|
+
# @return [Array] matching values
|
172
|
+
def interpret_wildcard_selector(selector, input)
|
173
|
+
values =
|
174
|
+
case input
|
175
|
+
when Array then input
|
176
|
+
when Hash then input.values
|
177
|
+
else []
|
178
|
+
end
|
179
|
+
|
180
|
+
return values if values.empty? # early exit, no need for further processing on empty list
|
181
|
+
return values unless selector.child
|
182
|
+
|
183
|
+
# Apply next selector to every value
|
184
|
+
# FIXME: is this correct?? Makes the CTS pass "basic, wildcard shorthand, then name shorthand"
|
185
|
+
child = selector.child
|
186
|
+
results = []
|
187
|
+
values.each do |value|
|
188
|
+
result = send(:"interpret_#{child.type}", child, value)
|
189
|
+
results << result.first unless result.empty?
|
190
|
+
end
|
191
|
+
results
|
192
|
+
end
|
193
|
+
|
194
|
+
# Filter the input by applying the array slice selector.
|
195
|
+
# Returns at most 1 element.
|
196
|
+
#
|
197
|
+
# @param selector [ArraySliceSelector]
|
198
|
+
# @param input [Array]
|
199
|
+
# @return [Array]
|
200
|
+
def interpret_array_slice_selector(selector, input)
|
201
|
+
return [] unless input.is_a?(Array)
|
202
|
+
return [] if selector.step && selector.step.zero? # IETF: When step is 0, no elements are selected.
|
203
|
+
|
204
|
+
# Calculate the "real" start and end index based on the array size
|
205
|
+
start_index = selector.start_index(input.size)
|
206
|
+
last_index = selector.end_index(input.size)
|
207
|
+
|
208
|
+
|
209
|
+
# Collect values from target indices.
|
210
|
+
results = start_index
|
211
|
+
.step(to: last_index, by: selector.step)
|
212
|
+
.map { |i| input[i] }
|
213
|
+
|
214
|
+
# Interpret child using output of this name selector, and return result
|
215
|
+
child = selector.child
|
216
|
+
return results unless child
|
217
|
+
|
218
|
+
send(:"interpret_#{child.type}", child, results)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Return the set of values from the input for which the filter is true.
|
222
|
+
# For array, acts on array values.
|
223
|
+
# For hash, acts on hash values.
|
224
|
+
# Otherwise returns empty node list.
|
225
|
+
#
|
226
|
+
# @param selector [AST::FilterSelector]
|
227
|
+
# @param input [Object]
|
228
|
+
# @return [Array] list of matched values, or nil if no matched values
|
229
|
+
def interpret_filter_selector(selector, input)
|
230
|
+
values =
|
231
|
+
case input
|
232
|
+
when Array then input
|
233
|
+
when Hash then input.values
|
234
|
+
else return []
|
235
|
+
end
|
236
|
+
|
237
|
+
results = []
|
238
|
+
values.each do |value|
|
239
|
+
|
240
|
+
# Run filter and interpret result
|
241
|
+
result = interpret_node(selector.value, value)
|
242
|
+
|
243
|
+
case result
|
244
|
+
when TrueClass then results << value # comparison test - pass
|
245
|
+
when FalseClass then nil # comparison test - fail
|
246
|
+
when Array then results << value unless result.empty? # existence test - node list
|
247
|
+
else
|
248
|
+
results << value # existence test. Null values here == success.
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
return results unless selector.child
|
253
|
+
|
254
|
+
# Interpret child using output of this name selector, and return result
|
255
|
+
child = selector.child
|
256
|
+
send(:"interpret_#{child.type}", child, results)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Combine results from selectors into a single list.
|
260
|
+
# Duplicate elements are allowed.
|
261
|
+
#
|
262
|
+
# @param lhs [Array] left hand side
|
263
|
+
# @param rhs [Array] right hand side
|
264
|
+
# @return [Array]
|
265
|
+
def interpret_union(lhs, rhs)
|
266
|
+
if lhs.is_a?(Array) && rhs.is_a?(Array)
|
267
|
+
# can't use ruby's array union operator "|" here because it eliminates duplicates
|
268
|
+
lhs.concat rhs
|
269
|
+
else
|
270
|
+
[lhs, rhs]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Find all descendants of the current input that match the selector in the DescendantSegment
|
275
|
+
#
|
276
|
+
# @param descendant_segment [DescendantSegment]
|
277
|
+
# @param input [Object]
|
278
|
+
# @return [Array<AST::Expression>] node list
|
279
|
+
def interpret_descendant_segment(descendant_segment, input)
|
280
|
+
results = visit(input) { |node| interpret_node(descendant_segment.selector, node) }
|
281
|
+
|
282
|
+
return results unless descendant_segment.child
|
283
|
+
|
284
|
+
child = descendant_segment.child
|
285
|
+
send(:"interpret_#{child.type}", child, results)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Visit all descendants of `root`.
|
289
|
+
# Return results of applying `action` on each.
|
290
|
+
def visit(root, &action)
|
291
|
+
results = [yield(root)]
|
292
|
+
|
293
|
+
case root
|
294
|
+
when Array
|
295
|
+
results.concat(root.map { |elt| visit(elt, &action) })
|
296
|
+
when Hash
|
297
|
+
results.concat(root.values.map { |value| visit(value, &action) })
|
298
|
+
else
|
299
|
+
root
|
300
|
+
end
|
301
|
+
|
302
|
+
results.flatten(1).compact
|
303
|
+
end
|
304
|
+
|
305
|
+
def interpret_nodes(nodes)
|
306
|
+
last_value = nil
|
307
|
+
|
308
|
+
nodes.each do |node|
|
309
|
+
last_value = interpret_node(node, last_value)
|
310
|
+
end
|
311
|
+
|
312
|
+
last_value
|
313
|
+
end
|
314
|
+
|
315
|
+
# Interpret an AST node.
|
316
|
+
# Return the result, which may be a node list or basic type.
|
317
|
+
# @return [Object]
|
318
|
+
def interpret_node(node, input)
|
319
|
+
interpreter_method = "interpret_#{node.type}"
|
320
|
+
send(interpreter_method, node, input)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Interpret a node and extract its value, in preparation for using the node
|
324
|
+
# in a comparison operator.
|
325
|
+
# Basic literals such as AST::Number and AST::StringType evaluate to a number or string,
|
326
|
+
# but selectors and segments evaluate to a node list. Extract the value (if any)
|
327
|
+
# from the node list, or return basic type.
|
328
|
+
#
|
329
|
+
# @param node [AST::Expression]
|
330
|
+
# @param input [Object]
|
331
|
+
def interpret_node_as_value(node, input)
|
332
|
+
|
333
|
+
# nodes must be singular queries or literals
|
334
|
+
case node
|
335
|
+
when AST::CurrentNode, AST::RootNode
|
336
|
+
raise Error, "Expression #{node} does not produce a singular value for comparison" unless node.singular_query?
|
337
|
+
when AST::Number, AST::StringType, AST::Null, AST::Function, AST::Boolean then nil
|
338
|
+
else
|
339
|
+
raise "Invalid expression for comparison: #{node}"
|
340
|
+
end
|
341
|
+
|
342
|
+
result = interpret_node(node, input)
|
343
|
+
|
344
|
+
# Return basic types (ie. from AST::Number, AST::StringType)
|
345
|
+
return result unless result.is_a?(Array)
|
346
|
+
|
347
|
+
# Node lists are returned by Selectors, ChildSegment, DescendantSegment.
|
348
|
+
#
|
349
|
+
# This is for a comparison operator.
|
350
|
+
# An empty node list represents a missing element.
|
351
|
+
# This must not match any literal value (including null /nil) but must match another missing value.
|
352
|
+
return result if result.empty?
|
353
|
+
|
354
|
+
# Return the only node in the node list
|
355
|
+
raise 'node list contains multiple elements but this is a comparison' unless result.size == 1
|
356
|
+
|
357
|
+
result.first
|
358
|
+
end
|
359
|
+
|
360
|
+
# Given the result of evaluating an expression which is presumed to be a node list,
|
361
|
+
# convert the result into a basic ruby value (ie. Integer, String, nil)
|
362
|
+
# @param node_list [Array]
|
363
|
+
# @return [String, Integer, Float, nil]
|
364
|
+
def node_result_to_value(node_list)
|
365
|
+
return nil if node_list.empty?
|
366
|
+
|
367
|
+
return node_list.first if node_list.size == 1
|
368
|
+
|
369
|
+
raise "don't know how to handle node list with size > 1: #{node_list.inspect}"
|
370
|
+
end
|
371
|
+
|
372
|
+
# Evaluate a selector and return the result
|
373
|
+
# @return [Array] node list containing evaluation result
|
374
|
+
def interpret_selector(selector, input)
|
375
|
+
case selector
|
376
|
+
when AST::NameSelector then interpret_name_selector(selector, input)
|
377
|
+
when AST::WildcardSelector then interpret_wildcard_selector(selector, input)
|
378
|
+
when AST::IndexSelector then interpret_index_selector(selector, input)
|
379
|
+
when AST::ArraySliceSelector then interpret_array_slice_selector(selector, input)
|
380
|
+
when AST::FilterSelector then interpret_filter_selector(selector, input)
|
381
|
+
else
|
382
|
+
raise "Not a selector: #{selector.inspect}"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Apply selector to each value in the current node and return result.
|
387
|
+
#
|
388
|
+
# The result is an Array containing all results of evaluating the CurrentNode's selector (if any.)
|
389
|
+
#
|
390
|
+
# If the selector extracted values from nodes such as strings, numbers or nil/null, these will be included in the array.
|
391
|
+
# If the selector did not match any node, the array may be empty.
|
392
|
+
# If there was no selector, then the current input node is returned in the array.
|
393
|
+
#
|
394
|
+
# @param current_node [CurrentNode] current node identifer
|
395
|
+
# @param input [Hash, Array]
|
396
|
+
# @return [Array] Node List containing all results from evaluating this node's selectors.
|
397
|
+
def interpret_current_node(current_node, input)
|
398
|
+
next_expr = current_node.value
|
399
|
+
result =
|
400
|
+
# All of these return a node list
|
401
|
+
case next_expr
|
402
|
+
when AST::NameSelector then interpret_name_selector(next_expr, input)
|
403
|
+
when AST::WildcardSelector then interpret_wildcard_selector(next_expr, input)
|
404
|
+
when AST::IndexSelector then interpret_index_selector(next_expr, input)
|
405
|
+
when AST::ArraySliceSelector then interpret_array_slice_selector(next_expr, input)
|
406
|
+
when AST::FilterSelector then interpret_filter_selector(next_expr, input)
|
407
|
+
when AST::ChildSegment then interpret_child_segment(next_expr, input)
|
408
|
+
when AST::DescendantSegment then interpret_descendant_segment(next_expr, input)
|
409
|
+
when NilClass then input # FIXME: put it in a node list???
|
410
|
+
else
|
411
|
+
raise "don't know how to interpret #{next_expr}"
|
412
|
+
end
|
413
|
+
result
|
414
|
+
end
|
415
|
+
|
416
|
+
def interpret_identifier(identifier, _input)
|
417
|
+
if env.key?(identifier.name)
|
418
|
+
# Global variable.
|
419
|
+
env[identifier.name]
|
420
|
+
elsif call_stack.length.positive? && call_stack.last.env.key?(identifier.name)
|
421
|
+
# Local variable.
|
422
|
+
call_stack.last.env[identifier.name]
|
423
|
+
else
|
424
|
+
# Undefined variable.
|
425
|
+
raise Janeway::Error::Runtime::UndefinedVariable, identifier.name
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# The binary operators are all comparison operators that test equality.
|
430
|
+
#
|
431
|
+
# * boolean values specified in the query
|
432
|
+
# * JSONPath expressions which must be evaluated
|
433
|
+
#
|
434
|
+
# After a JSONPath expression is evaluated, it results in a node list.
|
435
|
+
# This may contain literal values or nodes, whose value must be extracted before comparison.
|
436
|
+
#
|
437
|
+
# @return [Boolean]
|
438
|
+
def interpret_binary_operator(binary_op, input)
|
439
|
+
case binary_op.operator
|
440
|
+
when :and, :or
|
441
|
+
# handle node list for existence check
|
442
|
+
lhs = interpret_node(binary_op.left, input)
|
443
|
+
rhs = interpret_node(binary_op.right, input)
|
444
|
+
when :equal, :not_equal, :less_than, :greater_than, :less_than_or_equal, :greater_than_or_equal
|
445
|
+
# handle node values for comparison check
|
446
|
+
lhs = interpret_node_as_value(binary_op.left, input)
|
447
|
+
rhs = interpret_node_as_value(binary_op.right, input)
|
448
|
+
else
|
449
|
+
raise "don't know how to handle binary operator #{binary_op.inspect}"
|
450
|
+
end
|
451
|
+
send(:"interpret_#{binary_op.operator}", lhs, rhs)
|
452
|
+
end
|
453
|
+
|
454
|
+
def interpret_equal(lhs, rhs)
|
455
|
+
|
456
|
+
# When either side of a comparison results in an empty nodelist or the
|
457
|
+
# special result Nothing (see Section 2.4.1):
|
458
|
+
# A comparison using the operator == yields true if and only if the other
|
459
|
+
# side also results in an empty nodelist or the special result Nothing.
|
460
|
+
lhs = NOTHING if lhs == []
|
461
|
+
rhs = NOTHING if rhs == []
|
462
|
+
|
463
|
+
lhs == rhs
|
464
|
+
end
|
465
|
+
|
466
|
+
def interpret_not_equal(lhs, rhs)
|
467
|
+
!interpret_equal(lhs, rhs)
|
468
|
+
end
|
469
|
+
|
470
|
+
# Interpret && operator
|
471
|
+
# May receive node lists, in which case empty node list == false
|
472
|
+
def interpret_and(lhs, rhs)
|
473
|
+
# non-empty array is already truthy, so that works properly without conversion
|
474
|
+
lhs = false if lhs == []
|
475
|
+
rhs = false if rhs == []
|
476
|
+
lhs && rhs
|
477
|
+
end
|
478
|
+
|
479
|
+
# Interpret || operator
|
480
|
+
# May receive node lists, in which case empty node list == false
|
481
|
+
def interpret_or(lhs, rhs)
|
482
|
+
# non-empty array is already truthy, so that works properly without conversion
|
483
|
+
lhs = false if lhs.is_a?(Array) && lhs.empty?
|
484
|
+
rhs = false if rhs.is_a?(Array) && rhs.empty?
|
485
|
+
lhs || rhs
|
486
|
+
end
|
487
|
+
|
488
|
+
def interpret_less_than(lhs, rhs)
|
489
|
+
lhs < rhs
|
490
|
+
rescue StandardError
|
491
|
+
false
|
492
|
+
end
|
493
|
+
|
494
|
+
def interpret_less_than_or_equal(lhs, rhs)
|
495
|
+
# Must be done in 2 comparisons, because the equality comparison is
|
496
|
+
# valid for many types that do not support the < operator.
|
497
|
+
return true if interpret_equal(lhs, rhs)
|
498
|
+
|
499
|
+
lhs < rhs
|
500
|
+
rescue StandardError
|
501
|
+
# This catches type mismatches like { a: 1 } <= 1
|
502
|
+
# IETF says that both < and > return false for such comparisons
|
503
|
+
false
|
504
|
+
end
|
505
|
+
|
506
|
+
def interpret_greater_than(lhs, rhs)
|
507
|
+
lhs > rhs
|
508
|
+
rescue StandardError
|
509
|
+
false
|
510
|
+
end
|
511
|
+
|
512
|
+
def interpret_greater_than_or_equal(lhs, rhs)
|
513
|
+
return true if interpret_equal(lhs, rhs)
|
514
|
+
|
515
|
+
lhs > rhs
|
516
|
+
rescue StandardError
|
517
|
+
false
|
518
|
+
end
|
519
|
+
|
520
|
+
# @param boolean [AST::Boolean]
|
521
|
+
# @return [Boolean]
|
522
|
+
def interpret_boolean(boolean, _input)
|
523
|
+
boolean.value
|
524
|
+
end
|
525
|
+
|
526
|
+
# @param number [AST::Number]
|
527
|
+
# @return [Integer, Float]
|
528
|
+
def interpret_number(number, _input)
|
529
|
+
number.value
|
530
|
+
end
|
531
|
+
|
532
|
+
# @param string [AST::StringType]
|
533
|
+
# @return [String]
|
534
|
+
def interpret_string_type(string, _input)
|
535
|
+
string.value
|
536
|
+
end
|
537
|
+
|
538
|
+
# @param _null [AST::Null] ignored
|
539
|
+
# @param _input [Object] ignored
|
540
|
+
def interpret_null(_null, _input)
|
541
|
+
nil
|
542
|
+
end
|
543
|
+
|
544
|
+
# FIXME: split implementation out into separate methods for not and minus
|
545
|
+
# because they are so different.
|
546
|
+
def interpret_unary_operator(op, input)
|
547
|
+
node_list = send(:"interpret_#{op.operand.type}", op.operand, input)
|
548
|
+
case op.operator
|
549
|
+
when :not then interpret_not(node_list)
|
550
|
+
when :minus then 0 - node_list.first # FIXME: sure hope this is a number!
|
551
|
+
else raise "unknown unary operator #{op.inspect}"
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
# Interpret unary operator "!".
|
556
|
+
# For a node list, this is an existence check that just determines if the list is empty.
|
557
|
+
# For a boolean, this inverts the meaning of the input.
|
558
|
+
# @return [Boolean]
|
559
|
+
def interpret_not(input)
|
560
|
+
result =
|
561
|
+
case input
|
562
|
+
when Array then input.empty?
|
563
|
+
when TrueClass, FalseClass then !input
|
564
|
+
else
|
565
|
+
raise "don't know how to apply not operator to #{input.inspect}"
|
566
|
+
end
|
567
|
+
result
|
568
|
+
end
|
569
|
+
|
570
|
+
# @param function [AST::Function]
|
571
|
+
# @param input [Hash, Array]
|
572
|
+
def interpret_function(function, input)
|
573
|
+
params = evaluate_function_parameters(function.parameters, function.name, input)
|
574
|
+
result = function.body.call(*params)
|
575
|
+
result
|
576
|
+
end
|
577
|
+
|
578
|
+
# Evaluate the expressions in the parameter list to make the parameter values
|
579
|
+
# to pass in to a JsonPath function.
|
580
|
+
#
|
581
|
+
# The node lists returned by a singulare query must be deconstructed into a single value for
|
582
|
+
# parameters of ValueType, this is done here.
|
583
|
+
# For explanation:
|
584
|
+
# @see https://www.rfc-editor.org/rfc/rfc9535.html#name-well-typedness-of-function-
|
585
|
+
#
|
586
|
+
# @param parameters [Array] parameters before evaluation
|
587
|
+
# @param func [String] function name (eg. "length", "count")
|
588
|
+
# @param input [Object]
|
589
|
+
# @return [Array] parameters after evaluation
|
590
|
+
def evaluate_function_parameters(parameters, func, input)
|
591
|
+
param_types = FUNCTION_PARAMETER_TYPES[func.to_sym]
|
592
|
+
|
593
|
+
parameters.map.with_index do |parameter, i|
|
594
|
+
type = param_types[i]
|
595
|
+
case parameter
|
596
|
+
when AST::CurrentNode, AST::RootNode
|
597
|
+
# Selectors always return a node list.
|
598
|
+
# Deconstruct the resulting node list if function parameter type is ValueType.
|
599
|
+
result = interpret_node(parameter, input)
|
600
|
+
if type == :value_type && parameter.singular_query?
|
601
|
+
deconstruct(result)
|
602
|
+
else
|
603
|
+
result
|
604
|
+
end
|
605
|
+
when AST::StringType, AST::Number
|
606
|
+
interpret_string_type(parameter, input)
|
607
|
+
else
|
608
|
+
# invalid parameter type. Function must accept it and return Nothing result
|
609
|
+
parameter
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# Prepare a value to be passed to as a parameter with type ValueType.
|
615
|
+
# Singular queries (see RFC) produce a node list containing one value.
|
616
|
+
# Return the value.
|
617
|
+
#
|
618
|
+
# Implements this part of the RFC:
|
619
|
+
# > When the declared type of the parameter is ValueType and
|
620
|
+
# the argument is one of the following:
|
621
|
+
# > ...
|
622
|
+
# >
|
623
|
+
# > A singular query. In this case:
|
624
|
+
# > * If the query results in a nodelist consisting of a single node,
|
625
|
+
# the argument is the value of the node.
|
626
|
+
# > * If the query results in an empty nodelist, the argument is
|
627
|
+
# the special result Nothing.
|
628
|
+
#
|
629
|
+
# @param input [Object] usually an array - sometimes a basic type like String, Numeric
|
630
|
+
# @return [Object] basic type -- string or number
|
631
|
+
def deconstruct(input)
|
632
|
+
return input unless input.is_a?(Array)
|
633
|
+
|
634
|
+
if input.size == 1
|
635
|
+
# FIXME: what if it was a size 1 array that was intended to be a node not a node list? How to detect this?
|
636
|
+
input.first
|
637
|
+
elsif input.empty?
|
638
|
+
NOTHING
|
639
|
+
else
|
640
|
+
input # input is a single node, which happens to be an Array
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
end
|