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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +7 -0
  3. data/README.md +43 -0
  4. data/bin/janeway +130 -0
  5. data/lib/janeway/ast/array_slice_selector.rb +104 -0
  6. data/lib/janeway/ast/binary_operator.rb +101 -0
  7. data/lib/janeway/ast/boolean.rb +24 -0
  8. data/lib/janeway/ast/child_segment.rb +48 -0
  9. data/lib/janeway/ast/current_node.rb +71 -0
  10. data/lib/janeway/ast/descendant_segment.rb +44 -0
  11. data/lib/janeway/ast/error.rb +10 -0
  12. data/lib/janeway/ast/expression.rb +55 -0
  13. data/lib/janeway/ast/filter_selector.rb +61 -0
  14. data/lib/janeway/ast/function.rb +40 -0
  15. data/lib/janeway/ast/helpers.rb +27 -0
  16. data/lib/janeway/ast/identifier.rb +35 -0
  17. data/lib/janeway/ast/index_selector.rb +27 -0
  18. data/lib/janeway/ast/name_selector.rb +52 -0
  19. data/lib/janeway/ast/null.rb +23 -0
  20. data/lib/janeway/ast/number.rb +21 -0
  21. data/lib/janeway/ast/query.rb +41 -0
  22. data/lib/janeway/ast/root_node.rb +50 -0
  23. data/lib/janeway/ast/selector.rb +32 -0
  24. data/lib/janeway/ast/string_type.rb +21 -0
  25. data/lib/janeway/ast/unary_operator.rb +26 -0
  26. data/lib/janeway/ast/wildcard_selector.rb +46 -0
  27. data/lib/janeway/error.rb +23 -0
  28. data/lib/janeway/functions/count.rb +39 -0
  29. data/lib/janeway/functions/length.rb +33 -0
  30. data/lib/janeway/functions/match.rb +69 -0
  31. data/lib/janeway/functions/search.rb +63 -0
  32. data/lib/janeway/functions/value.rb +47 -0
  33. data/lib/janeway/functions.rb +62 -0
  34. data/lib/janeway/interpreter.rb +644 -0
  35. data/lib/janeway/lexer.rb +514 -0
  36. data/lib/janeway/location.rb +3 -0
  37. data/lib/janeway/parser.rb +608 -0
  38. data/lib/janeway/token.rb +39 -0
  39. data/lib/janeway/version.rb +5 -0
  40. data/lib/janeway.rb +51 -0
  41. 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