rjq 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.
data/lib/rjq/ast.rb ADDED
@@ -0,0 +1,1475 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rjq
4
+ module AST
5
+ SourceSpan = Struct.new(:filename, :line, :column, :start_offset, :end_offset, keyword_init: true)
6
+
7
+ class Context
8
+ attr_reader :variables, :functions, :options, :current_path, :binding_variable_paths
9
+
10
+ def initialize(variables: {}, functions: {}, options: {}, current_path: [], binding_variable_paths: {})
11
+ @variables = variables
12
+ @functions = functions
13
+ @options = options
14
+ @current_path = current_path
15
+ @binding_variable_paths = binding_variable_paths
16
+ end
17
+
18
+ def with_variable(name, value)
19
+ copy(variables: variables.merge(name => value))
20
+ end
21
+
22
+ def with_function(name, arity, definition)
23
+ copy(functions: functions.merge([name, arity] => definition))
24
+ end
25
+
26
+ def with_functions(new_functions)
27
+ copy(functions: new_functions)
28
+ end
29
+
30
+ def with_path_state(current_path:, variables: binding_variable_paths)
31
+ copy(current_path: current_path, binding_variable_paths: variables)
32
+ end
33
+
34
+ def with_options(new_options)
35
+ copy(options: new_options)
36
+ end
37
+
38
+ private
39
+
40
+ def copy(variables: @variables, functions: @functions, options: @options,
41
+ current_path: @current_path, binding_variable_paths: @binding_variable_paths)
42
+ self.class.new(variables: variables, functions: functions, options: options,
43
+ current_path: current_path, binding_variable_paths: binding_variable_paths)
44
+ end
45
+ end
46
+
47
+ class Node
48
+ attr_reader :source_span
49
+
50
+ def with_source_span(span)
51
+ @source_span = span
52
+ self
53
+ end
54
+
55
+ def eval(_input, _context)
56
+ raise NotImplementedError, "#{self.class}#eval"
57
+ end
58
+
59
+ def take(input, context, count)
60
+ eval(input, context).first(count)
61
+ end
62
+
63
+ def paths(_input, _context)
64
+ raise TypeError, "#{self.class} is not a path expression"
65
+ end
66
+
67
+ def invalid_path_message(result, _input, _context)
68
+ "Invalid path expression with result #{JSON::Dumper.dump(result, indent: nil)}"
69
+ end
70
+
71
+ def assign_value(copy, input, context, value)
72
+ paths(input, context).each { |path| copy = Path.set(copy, path, value) }
73
+ copy
74
+ end
75
+
76
+ private
77
+
78
+ def one(node, input, context)
79
+ values = node.eval(input, context)
80
+ values.first
81
+ end
82
+
83
+ def numeric(value)
84
+ AST.numeric(value)
85
+ end
86
+
87
+ def range_for(length, start, finish)
88
+ AST.range_for(length, start, finish)
89
+ end
90
+ end
91
+
92
+ class Program < Node
93
+ attr_reader :body, :definitions, :directives
94
+
95
+ def initialize(body, definitions = [], directives = [])
96
+ @body = body
97
+ @definitions = definitions
98
+ @directives = directives
99
+ end
100
+
101
+ def eval(input, context)
102
+ body.eval(input, context_with_definitions(context))
103
+ end
104
+
105
+ def context_with_definitions(context)
106
+ apply_definitions(context)
107
+ end
108
+
109
+ private
110
+
111
+ def apply_definitions(context)
112
+ definitions.reduce(context) do |ctx, definition|
113
+ closed = definition.with_closure(ctx.functions)
114
+ ctx_with_self = ctx.with_function(definition.name, definition.params.length, closed)
115
+ ctx_with_self.with_function(
116
+ definition.name,
117
+ definition.params.length,
118
+ definition.with_closure(ctx_with_self.functions)
119
+ )
120
+ end
121
+ end
122
+ end
123
+
124
+ ModuleDirective = Struct.new(:type, :name, :metadata, :alias_name, keyword_init: true)
125
+
126
+ class FunctionDefinition
127
+ attr_reader :name, :params, :body, :closure
128
+
129
+ def initialize(name, params, body, closure = nil)
130
+ @name = name
131
+ @params = params
132
+ @body = body
133
+ @closure = closure
134
+ end
135
+
136
+ def with_closure(functions)
137
+ self.class.new(name, params, body, functions)
138
+ end
139
+ end
140
+
141
+ class ScopedDefinition < Node
142
+ attr_reader :definition, :body
143
+
144
+ def initialize(definition, body)
145
+ @definition = definition
146
+ @body = body
147
+ end
148
+
149
+ def eval(input, context)
150
+ @body.eval(input, apply_definition(context))
151
+ end
152
+
153
+ private
154
+
155
+ def apply_definition(context)
156
+ closed = @definition.with_closure(context.functions)
157
+ context_with_self = context.with_function(@definition.name, @definition.params.length, closed)
158
+ context_with_self.with_function(
159
+ @definition.name,
160
+ @definition.params.length,
161
+ @definition.with_closure(context_with_self.functions)
162
+ )
163
+ end
164
+ end
165
+
166
+ class CapturedFilter < Node
167
+ attr_reader :node, :captured_context
168
+
169
+ def initialize(node, captured_context)
170
+ @node = node
171
+ @captured_context = captured_context
172
+ end
173
+
174
+ def eval(input, _context)
175
+ node.eval(input, captured_context)
176
+ end
177
+
178
+ def take(input, _context, count)
179
+ node.take(input, captured_context, count)
180
+ end
181
+
182
+ def paths(input, _context)
183
+ node.paths(input, captured_context)
184
+ end
185
+ end
186
+
187
+ class Identity < Node
188
+ def eval(input, _context)
189
+ [input]
190
+ end
191
+
192
+ def paths(_input, context)
193
+ [context.current_path]
194
+ end
195
+ end
196
+
197
+ class Literal < Node
198
+ attr_reader :value
199
+
200
+ def initialize(value)
201
+ @value = value
202
+ end
203
+
204
+ def eval(_input, _context)
205
+ [Value.deep_copy(value)]
206
+ end
207
+ end
208
+
209
+ class StringLiteral < Node
210
+ attr_reader :value
211
+
212
+ def initialize(value)
213
+ @value = value
214
+ end
215
+
216
+ def eval(input, context)
217
+ return [@value] if @value.is_a?(String)
218
+
219
+ @value.reduce(['']) do |prefixes, (kind, value)|
220
+ suffixes = eval_segment(kind, value, input, context)
221
+ prefixes.flat_map { |prefix| suffixes.map { |suffix| prefix + suffix } }
222
+ end
223
+ end
224
+
225
+ private
226
+
227
+ def eval_segment(kind, value, input, context)
228
+ return [value] if kind == :text
229
+
230
+ parser = Rjq::Parser.new(value)
231
+ parser.parse.eval(input, context).map { |item| Builtins.to_string(item) }
232
+ end
233
+ end
234
+
235
+ class Format < Node
236
+ attr_reader :name, :expression
237
+
238
+ def initialize(name, expression = nil)
239
+ @name = name
240
+ @expression = expression
241
+ end
242
+
243
+ def eval(input, context)
244
+ return Builtins.call(@name, input, context, []) unless @expression
245
+
246
+ if @expression.is_a?(StringLiteral) && @expression.value.is_a?(Array)
247
+ return @expression.value.reduce(['']) do |prefixes, (kind, value)|
248
+ suffixes = format_segment(kind, value, input, context)
249
+ prefixes.flat_map { |prefix| suffixes.map { |suffix| prefix + suffix } }
250
+ end
251
+ end
252
+
253
+ @expression.eval(input, context).flat_map { |value| Builtins.call(@name, value, context, []) }
254
+ end
255
+
256
+ private
257
+
258
+ def format_segment(kind, value, input, context)
259
+ return [value] if kind == :text
260
+
261
+ Parser.new(value).parse.eval(input, context).map do |item|
262
+ Builtins.to_string(Builtins.call(@name, item, context, []).first)
263
+ end
264
+ end
265
+ end
266
+
267
+ class Variable < Node
268
+ attr_reader :name
269
+
270
+ def initialize(name)
271
+ @name = name
272
+ end
273
+
274
+ def eval(_input, context)
275
+ return [ENV.to_h] if @name == 'ENV'
276
+
277
+ if @name == 'ARGS'
278
+ positional = context.variables.fetch('ARGS.positional', [])
279
+ named = context.variables.fetch('ARGS.named', {})
280
+ return [{ 'positional' => positional, 'named' => named }]
281
+ end
282
+ if @name == '__loc__'
283
+ return [{ 'file' => source_span&.filename || context.options.fetch(:source_path, '<top-level>'),
284
+ 'line' => source_span&.line || 1 }]
285
+ end
286
+
287
+ raise RuntimeError, "variable $#{@name} is not defined" unless context.variables.key?(@name)
288
+
289
+ [context.variables[@name]]
290
+ end
291
+
292
+ def paths(input, context)
293
+ return [context.binding_variable_paths.fetch(@name)] if context.binding_variable_paths.key?(@name)
294
+
295
+ result = eval(input, context).first
296
+ raise InvalidPathError.new(invalid_path_message(result, input, context), result)
297
+ end
298
+ end
299
+
300
+ class Pipe < Node
301
+ attr_reader :left, :right
302
+
303
+ def initialize(left, right)
304
+ @left = left
305
+ @right = right
306
+ end
307
+
308
+ def eval(input, context)
309
+ out = []
310
+ @left.eval(input, context).each do |value|
311
+ out.concat(@right.eval(value, context))
312
+ rescue ErrorValue => e
313
+ raise ErrorValue.new(e.value, outputs: out + (e.outputs || []))
314
+ rescue BreakSignal => e
315
+ raise BreakSignal.new(e.label, e.value, outputs: out + (e.outputs || []))
316
+ end
317
+ out
318
+ end
319
+
320
+ def paths(input, context)
321
+ @left.paths(input, context).flat_map do |path|
322
+ value = Path.get(input, path)
323
+ @right.paths(value, context).map { |suffix| path + suffix }
324
+ end
325
+ rescue InvalidPathError => e
326
+ raise InvalidPathError.new(@right.invalid_path_message(e.result, input, context), e.result)
327
+ end
328
+ end
329
+
330
+ class Comma < Node
331
+ attr_reader :left, :right
332
+
333
+ def initialize(left, right)
334
+ @left = left
335
+ @right = right
336
+ end
337
+
338
+ def eval(input, context)
339
+ left_values = @left.eval(input, context)
340
+ left_values + @right.eval(input, context)
341
+ rescue ErrorValue => e
342
+ raise ErrorValue.new(e.value, outputs: Array(left_values) + (e.outputs || []))
343
+ rescue BreakSignal => e
344
+ raise BreakSignal.new(e.label, e.value, outputs: Array(left_values) + (e.outputs || []))
345
+ end
346
+
347
+ def take(input, context, count)
348
+ left_values = @left.take(input, context, count)
349
+ return left_values if left_values.length >= count
350
+
351
+ left_values + @right.take(input, context, count - left_values.length)
352
+ end
353
+
354
+ def paths(input, context)
355
+ @left.paths(input, context) + @right.paths(input, context)
356
+ end
357
+ end
358
+
359
+ class Binding < Node
360
+ attr_reader :source, :pattern, :body
361
+
362
+ def initialize(source, pattern, body)
363
+ @source = source
364
+ @pattern = pattern
365
+ @body = body
366
+ end
367
+
368
+ def eval(input, context)
369
+ @source.eval(input, context).flat_map do |value|
370
+ bound = AST.bind_pattern(context, @pattern, value)
371
+ bound ? @body.eval(input, bound) : []
372
+ end
373
+ end
374
+
375
+ def paths(input, context)
376
+ values = @source.eval(input, context)
377
+ values.flat_map do |value|
378
+ bound, pattern_path, relative_variables = AST.bind_pattern_with_path(context, @pattern, value)
379
+ base_path = context.current_path + pattern_path
380
+ variable_paths = context.binding_variable_paths.to_h { |name, _path| [name, base_path] }
381
+ variable_paths.merge!(relative_variables.transform_values { |path| context.current_path + path })
382
+ AST.validate_pattern_path(input, base_path)
383
+ scoped = bound.with_path_state(current_path: base_path, variables: variable_paths)
384
+ AST.validate_binding_results(input, @body.eval(input, scoped), @body.paths(input, scoped),
385
+ alternative_base: base_path,
386
+ variable_paths: variable_paths.values,
387
+ alternative_paths: AST.alternative_paths(@pattern))
388
+ end
389
+ end
390
+ end
391
+
392
+ class Field < Node
393
+ attr_reader :base, :name
394
+
395
+ def initialize(base, name, optional: false)
396
+ @base = base
397
+ @name = name
398
+ @optional = optional
399
+ end
400
+
401
+ def eval(input, context)
402
+ @base.eval(input, context).flat_map do |value|
403
+ [read(value)]
404
+ rescue Rjq::RuntimeError
405
+ @optional ? [] : raise
406
+ end
407
+ end
408
+
409
+ def paths(input, context)
410
+ @base.paths(input, context).map { |path| path + [@name] }
411
+ end
412
+
413
+ def invalid_path_message(result, _input, _context)
414
+ "Invalid path expression near attempt to access element #{@name.inspect} of #{JSON::Dumper.dump(result,
415
+ indent: nil)}"
416
+ end
417
+
418
+ private
419
+
420
+ def read(value)
421
+ return nil if value.nil?
422
+ return value[@name] if value.is_a?(Hash)
423
+
424
+ raise TypeError, "Cannot index #{Value.type_of(value)} with string #{@name.inspect}"
425
+ end
426
+ end
427
+
428
+ class Index < Node
429
+ attr_reader :base, :index
430
+
431
+ def initialize(base, index, optional: false)
432
+ @base = base
433
+ @index = index
434
+ @optional = optional
435
+ end
436
+
437
+ def eval(input, context)
438
+ @base.eval(input, context).flat_map do |value|
439
+ @index.eval(input, context).map { |idx| read(value, idx) }
440
+ rescue Rjq::RuntimeError
441
+ @optional ? [] : raise
442
+ end
443
+ end
444
+
445
+ def paths(input, context)
446
+ indices = @index.eval(input, context)
447
+ @base.paths(input, context).flat_map do |path|
448
+ value = Path.get(input, path)
449
+ indices.map { |index| path + [path_index(value, index)] }
450
+ end
451
+ end
452
+
453
+ def invalid_path_message(result, input, context)
454
+ index = @index.eval(input, context).first
455
+ "Invalid path expression near attempt to access element #{JSON::Dumper.dump(index,
456
+ indent: nil)} of #{JSON::Dumper.dump(
457
+ result, indent: nil
458
+ )}"
459
+ end
460
+
461
+ private
462
+
463
+ def read(value, index)
464
+ Path.read_index(value, index)
465
+ end
466
+
467
+ def path_index(value, index)
468
+ return index unless index.is_a?(Numeric)
469
+ return index if index.respond_to?(:nan?) && index.nan?
470
+
471
+ index = index.floor
472
+ return index unless index.negative?
473
+
474
+ length =
475
+ case value
476
+ when Array
477
+ value.length
478
+ when String
479
+ value.each_char.count
480
+ else
481
+ 0
482
+ end
483
+ normalized = length + index
484
+ raise RuntimeError, 'Out of bounds negative array index' if normalized.negative?
485
+
486
+ normalized
487
+ end
488
+ end
489
+
490
+ class Slice < Node
491
+ attr_reader :base, :start_node, :finish_node
492
+
493
+ def initialize(base, start_node, finish_node, optional: false)
494
+ @base = base
495
+ @start_node = start_node
496
+ @finish_node = finish_node
497
+ @optional = optional
498
+ end
499
+
500
+ def eval(input, context)
501
+ @base.eval(input, context).flat_map do |value|
502
+ starts = @start_node ? @start_node.eval(input, context) : [nil]
503
+ finishes = @finish_node ? @finish_node.eval(input, context) : [nil]
504
+ starts.flat_map { |start| finishes.map { |finish| slice(value, start, finish) } }
505
+ rescue Rjq::RuntimeError
506
+ @optional ? [] : raise
507
+ end
508
+ end
509
+
510
+ def paths(input, context)
511
+ starts = @start_node ? @start_node.eval(input, context) : [nil]
512
+ finishes = @finish_node ? @finish_node.eval(input, context) : [nil]
513
+ @base.paths(input, context).flat_map do |path|
514
+ starts.flat_map { |start| finishes.map { |finish| path + [{ 'start' => start, 'end' => finish }] } }
515
+ end
516
+ end
517
+
518
+ def assign_value(copy, input, context, value)
519
+ @base.paths(input, context).each do |path|
520
+ starts = @start_node ? @start_node.eval(input, context) : [nil]
521
+ finishes = @finish_node ? @finish_node.eval(input, context) : [nil]
522
+ starts.each do |start|
523
+ finishes.each do |finish|
524
+ target = Path.get(copy, path)
525
+ copy = Path.set(copy, path, replace_slice(target, start, finish, value))
526
+ end
527
+ end
528
+ end
529
+ copy
530
+ end
531
+
532
+ private
533
+
534
+ def slice(value, start, finish)
535
+ return nil if value.nil?
536
+
537
+ case value
538
+ when Array
539
+ value[range_for(value.length, start, finish)] || []
540
+ when String
541
+ value.each_char.to_a[range_for(value.each_char.count, start, finish)].join
542
+ else
543
+ raise TypeError, "cannot slice #{Value.type_of(value)}"
544
+ end
545
+ end
546
+
547
+ def replace_slice(target, start, finish, replacement)
548
+ case target
549
+ when Array
550
+ raise TypeError, 'can only assign an array to an array slice' unless replacement.is_a?(Array)
551
+
552
+ range = range_for(target.length, start, finish)
553
+ target[0...range.begin] + Value.deep_copy(replacement) + target[range.end..].to_a
554
+ when String
555
+ raise TypeError, 'Cannot update string slices'
556
+ else
557
+ raise TypeError, "cannot slice #{Value.type_of(target)}"
558
+ end
559
+ end
560
+ end
561
+
562
+ class Iterate < Node
563
+ attr_reader :base
564
+
565
+ def initialize(base, optional: false)
566
+ @base = base
567
+ @optional = optional
568
+ end
569
+
570
+ def eval(input, context)
571
+ @base.eval(input, context).flat_map do |value|
572
+ iterate(value)
573
+ rescue Rjq::RuntimeError
574
+ @optional ? [] : raise
575
+ end
576
+ end
577
+
578
+ def paths(input, context)
579
+ @base.eval(input, context).flat_map do |value|
580
+ keys =
581
+ case value
582
+ when Array
583
+ (0...value.length).to_a
584
+ when Hash
585
+ value.keys
586
+ else
587
+ raise TypeError, "cannot iterate over #{Value.type_of(value)}"
588
+ end
589
+ @base.paths(input, context).flat_map { |path| keys.map { |key| path + [key] } }
590
+ end
591
+ rescue InvalidPathError => e
592
+ raise InvalidPathError.new(invalid_path_message(e.result, input, context), e.result)
593
+ end
594
+
595
+ def invalid_path_message(result, _input, _context)
596
+ "Invalid path expression near attempt to iterate through #{JSON::Dumper.dump(result, indent: nil)}"
597
+ end
598
+
599
+ private
600
+
601
+ def iterate(value)
602
+ case value
603
+ when Array
604
+ value
605
+ when Hash
606
+ value.values
607
+ else
608
+ raise TypeError, "Cannot iterate over #{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)})"
609
+ end
610
+ end
611
+ end
612
+
613
+ class Optional < Node
614
+ attr_reader :node
615
+
616
+ def initialize(node)
617
+ @node = node
618
+ end
619
+
620
+ def eval(input, context)
621
+ @node.eval(input, context)
622
+ rescue Rjq::RuntimeError
623
+ []
624
+ end
625
+ end
626
+
627
+ class ArrayLiteral < Node
628
+ attr_reader :expression
629
+
630
+ def initialize(expression)
631
+ @expression = expression
632
+ end
633
+
634
+ def eval(input, context)
635
+ [@expression ? @expression.eval(input, context) : []]
636
+ end
637
+ end
638
+
639
+ class ObjectLiteral < Node
640
+ Pair = Struct.new(:key, :value, keyword_init: true)
641
+ attr_reader :pairs
642
+
643
+ def initialize(pairs)
644
+ @pairs = pairs
645
+ end
646
+
647
+ def eval(input, context)
648
+ objects = [{}]
649
+ @pairs.each do |pair|
650
+ key_values = key_outputs(pair.key, input, context)
651
+ value_values = pair.value.eval(input, context)
652
+ objects = objects.flat_map do |object|
653
+ key_values.flat_map do |key|
654
+ value_values.map { |value| object.merge(key.to_s => value) }
655
+ end
656
+ end
657
+ end
658
+ objects
659
+ end
660
+
661
+ private
662
+
663
+ def key_outputs(key, input, context)
664
+ return StringLiteral.new(key).eval(input, context) if key.is_a?(Array)
665
+
666
+ key.is_a?(Node) ? key.eval(input, context) : [key]
667
+ end
668
+ end
669
+
670
+ class If < Node
671
+ attr_reader :condition, :then_branch, :else_branch
672
+
673
+ def initialize(condition, then_branch, else_branch)
674
+ @condition = condition
675
+ @then_branch = then_branch
676
+ @else_branch = else_branch
677
+ end
678
+
679
+ def eval(input, context)
680
+ @condition.eval(input, context).flat_map do |value|
681
+ branch = Value.truthy?(value) ? @then_branch : @else_branch
682
+ branch.eval(input, context)
683
+ end
684
+ end
685
+ end
686
+
687
+ class Try < Node
688
+ attr_reader :body, :handler
689
+
690
+ def initialize(body, handler = nil)
691
+ @body = body
692
+ @handler = handler
693
+ end
694
+
695
+ def eval(input, context)
696
+ @body.eval(input, context)
697
+ rescue Rjq::ErrorValue => e
698
+ (e.outputs || []) + (@handler ? @handler.eval(e.value, context) : [])
699
+ rescue Rjq::RuntimeError => e
700
+ @handler ? @handler.eval(e.message, context) : []
701
+ end
702
+ end
703
+
704
+ class Reduce < Node
705
+ attr_reader :generator, :variable, :initial, :update
706
+
707
+ def initialize(generator, variable, initial, update)
708
+ @generator = generator
709
+ @variable = variable
710
+ @initial = initial
711
+ @update = update
712
+ end
713
+
714
+ def eval(input, context)
715
+ accumulators = @initial.eval(input, context)
716
+ @generator.eval(input, context).each do |value|
717
+ ctx = AST.bind_pattern(context, @variable, value)
718
+ next unless ctx
719
+ accumulators = accumulators.flat_map { |accumulator| @update.eval(accumulator, ctx) }
720
+ end
721
+ accumulators
722
+ end
723
+ end
724
+
725
+ class Foreach < Node
726
+ attr_reader :generator, :variable, :initial, :update, :extract
727
+
728
+ def initialize(generator, variable, initial, update, extract)
729
+ @generator = generator
730
+ @variable = variable
731
+ @initial = initial
732
+ @update = update
733
+ @extract = extract
734
+ end
735
+
736
+ def eval(input, context)
737
+ out = []
738
+ @initial.eval(input, context).each do |initial|
739
+ accumulators = [initial]
740
+ @generator.eval(input, context).each do |value|
741
+ ctx = AST.bind_pattern(context, @variable, value)
742
+ next unless ctx
743
+ accumulators = accumulators.flat_map { |accumulator| @update.eval(accumulator, ctx) }
744
+ accumulators.each { |accumulator| out.concat(@extract ? @extract.eval(accumulator, ctx) : [accumulator]) }
745
+ rescue BreakSignal => e
746
+ raise BreakSignal.new(e.label, e.value, outputs: out + (e.outputs || []))
747
+ end
748
+ end
749
+ out
750
+ end
751
+ end
752
+
753
+ class Label < Node
754
+ attr_reader :label, :body
755
+
756
+ def initialize(label, body)
757
+ @label = label
758
+ @body = body
759
+ end
760
+
761
+ def eval(input, context)
762
+ @body.eval(input, context)
763
+ rescue BreakSignal => e
764
+ raise unless e.label == @label
765
+
766
+ return e.outputs if e.outputs
767
+
768
+ e.value.nil? ? [] : [e.value]
769
+ end
770
+ end
771
+
772
+ class Break < Node
773
+ attr_reader :label
774
+
775
+ def initialize(label)
776
+ @label = label
777
+ end
778
+
779
+ def eval(_input, _context)
780
+ raise BreakSignal, @label
781
+ end
782
+ end
783
+
784
+ class UnaryOp < Node
785
+ attr_reader :op, :expression
786
+
787
+ def initialize(op, expression)
788
+ @op = op
789
+ @expression = expression
790
+ end
791
+
792
+ def eval(input, context)
793
+ @expression.eval(input, context).map do |value|
794
+ case @op
795
+ when '-'
796
+ unless value.is_a?(Numeric)
797
+ raise TypeError,
798
+ "#{Value.type_of(value)} (#{AST.short_dump(value)}) cannot be negated"
799
+ end
800
+
801
+ next -0.0 if value.zero?
802
+
803
+ value * -1
804
+ when 'not'
805
+ !Value.truthy?(value)
806
+ else
807
+ raise "unknown unary operator #{@op}"
808
+ end
809
+ end
810
+ end
811
+ end
812
+
813
+ class BinaryOp < Node
814
+ attr_reader :left, :op, :right
815
+
816
+ def initialize(left, op, right)
817
+ @left = left
818
+ @op = op
819
+ @right = right
820
+ end
821
+
822
+ def eval(input, context)
823
+ return eval_alternative(input, context) if @op == '//'
824
+ return eval_boolean(input, context) if @op == 'and' || @op == 'or'
825
+
826
+ left_values = @left.eval(input, context)
827
+ right_values = @right.eval(input, context)
828
+ left_values.flat_map do |left|
829
+ right_values.map { |right| apply(left, right) }
830
+ end
831
+ end
832
+
833
+ private
834
+
835
+ def eval_alternative(input, context)
836
+ left_values = @left.eval(input, context).select { |value| Value.truthy?(value) }
837
+ return left_values unless left_values.empty?
838
+
839
+ @right.eval(input, context)
840
+ end
841
+
842
+ def eval_boolean(input, context)
843
+ @left.eval(input, context).flat_map do |left|
844
+ if (@op == 'and' && !Value.truthy?(left)) || (@op == 'or' && Value.truthy?(left))
845
+ [@op == 'or']
846
+ else
847
+ @right.eval(input, context).map { |right| Value.truthy?(right) }
848
+ end
849
+ end
850
+ end
851
+
852
+ def apply(left, right)
853
+ case @op
854
+ when '+'
855
+ add(left, right)
856
+ when '-'
857
+ subtract(left, right)
858
+ when '*'
859
+ multiply(left, right)
860
+ when '/'
861
+ divide(left, right)
862
+ when '%'
863
+ modulo(left, right)
864
+ when '=='
865
+ Value.equal?(left, right)
866
+ when '!='
867
+ !Value.equal?(left, right)
868
+ when '<'
869
+ Value.compare(left, right).negative?
870
+ when '<='
871
+ Value.compare(left, right) <= 0
872
+ when '>'
873
+ Value.compare(left, right).positive?
874
+ when '>='
875
+ Value.compare(left, right) >= 0
876
+ else
877
+ raise "unknown operator #{@op}"
878
+ end
879
+ end
880
+
881
+ def add(left, right)
882
+ return right if left.nil?
883
+ return left if right.nil?
884
+ return numeric_pair(left, right).then { |a, b| a + b } if left.is_a?(Numeric) && right.is_a?(Numeric)
885
+ return left + right if left.is_a?(String) && right.is_a?(String)
886
+ return left + right if left.is_a?(Array) && right.is_a?(Array)
887
+ return left.merge(right) if left.is_a?(Hash) && right.is_a?(Hash)
888
+
889
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be added"
890
+ end
891
+
892
+ def subtract(left, right)
893
+ return numeric_pair(left, right).then { |a, b| a - b } if left.is_a?(Numeric) && right.is_a?(Numeric)
894
+ if left.is_a?(Array) && right.is_a?(Array)
895
+ return left.reject do |item|
896
+ right.any? do |other|
897
+ Value.equal?(item, other)
898
+ end
899
+ end
900
+ end
901
+
902
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be subtracted"
903
+ end
904
+
905
+ def multiply(left, right)
906
+ return numeric_pair(left, right).then { |a, b| a * b } if left.is_a?(Numeric) && right.is_a?(Numeric)
907
+ return repeat_string(left, right) if left.is_a?(String) && right.is_a?(Numeric)
908
+ return repeat_string(right, left) if right.is_a?(String) && left.is_a?(Numeric)
909
+ return recursive_merge(left, right) if left.is_a?(Hash) && right.is_a?(Hash)
910
+
911
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be multiplied"
912
+ end
913
+
914
+ def divide(left, right)
915
+ if left.is_a?(String) && right.is_a?(String)
916
+ return left.each_char.to_a if right.empty?
917
+
918
+ return left.split(right, -1)
919
+ end
920
+
921
+ left_number = numeric(left)
922
+ right_number = numeric(right)
923
+ raise TypeError, division_by_zero_message(left_number, right_number, 'divided') if right_number.zero?
924
+
925
+ left_number.fdiv(right_number)
926
+ end
927
+
928
+ def modulo(left, right)
929
+ left, right = numeric_pair(numeric(left), numeric(right))
930
+ return Float::NAN if nan_number?(left) || nan_number?(right)
931
+
932
+ left_integer = jq_integer(left)
933
+ right_integer = jq_integer(right)
934
+ raise TypeError, division_by_zero_message(left, right, 'divided (remainder)') if right_integer.zero?
935
+
936
+ remainder = left_integer.remainder(right_integer)
937
+ if remainder.zero? && left.is_a?(Float) && left.zero? && (1.0 / left).negative?
938
+ return -0.0
939
+ end
940
+ return remainder.to_f.round(-3) if (nonfinite_number?(left) || nonfinite_number?(right)) &&
941
+ unsafe_integer?(remainder)
942
+
943
+ unsafe_integer?(remainder) ? remainder.to_f : remainder
944
+ end
945
+
946
+ def jq_integer(value)
947
+ return (2**63) - 1 if value.respond_to?(:infinite?) && value.infinite? == 1
948
+ return -(2**63) if value.respond_to?(:infinite?) && value.infinite? == -1
949
+
950
+ [[value.to_i, -(2**63)].max, (2**63) - 1].min
951
+ end
952
+
953
+ def nonfinite_number?(value)
954
+ value.respond_to?(:finite?) && !value.finite?
955
+ end
956
+
957
+ def numeric_pair(left, right)
958
+ return [left.to_f, right.to_f] if unsafe_integer?(left) || unsafe_integer?(right)
959
+
960
+ [left, right]
961
+ end
962
+
963
+ def unsafe_integer?(value)
964
+ value.is_a?(Integer) && value.abs > (2**53)
965
+ end
966
+
967
+ def repeat_string(string, count)
968
+ return nil if count.respond_to?(:nan?) && count.nan?
969
+
970
+ count = count.floor
971
+ return nil if count.negative?
972
+
973
+ string * count
974
+ end
975
+
976
+ def recursive_merge(left, right)
977
+ Value.merge_objects(left, right)
978
+ end
979
+
980
+ def division_by_zero_message(left, right, verb)
981
+ "number (#{left}) and number (#{right}) cannot be #{verb} because the divisor is zero"
982
+ end
983
+ end
984
+
985
+ class Assignment < Node
986
+ DELETE = Object.new.freeze
987
+ NO_OUTPUT = Object.new.freeze
988
+ attr_reader :left, :op, :right
989
+
990
+ def initialize(left, op, right)
991
+ @left = left
992
+ @op = op
993
+ @right = right
994
+ end
995
+
996
+ def eval(input, context)
997
+ @op == '=' ? assign(input, context) : update(input, context)
998
+ end
999
+
1000
+ private
1001
+
1002
+ def assign(input, context)
1003
+ values = @right.eval(input, context)
1004
+ return [] if values.empty?
1005
+
1006
+ values.map do |value|
1007
+ copy = Value.deep_copy(input)
1008
+ @left.assign_value(copy, input, context, value)
1009
+ end
1010
+ end
1011
+
1012
+ def update(input, context)
1013
+ operations = @left.paths(input, context).map do |path|
1014
+ current = Path.get(input, path)
1015
+ value = update_value(current, input, context)
1016
+ return [] if value.equal?(NO_OUTPUT)
1017
+
1018
+ [path, value]
1019
+ end
1020
+ copy = Value.deep_copy(input)
1021
+ operations.reject { |_path, value| value.equal?(DELETE) }.each do |path, value|
1022
+ copy = Path.set(copy, path, value)
1023
+ end
1024
+ Builtins.ordered_delete_paths(operations.select do |_path, value|
1025
+ value.equal?(DELETE)
1026
+ end.map(&:first)).each do |path|
1027
+ Path.delete(copy, path)
1028
+ end
1029
+ [copy]
1030
+ end
1031
+
1032
+ def update_value(current, input, context)
1033
+ if @op == '|='
1034
+ values = @right.eval(current, context)
1035
+ return DELETE if values.empty?
1036
+
1037
+ values.first
1038
+ else
1039
+ values = @right.eval(input, context)
1040
+ return NO_OUTPUT if values.empty?
1041
+
1042
+ rhs = values.first
1043
+ BinaryOp.new(Literal.new(current), @op.delete_suffix('='), Literal.new(rhs)).eval(current, context).first
1044
+ end
1045
+ end
1046
+ end
1047
+
1048
+ class FunctionCall < Node
1049
+ attr_reader :name, :args
1050
+
1051
+ def initialize(name, args = [])
1052
+ @name = name
1053
+ @args = args
1054
+ end
1055
+
1056
+ def eval(input, context)
1057
+ if @args.empty? && context.variables[filter_variable_name(@name)].is_a?(Node)
1058
+ return context.variables[filter_variable_name(@name)].eval(input, context)
1059
+ end
1060
+
1061
+ if context.functions.key?([@name, @args.length])
1062
+ return eval_user_function(input, context, context.functions.fetch([@name, @args.length]))
1063
+ end
1064
+
1065
+ Builtins.call(@name, input, context, @args)
1066
+ end
1067
+
1068
+ def paths(input, context)
1069
+ if @args.empty? && context.variables[filter_variable_name(@name)].is_a?(Node)
1070
+ return context.variables[filter_variable_name(@name)].paths(input, context)
1071
+ end
1072
+
1073
+ return @args.first.eval(input, context).map { |path| Array(path) } if @name == 'getpath' && @args.length == 1
1074
+
1075
+ if context.functions.key?([@name, @args.length])
1076
+ definition = context.functions.fetch([@name, @args.length])
1077
+ return call_contexts(input, context, definition).flat_map { |ctx| definition.body.paths(input, ctx) }
1078
+ end
1079
+
1080
+ if @name == 'select' && @args.length == 1
1081
+ return @args.first.eval(input, context).any? { |value| Value.truthy?(value) } ? [[]] : []
1082
+ end
1083
+ return [] if @name == 'empty' && @args.empty?
1084
+ return [[0]] if @name == 'first' && @args.empty?
1085
+ return [[-1]] if @name == 'last' && @args.empty?
1086
+
1087
+ result = eval(input, context)
1088
+ result = result.first if result.length == 1
1089
+ raise InvalidPathError.new(invalid_path_message(result, input, context), result)
1090
+ end
1091
+
1092
+ private
1093
+
1094
+ def eval_user_function(input, context, definition)
1095
+ call_contexts(input, context, definition).flat_map { |ctx| definition.body.eval(input, ctx) }
1096
+ end
1097
+
1098
+ def call_contexts(input, context, definition)
1099
+ functions = (definition.closure || context.functions).merge([definition.name,
1100
+ definition.params.length] => definition)
1101
+ contexts = [context.with_functions(functions)]
1102
+ definition.params.zip(@args).each do |param, arg|
1103
+ if param.start_with?('$')
1104
+ values = arg.eval(input, context)
1105
+ contexts = contexts.flat_map do |ctx|
1106
+ values.map { |value| ctx.with_variable(param.delete_prefix('$'), value) }
1107
+ end
1108
+ else
1109
+ filter = resolve_filter_argument(arg, context)
1110
+ contexts = contexts.map { |ctx| ctx.with_variable(filter_variable_name(param), filter) }
1111
+ end
1112
+ end
1113
+ contexts
1114
+ end
1115
+
1116
+ def resolve_filter_argument(arg, context)
1117
+ if arg.is_a?(FunctionCall) && arg.args.empty? && context.variables[filter_variable_name(arg.name)].is_a?(Node)
1118
+ return context.variables[filter_variable_name(arg.name)]
1119
+ end
1120
+
1121
+ CapturedFilter.new(arg, context)
1122
+ end
1123
+
1124
+ def filter_variable_name(name)
1125
+ "filter:#{name}"
1126
+ end
1127
+ end
1128
+
1129
+ class Recurse < Node
1130
+ def eval(input, _context)
1131
+ values = []
1132
+ visit = lambda do |value|
1133
+ values << value
1134
+ case value
1135
+ when Array
1136
+ value.each { |item| visit.call(item) }
1137
+ when Hash
1138
+ value.each_value { |item| visit.call(item) }
1139
+ end
1140
+ end
1141
+ visit.call(input)
1142
+ values
1143
+ end
1144
+
1145
+ def paths(input, _context)
1146
+ Path.paths(input, leaves_only: false)
1147
+ end
1148
+ end
1149
+
1150
+ module_function
1151
+
1152
+ def bind_pattern(context, pattern, value)
1153
+ case pattern[0]
1154
+ when :alternatives
1155
+ matched = pattern[1].lazy.map { |candidate| match_bind_pattern(context, candidate, value) }.find(&:itself)
1156
+ matched ||= bind_pattern(context, pattern[1].first, value)
1157
+ bind_missing_variables(matched, pattern_variable_names(pattern), nil)
1158
+ when :both
1159
+ bind_pattern(bind_pattern(context, pattern[1], value), pattern[2], value)
1160
+ when :var
1161
+ context.with_variable(pattern[1], value)
1162
+ when :object
1163
+ pattern[1].reduce(context) do |ctx, (key, child)|
1164
+ actual_key = key.is_a?(Node) ? key.eval(value, ctx).first : key
1165
+ unless value.nil? || value.is_a?(Hash)
1166
+ raise TypeError, "Cannot index #{Value.type_of(value)} with string #{actual_key.to_s.inspect}"
1167
+ end
1168
+ bind_pattern(ctx, child, value.is_a?(Hash) ? value[actual_key.to_s] : nil)
1169
+ end
1170
+ when :array
1171
+ unless value.nil? || value.is_a?(Array)
1172
+ raise TypeError, "Cannot index #{Value.type_of(value)} with number"
1173
+ end
1174
+ pattern[1].each_with_index.reduce(context) do |ctx, (child, index)|
1175
+ bind_pattern(ctx, child, value.is_a?(Array) ? value[index] : nil)
1176
+ end
1177
+ end
1178
+ end
1179
+
1180
+ def match_bind_pattern(context, pattern, value)
1181
+ case pattern[0]
1182
+ when :var
1183
+ bind_pattern(context, pattern, value)
1184
+ when :object
1185
+ return nil unless value.nil? || value.is_a?(Hash)
1186
+
1187
+ bind_pattern(context, pattern, value)
1188
+ when :array
1189
+ return nil unless value.nil? || value.is_a?(Array)
1190
+
1191
+ bind_pattern(context, pattern, value)
1192
+ when :alternatives
1193
+ pattern[1].lazy.map { |candidate| match_bind_pattern(context, candidate, value) }.find(&:itself)
1194
+ when :both
1195
+ first = match_bind_pattern(context, pattern[1], value)
1196
+ first && match_bind_pattern(first, pattern[2], value)
1197
+ end
1198
+ end
1199
+
1200
+ def pattern_variable_names(pattern)
1201
+ case pattern[0]
1202
+ when :var
1203
+ [pattern[1]]
1204
+ when :object
1205
+ pattern[1].flat_map { |_key, child| pattern_variable_names(child) }
1206
+ when :array
1207
+ pattern[1].flat_map { |child| pattern_variable_names(child) }
1208
+ when :both
1209
+ pattern_variable_names(pattern[1]) + pattern_variable_names(pattern[2])
1210
+ when :alternatives
1211
+ pattern[1].flat_map { |child| pattern_variable_names(child) }.uniq
1212
+ else
1213
+ []
1214
+ end
1215
+ end
1216
+
1217
+ def bind_missing_variables(context, names, value)
1218
+ names.reduce(context) do |ctx, name|
1219
+ ctx.variables.key?(name) ? ctx : ctx.with_variable(name, value)
1220
+ end
1221
+ end
1222
+
1223
+ def bind_pattern_with_path(context, pattern, value)
1224
+ candidates = pattern[0] == :alternatives ? pattern[1] : [pattern]
1225
+ errors = []
1226
+ candidates.each do |candidate|
1227
+ begin
1228
+ return bind_pattern_candidate_with_path(context, pattern, candidate, value)
1229
+ rescue Rjq::RuntimeError => e
1230
+ errors << e
1231
+ end
1232
+ end
1233
+ raise errors.last
1234
+ end
1235
+
1236
+ def bind_pattern_candidate_with_path(context, pattern, candidate, value)
1237
+ bound, path, variables = bind_single_pattern_with_path(context, candidate, value)
1238
+ selected = value_at_path(value, path)
1239
+ pattern_variable_names(pattern).each do |name|
1240
+ next if variables.key?(name)
1241
+
1242
+ variables[name] = selected.nil? ? path : (static_variable_path(pattern, name) || path)
1243
+ end
1244
+ [bind_missing_variables(bound, pattern_variable_names(pattern), nil), path, variables]
1245
+ end
1246
+
1247
+ def validate_binding_results(input, results, paths, alternative_base: nil, variable_paths: [],
1248
+ alternative_paths: [])
1249
+ return [] if results.empty?
1250
+
1251
+ validated = []
1252
+ switched_path = nil
1253
+ results.zip(paths).each do |result, path|
1254
+ if alternative_paths.include?([]) && path == alternative_base &&
1255
+ !binding_path_matches?(input, path, result)
1256
+ path = []
1257
+ end
1258
+ variable_path = variable_paths.include?(path)
1259
+ if switched_path && variable_path
1260
+ if binding_path_matches?(input, path, result)
1261
+ validated << switched_path
1262
+ next
1263
+ end
1264
+ raise InvalidPathError.new("Invalid path expression with result #{JSON::Dumper.dump(result, indent: nil)}",
1265
+ result, outputs: validated)
1266
+ end
1267
+
1268
+ switching = !alternative_paths.empty? && variable_path && path != alternative_base
1269
+ if switching && validated.empty?
1270
+ validate_pattern_path(input, path) unless alternative_paths.include?([])
1271
+ validated << path
1272
+ switched_path = path unless binding_path_matches?(input, path, result)
1273
+ next
1274
+ end
1275
+
1276
+ unless binding_path_matches?(input, path, result)
1277
+ raise InvalidPathError.new("Invalid path expression with result #{JSON::Dumper.dump(result, indent: nil)}",
1278
+ result, outputs: validated)
1279
+ end
1280
+ validated << path
1281
+ if switching && binding_path_matches?(input, path, result)
1282
+ switched_path = path
1283
+ validated << path
1284
+ end
1285
+ end
1286
+ validated
1287
+ end
1288
+
1289
+ def binding_path_matches?(input, path, result)
1290
+ path && binding_path_components_valid?(input, path) && Value.equal?(value_at_path(input, path), result)
1291
+ end
1292
+
1293
+ def binding_path_components_valid?(input, path)
1294
+ current = input
1295
+ path.each do |component|
1296
+ return true if current.nil?
1297
+ return false unless current.is_a?(Array) || current.is_a?(Hash)
1298
+ return false if current.is_a?(Array) && !component.is_a?(Numeric)
1299
+ return false if current.is_a?(Hash) && !component.is_a?(String)
1300
+
1301
+ current = current.is_a?(Array) ? current[component.to_i] : current[component]
1302
+ end
1303
+ true
1304
+ end
1305
+
1306
+ def alternative_paths(pattern)
1307
+ return [] unless pattern[0] == :alternatives
1308
+
1309
+ pattern[1].map { |candidate| pattern_primary_path(candidate) }
1310
+ end
1311
+
1312
+ def pattern_primary_path(pattern)
1313
+ case pattern[0]
1314
+ when :var then []
1315
+ when :object
1316
+ key, child = pattern[1].first
1317
+ [key.is_a?(Node) ? nil : key.to_s] + pattern_primary_path(child)
1318
+ when :array then [0] + pattern_primary_path(pattern[1].first || [:var, ''])
1319
+ when :both then pattern_primary_path(pattern[1]) + pattern_primary_path(pattern[2])
1320
+ else []
1321
+ end
1322
+ end
1323
+
1324
+ def validate_pattern_path(input, path)
1325
+ return true if input.nil? || path.empty?
1326
+
1327
+ current = input
1328
+ previous_container = input
1329
+ path.each do |component|
1330
+ unless current.is_a?(Array) || current.is_a?(Hash)
1331
+ raise InvalidPathError.new("Invalid path expression near attempt to access element #{component.inspect} of " \
1332
+ "#{JSON::Dumper.dump(previous_container, indent: nil)}", current)
1333
+ end
1334
+ if current.is_a?(Array) && !component.is_a?(Numeric)
1335
+ raise TypeError, "Cannot index array with string #{component.to_s.inspect}"
1336
+ end
1337
+ if current.is_a?(Hash) && !component.is_a?(String)
1338
+ raise TypeError, 'Cannot index object with number'
1339
+ end
1340
+ previous_container = current
1341
+ current = current.is_a?(Array) ? current[component.to_i] : current[component]
1342
+ end
1343
+ true
1344
+ end
1345
+
1346
+ def bind_single_pattern_with_path(context, pattern, value)
1347
+ case pattern[0]
1348
+ when :var
1349
+ [context.with_variable(pattern[1], value), [], { pattern[1] => [] }]
1350
+ when :object
1351
+ unless value.nil? || value.is_a?(Hash)
1352
+ key = pattern[1].first&.first
1353
+ key = key.eval(value, context).first if key.is_a?(Node)
1354
+ raise TypeError, "Cannot index #{Value.type_of(value)} with string #{key.to_s.inspect}"
1355
+ end
1356
+ ctx = context
1357
+ combined = []
1358
+ variables = {}
1359
+ pattern[1].each do |key, child|
1360
+ actual_key = key.is_a?(Node) ? key.eval(value, ctx).first : key
1361
+ child_value = value.is_a?(Hash) ? value[actual_key.to_s] : nil
1362
+ ctx, child_path, child_variables = bind_single_pattern_with_path(ctx, child, child_value)
1363
+ full = [actual_key.to_s] + child_path
1364
+ combined.concat(full)
1365
+ variables.merge!(child_variables.transform_values { |path| [actual_key.to_s] + path })
1366
+ end
1367
+ variables.transform_values! { combined } if variables.length > 1
1368
+ [ctx, combined, variables]
1369
+ when :array
1370
+ unless value.nil? || value.is_a?(Array)
1371
+ raise TypeError, "Cannot index #{Value.type_of(value)} with number"
1372
+ end
1373
+ ctx = context
1374
+ entries = pattern[1].each_with_index.map do |child, index|
1375
+ child_value = value.is_a?(Array) ? value[index] : nil
1376
+ ctx, child_path, child_variables = bind_single_pattern_with_path(ctx, child, child_value)
1377
+ [[index] + child_path, child_variables.transform_values { |path| [index] + path }]
1378
+ end
1379
+ combined = entries.reverse.flat_map(&:first)
1380
+ variables = entries.each_with_object({}) { |(_path, vars), all| all.merge!(vars) }
1381
+ variables.transform_values! { combined } if variables.length > 1
1382
+ [ctx, combined, variables]
1383
+ when :both
1384
+ first_ctx, first_path, first_variables = bind_single_pattern_with_path(context, pattern[1], value)
1385
+ second_ctx, second_path, second_variables = bind_single_pattern_with_path(first_ctx, pattern[2], value)
1386
+ combined = first_path + second_path
1387
+ variables = first_variables.merge(second_variables)
1388
+ variables.transform_values! { combined } if variables.length > 1
1389
+ [second_ctx, combined, variables]
1390
+ else
1391
+ raise TypeError, 'invalid binding pattern'
1392
+ end
1393
+ end
1394
+
1395
+ def static_variable_path(pattern, target, prefix = [])
1396
+ case pattern[0]
1397
+ when :var
1398
+ return prefix if pattern[1] == target
1399
+ when :object
1400
+ pattern[1].each do |key, child|
1401
+ next if key.is_a?(Node)
1402
+
1403
+ found = static_variable_path(child, target, prefix + [key.to_s])
1404
+ return found if found
1405
+ end
1406
+ when :array
1407
+ pattern[1].each_with_index do |child, index|
1408
+ found = static_variable_path(child, target, prefix + [index])
1409
+ return found if found
1410
+ end
1411
+ when :both
1412
+ return static_variable_path(pattern[1], target, prefix) || static_variable_path(pattern[2], target, prefix)
1413
+ when :alternatives
1414
+ pattern[1].each do |candidate|
1415
+ found = static_variable_path(candidate, target, prefix)
1416
+ return found if found
1417
+ end
1418
+ end
1419
+ nil
1420
+ end
1421
+
1422
+ def value_at_path(value, path)
1423
+ path.reduce(value) do |current, component|
1424
+ return nil unless current.is_a?(Array) || current.is_a?(Hash)
1425
+ return nil if current.is_a?(Array) && !component.is_a?(Numeric)
1426
+ return nil if current.is_a?(Hash) && !component.is_a?(String)
1427
+
1428
+ current.is_a?(Array) ? current[component.to_i] : current[component]
1429
+ end
1430
+ end
1431
+
1432
+ def valid_path_parent?(input, path)
1433
+ return true if input.nil? || path.empty?
1434
+
1435
+ parent = path[0...-1].reduce(input) do |current, component|
1436
+ return false unless current.is_a?(Array) || current.is_a?(Hash)
1437
+ return false if current.is_a?(Array) && !component.is_a?(Numeric)
1438
+ return false if current.is_a?(Hash) && !component.is_a?(String)
1439
+
1440
+ current.is_a?(Array) ? current[component.to_i] : current[component]
1441
+ end
1442
+ parent.is_a?(Array) || parent.is_a?(Hash)
1443
+ end
1444
+
1445
+ def numeric(value)
1446
+ raise TypeError, "#{Value.type_of(value)} is not a number" unless value.is_a?(Numeric)
1447
+
1448
+ value
1449
+ end
1450
+
1451
+ def short_dump(value)
1452
+ dumped = JSON::Dumper.dump(value, indent: nil)
1453
+ dumped.length > 14 ? "#{dumped[0, 11]}..." : dumped
1454
+ end
1455
+
1456
+ def range_for(length, start, finish)
1457
+ from = start.nil? || nan_number?(start) ? 0 : normalize_boundary(start, length, :floor)
1458
+ to = finish.nil? || nan_number?(finish) ? length : normalize_boundary(finish, length, :ceil)
1459
+ from...to
1460
+ end
1461
+
1462
+ def normalize_boundary(index, length, rounding)
1463
+ raise TypeError, 'slice index must be a number' unless index.is_a?(Numeric)
1464
+
1465
+ rounded = rounding == :ceil ? index.ceil : index.floor
1466
+
1467
+ normalized = index.negative? ? length + rounded : rounded
1468
+ [[normalized, 0].max, length].min
1469
+ end
1470
+
1471
+ def nan_number?(value)
1472
+ value.respond_to?(:nan?) && value.nan?
1473
+ end
1474
+ end
1475
+ end