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/vm.rb ADDED
@@ -0,0 +1,1359 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rjq
4
+ class VM
5
+ class InstructionBudget
6
+ attr_reader :maximum
7
+
8
+ def initialize(maximum)
9
+ @maximum = maximum
10
+ @count = 0
11
+ end
12
+
13
+ def charge!
14
+ return unless @maximum
15
+
16
+ @count += 1
17
+ raise ResourceLimitError, "instruction limit exceeded (#{@maximum})" if @count > @maximum
18
+ end
19
+ end
20
+
21
+ class RecordingInputQueue
22
+ attr_reader :current_record
23
+
24
+ def initialize(queue)
25
+ @queue = queue
26
+ @records = []
27
+ end
28
+
29
+ def empty?
30
+ @queue.empty?
31
+ end
32
+
33
+ def shift_record
34
+ @current_record = @queue.shift_record
35
+ @records << @current_record if @current_record
36
+ @current_record
37
+ end
38
+
39
+ def shift
40
+ shift_record&.value
41
+ end
42
+
43
+ def each_remaining
44
+ return enum_for(:each_remaining) unless block_given?
45
+
46
+ yield shift until empty?
47
+ end
48
+
49
+ def playback
50
+ Runtime::InputQueue.new(@records)
51
+ end
52
+ end
53
+
54
+ def initialize(program, opts = {}, instruction_budget: nil)
55
+ @program = program
56
+ @opts = opts
57
+ @instruction_budget = instruction_budget || InstructionBudget.new(opts[:max_instructions])
58
+ end
59
+
60
+ def run(input_value)
61
+ Enumerator.new do |yielder|
62
+ @call_depth = 0
63
+ Value.validate!(input_value)
64
+ @opts.fetch(:variables, {}).each_value { |value| Value.validate!(value) }
65
+ context = context_with_definitions(base_context)
66
+ output_count = 0
67
+ begin
68
+ each_block(@program.program.instructions, input_value, context).each do |value|
69
+ output_count += 1
70
+ max_outputs = @opts[:max_outputs]
71
+ raise RuntimeError, "output limit exceeded (#{max_outputs})" if max_outputs && output_count > max_outputs
72
+
73
+ yielder << value
74
+ end
75
+ rescue ErrorValue => e
76
+ Array(e.outputs).each { |value| yielder << value }
77
+ raise
78
+ rescue SystemStackError
79
+ raise ResourceLimitError, 'execution recursion exceeded the host stack limit'
80
+ end
81
+ end
82
+ end
83
+
84
+ def execute_block(instructions, input, context)
85
+ output = []
86
+ each_block(instructions, input, context) { |value| output << value }
87
+ output
88
+ rescue Rjq::RuntimeError => e
89
+ raise e.prepend_outputs(output)
90
+ rescue BreakSignal => e
91
+ raise BreakSignal.new(e.label, e.value, outputs: output + Array(e.outputs))
92
+ end
93
+
94
+ def take_block(instructions, input, context, count)
95
+ return [] if count <= 0
96
+
97
+ each_block(instructions, input, context).take(count)
98
+ end
99
+
100
+ def paths_for_block(instructions, input, context)
101
+ stack = []
102
+ instructions.each_with_index do |instruction, index|
103
+ execute_path_instruction(instruction, stack, input, context)
104
+ rescue Rjq::RuntimeError => e
105
+ remaining = instructions[(index + 1)..].to_a
106
+ raise if remaining.empty?
107
+
108
+ if e.is_a?(InvalidPathError)
109
+ raise InvalidPathError.new(invalid_path_message(remaining, e.result, input, context), e.result,
110
+ outputs: e.outputs)
111
+ end
112
+ raise
113
+ end
114
+ stack.pop || []
115
+ end
116
+
117
+ def source_any?(instructions, input, context, &)
118
+ each_block(instructions, input, context).any?(&)
119
+ end
120
+
121
+ def source_all?(instructions, input, context, &)
122
+ each_block(instructions, input, context).all?(&)
123
+ end
124
+
125
+ def replacement_filters(block, captured_context)
126
+ if block.instructions.last&.op == :append
127
+ left = BytecodeBlock.new(instructions: block.instructions[0...-1])
128
+ right = block.instructions.last.arg1
129
+ return replacement_filters(left, captured_context) + replacement_filters(right, captured_context)
130
+ end
131
+
132
+ [BytecodeFilter.new(self, block, captured_context: captured_context)]
133
+ end
134
+
135
+ def stream_block(block, input, context)
136
+ each_block(block.instructions, input, context)
137
+ end
138
+
139
+ private
140
+
141
+ def base_context
142
+ AST::Context.new(
143
+ variables: @program.program.module_variables.merge(@opts.fetch(:variables, {})),
144
+ functions: {},
145
+ options: @opts.merge(module_metadata: @program.program.module_metadata)
146
+ )
147
+ end
148
+
149
+ def context_with_definitions(context, definitions = @program.program.definitions)
150
+ definitions.reduce(context) { |ctx, definition| apply_definition(ctx, definition) }
151
+ end
152
+
153
+ def apply_definition(context, definition)
154
+ closed = definition.with_closure(context.functions)
155
+ context_with_self = context.with_function(definition.name, definition.params.length, closed)
156
+ context_with_self.with_function(
157
+ definition.name,
158
+ definition.params.length,
159
+ definition.with_closure(context_with_self.functions)
160
+ )
161
+ end
162
+
163
+ def execute_filter(block, input, context)
164
+ execute_block(block.instructions, input, context)
165
+ end
166
+
167
+ def each_block(instructions, input, context, &block)
168
+ return enum_for(:each_block, instructions, input, context) unless block
169
+
170
+ stack = []
171
+ instructions.each { |instruction| execute_stream_instruction(instruction, stack, input, context) }
172
+ (stack.pop || empty_stream).each(&block)
173
+ end
174
+
175
+ def execute_stream_instruction(instruction, stack, input, context)
176
+ charge_instruction!
177
+ case instruction.op
178
+ when :load_input then stack << value_stream(input)
179
+ when :load_const then stack << value_stream(Value.deep_copy(@program.program.constants.fetch(instruction.arg1)))
180
+ when :string_interp then stack << values_stream(evaluate_string(instruction.arg1, input, context))
181
+ when :format then stack << values_stream(evaluate_format(instruction.arg1, instruction.arg2, input, context))
182
+ when :variable then stack << values_stream(evaluate_variable(instruction.arg1, context, instruction.loc))
183
+ when :field then stack << map_stream(stack.pop) { |value| read_field(value, instruction.arg1) }
184
+ when :index_const then stack << map_stream(stack.pop) { |value| read_index(value, instruction.arg1) }
185
+ when :index_filter then stack << index_filter_stream(stack.pop, instruction.arg1, input, context)
186
+ when :slice_const then stack << map_stream(stack.pop) do |value|
187
+ read_slice(value, instruction.arg1, instruction.arg2)
188
+ end
189
+ when :slice_filter then stack << slice_filter_stream(stack.pop, instruction.arg1, input, context)
190
+ when :each then stack << flat_map_stream(stack.pop) { |value| values_stream(each_value(value)) }
191
+ when :path then stack << path_stream(instruction.arg1, input, context)
192
+ when :optional then stack << optional_stream(instruction.arg1, input, context)
193
+ when :pipe then stack << pipe_stream(stack.pop, instruction.arg1, context)
194
+ when :append then stack << concat_stream(stack.pop, each_block(instruction.arg1.instructions, input, context))
195
+ when :binding then stack << binding_stream(instruction.arg1, input, context)
196
+ when :array then stack << array_stream(instruction.arg1, input, context)
197
+ when :object then stack << object_stream(instruction.arg1, input, context)
198
+ when :branch then stack << branch_stream(instruction, input, context)
199
+ when :try then stack << try_stream(instruction.arg1, input, context)
200
+ when :reduce then stack << reduce_stream(instruction.arg1, input, context)
201
+ when :foreach then stack << foreach_stream(instruction.arg1, input, context)
202
+ when :label then stack << label_stream(instruction.arg1, instruction.arg2, input, context)
203
+ when :break then raise BreakSignal, instruction.arg1
204
+ when :unary then stack << unary_stream(instruction.arg1, instruction.arg2, input, context)
205
+ when :binary then stack << binary_stream(instruction.arg1, instruction.arg2, input, context)
206
+ when :assign then stack << assignment_stream(instruction.arg1, input, context)
207
+ when :call then stack << call_stream(instruction, input, context)
208
+ when :tail_call then stack << call_stream(instruction, input, context, tail: true)
209
+ when :recurse then stack << recurse_stream(input)
210
+ when :scoped_def then stack << each_block(instruction.arg2.instructions, input,
211
+ apply_definition(context, instruction.arg1))
212
+ else raise "unknown opcode #{instruction.op}"
213
+ end
214
+ end
215
+
216
+ def empty_stream
217
+ values_stream([])
218
+ end
219
+
220
+ def value_stream(value)
221
+ values_stream([value])
222
+ end
223
+
224
+ def values_stream(values)
225
+ Enumerator.new { |yielder| values.each { |value| yielder << value } }
226
+ end
227
+
228
+ def deferred_values_stream
229
+ Enumerator.new do |yielder|
230
+ yield.each { |value| yielder << value }
231
+ end
232
+ end
233
+
234
+ def map_stream(stream)
235
+ Enumerator.new do |yielder|
236
+ stream.each { |value| yielder << yield(value) }
237
+ end
238
+ end
239
+
240
+ def flat_map_stream(stream)
241
+ Enumerator.new do |yielder|
242
+ stream.each do |value|
243
+ yield(value).each { |item| yielder << item }
244
+ end
245
+ end
246
+ end
247
+
248
+ def concat_stream(left, right)
249
+ Enumerator.new do |yielder|
250
+ left.each { |value| yielder << value }
251
+ right.each { |value| yielder << value }
252
+ end
253
+ end
254
+
255
+ def index_filter_stream(values, block, input, context)
256
+ indices = replayable_stream(each_block(block.instructions, input, context))
257
+ flat_map_stream(values) { |value| map_stream(indices.call) { |index| read_index(value, index) } }
258
+ end
259
+
260
+ def slice_filter_stream(values, spec, input, context)
261
+ Enumerator.new do |yielder|
262
+ starts = spec[:start] ? each_block(spec.fetch(:start).instructions, input, context).to_a : [nil]
263
+ finishes = spec[:finish] ? each_block(spec.fetch(:finish).instructions, input, context).to_a : [nil]
264
+ values.each do |value|
265
+ starts.each do |start_index|
266
+ finishes.each { |finish_index| yielder << read_slice(value, start_index, finish_index) }
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ def replayable_stream(stream)
273
+ cache = []
274
+ source = stream.to_enum
275
+ exhausted = false
276
+ failure = nil
277
+
278
+ lambda do
279
+ Enumerator.new do |yielder|
280
+ index = 0
281
+ loop do
282
+ if index < cache.length
283
+ yielder << cache.fetch(index)
284
+ index += 1
285
+ next
286
+ end
287
+
288
+ raise failure if failure
289
+ break if exhausted
290
+
291
+ begin
292
+ value = source.next
293
+ maximum = @opts[:max_replay_cache]
294
+ if maximum && cache.length >= maximum
295
+ raise ResourceLimitError, "replay cache limit exceeded (#{maximum})"
296
+ end
297
+ cache << value
298
+ yielder << value
299
+ index += 1
300
+ rescue StopIteration
301
+ exhausted = true
302
+ break
303
+ rescue StandardError => e
304
+ failure = e
305
+ raise
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ def optional_stream(block, input, context)
313
+ Enumerator.new do |yielder|
314
+ source = each_block(block.instructions, input, context).to_enum
315
+ loop do
316
+ value =
317
+ begin
318
+ source.next
319
+ rescue StopIteration
320
+ break
321
+ rescue ResourceLimitError
322
+ raise
323
+ rescue Rjq::RuntimeError
324
+ break
325
+ end
326
+ yielder << value
327
+ end
328
+ end
329
+ end
330
+
331
+ def path_stream(block, input, context)
332
+ Enumerator.new do |yielder|
333
+ paths_for_block(block.instructions, input, context).each { |path| yielder << path }
334
+ rescue Rjq::RuntimeError => e
335
+ e.take_outputs.each { |path| yielder << path }
336
+ raise
337
+ end
338
+ end
339
+
340
+ def array_stream(block, input, context)
341
+ Enumerator.new do |yielder|
342
+ yielder << (block ? each_block(block.instructions, input, context).to_a : [])
343
+ end
344
+ end
345
+
346
+ def pipe_stream(values, block, context)
347
+ flat_map_stream(values) { |value| each_block(block.instructions, value, context) }
348
+ end
349
+
350
+ def binding_stream(spec, input, context)
351
+ flat_map_stream(each_block(spec.fetch(:source).instructions, input, context)) do |value|
352
+ bound = AST.bind_pattern(context, spec.fetch(:pattern), value)
353
+ bound ? each_block(spec.fetch(:body).instructions, input, bound) : empty_stream
354
+ end
355
+ end
356
+
357
+ def object_stream(pairs, input, context)
358
+ Enumerator.new do |yielder|
359
+ emit_object_pair(pairs, 0, {}, input, context, yielder)
360
+ end
361
+ end
362
+
363
+ def emit_object_pair(pairs, index, object, input, context, yielder)
364
+ if index >= pairs.length
365
+ yielder << object
366
+ return
367
+ end
368
+
369
+ pair = pairs.fetch(index)
370
+ object_key_stream(pair.fetch(:key), input, context).each do |key|
371
+ each_block(pair.fetch(:value).instructions, input, context) do |value|
372
+ emit_object_pair(pairs, index + 1, object.merge(key.to_s => value), input, context, yielder)
373
+ end
374
+ end
375
+ end
376
+
377
+ def object_key_stream(key, input, context)
378
+ return value_stream(key.fetch(:value)) if key.fetch(:type) == :literal
379
+
380
+ map_stream(each_block(key.fetch(:block).instructions, input, context)) { |value| object_key(value) }
381
+ end
382
+
383
+ def branch_stream(instruction, input, context)
384
+ then_block, else_block = instruction.arg2
385
+ flat_map_stream(each_block(instruction.arg1.instructions, input, context)) do |value|
386
+ each_block((Value.truthy?(value) ? then_block : else_block).instructions, input, context)
387
+ end
388
+ end
389
+
390
+ def try_stream(spec, input, context)
391
+ Enumerator.new do |yielder|
392
+ source = each_block(spec.fetch(:body).instructions, input, context).to_enum
393
+ loop do
394
+ value =
395
+ begin
396
+ source.next
397
+ rescue StopIteration
398
+ break
399
+ rescue Rjq::ErrorValue => e
400
+ Array(e.outputs).each { |item| yielder << item }
401
+ if spec[:handler]
402
+ each_block(spec.fetch(:handler).instructions, e.value, context).each do |item|
403
+ yielder << item
404
+ end
405
+ end
406
+ break
407
+ rescue Rjq::ResourceLimitError
408
+ raise
409
+ rescue Rjq::RuntimeError => e
410
+ if spec[:handler]
411
+ each_block(spec.fetch(:handler).instructions, e.message, context).each do |item|
412
+ yielder << item
413
+ end
414
+ end
415
+ break
416
+ end
417
+ yielder << value
418
+ end
419
+ end
420
+ end
421
+
422
+ def reduce_stream(spec, input, context)
423
+ Enumerator.new do |yielder|
424
+ each_block(spec.fetch(:initial).instructions, input, context).each do |initial|
425
+ accumulator = initial
426
+ each_block(spec.fetch(:generator).instructions, input, context).each do |value|
427
+ ctx = AST.bind_pattern(context, spec.fetch(:pattern), value)
428
+ next unless ctx
429
+ did_update = false
430
+ each_block(spec.fetch(:update).instructions, accumulator, ctx).each do |next_accumulator|
431
+ did_update = true
432
+ accumulator = next_accumulator
433
+ end
434
+ accumulator = nil unless did_update
435
+ end
436
+ yielder << accumulator
437
+ end
438
+ end
439
+ end
440
+
441
+ def foreach_stream(spec, input, context)
442
+ Enumerator.new do |yielder|
443
+ each_block(spec.fetch(:initial).instructions, input, context).each do |initial|
444
+ accumulator = initial
445
+ each_block(spec.fetch(:generator).instructions, input, context).each do |value|
446
+ ctx = AST.bind_pattern(context, spec.fetch(:pattern), value)
447
+ next unless ctx
448
+ did_update = false
449
+ each_block(spec.fetch(:update).instructions, accumulator, ctx).each do |next_accumulator|
450
+ did_update = true
451
+ accumulator = next_accumulator
452
+ values = if spec[:extract]
453
+ each_block(spec.fetch(:extract).instructions, next_accumulator, ctx)
454
+ else
455
+ value_stream(next_accumulator)
456
+ end
457
+ values.each { |item| yielder << item }
458
+ end
459
+ accumulator = nil unless did_update
460
+ rescue BreakSignal => e
461
+ raise BreakSignal.new(e.label, e.value, outputs: e.outputs)
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ def label_stream(label, block, input, context)
468
+ Enumerator.new do |yielder|
469
+ source = each_block(block.instructions, input, context).to_enum
470
+ loop do
471
+ value =
472
+ begin
473
+ source.next
474
+ rescue StopIteration
475
+ break
476
+ rescue BreakSignal => e
477
+ raise unless e.label == label
478
+
479
+ if e.outputs
480
+ e.outputs.each { |item| yielder << item }
481
+ elsif !e.value.nil?
482
+ yielder << e.value
483
+ end
484
+ break
485
+ end
486
+ yielder << value
487
+ end
488
+ end
489
+ end
490
+
491
+ def unary_stream(op, block, input, context)
492
+ map_stream(each_block(block.instructions, input, context)) { |value| apply_unary(op, value) }
493
+ end
494
+
495
+ def binary_stream(op, blocks, input, context)
496
+ return alternative_stream(blocks, input, context) if op == '//'
497
+ return boolean_stream(op, blocks, input, context) if %w[and or].include?(op)
498
+
499
+ flat_map_stream(each_block(blocks[1].instructions, input, context)) do |right|
500
+ map_stream(each_block(blocks[0].instructions, input, context)) { |left| apply_binary(op, left, right) }
501
+ end
502
+ end
503
+
504
+ def alternative_stream(blocks, input, context)
505
+ Enumerator.new do |yielder|
506
+ found = false
507
+ each_block(blocks[0].instructions, input, context).each do |value|
508
+ next unless Value.truthy?(value)
509
+
510
+ found = true
511
+ yielder << value
512
+ end
513
+ each_block(blocks[1].instructions, input, context).each { |value| yielder << value } unless found
514
+ end
515
+ end
516
+
517
+ def boolean_stream(op, blocks, input, context)
518
+ flat_map_stream(each_block(blocks[0].instructions, input, context)) do |left|
519
+ if (op == 'and' && !Value.truthy?(left)) || (op == 'or' && Value.truthy?(left))
520
+ value_stream(op == 'or')
521
+ else
522
+ map_stream(each_block(blocks[1].instructions, input, context)) { |right| Value.truthy?(right) }
523
+ end
524
+ end
525
+ end
526
+
527
+ def assignment_stream(spec, input, context)
528
+ return deferred_values_stream { execute_assignment(spec, input, context) } if spec.fetch(:op) == '|='
529
+
530
+ assignment_rhs_stream(spec, input, context)
531
+ end
532
+
533
+ def assignment_rhs_stream(spec, input, context)
534
+ flat_map_stream(each_block(spec.fetch(:right).instructions, input, context)) do |rhs|
535
+ value_stream(execute_assignment_with_rhs(spec, input, context, rhs))
536
+ end
537
+ end
538
+
539
+ def call_stream(instruction, input, context, tail: false)
540
+ name = instruction.arg1
541
+ arg_blocks = instruction.arg2
542
+
543
+ if arg_blocks.empty? && context.variables[filter_variable_name(name)].is_a?(BytecodeFilter)
544
+ return context.variables[filter_variable_name(name)].stream(input, context)
545
+ end
546
+
547
+ if context.functions.key?([name, arg_blocks.length])
548
+ definition = context.functions.fetch([name, arg_blocks.length])
549
+ return value_stream(TailCall.new(input: input, context: context, definition: definition,
550
+ arg_blocks: arg_blocks)) if tail
551
+
552
+ return user_function_stream(input, context, definition, arg_blocks)
553
+ end
554
+
555
+ builtin_args = arg_blocks.map { |block| BytecodeFilter.new(self, block) }
556
+ deferred_values_stream { Builtins.call_stream(name, input, context, builtin_args) }
557
+ end
558
+
559
+ def user_function_stream(input, context, definition, arg_blocks)
560
+ Enumerator.new do |yielder|
561
+ with_call_frame do
562
+ stack = []
563
+ push_tail_contexts(stack, input, call_contexts(input, context, definition, arg_blocks), definition)
564
+ until stack.empty?
565
+ begin
566
+ value = stack.last.next
567
+ if value.is_a?(TailCall)
568
+ contexts = call_contexts(value.input, value.context, value.definition, value.arg_blocks)
569
+ push_tail_contexts(stack, value.input, contexts, value.definition)
570
+ else
571
+ yielder << value
572
+ end
573
+ rescue StopIteration
574
+ stack.pop
575
+ end
576
+ end
577
+ end
578
+ end
579
+ end
580
+
581
+ def push_tail_contexts(stack, input, contexts, definition)
582
+ contexts.reverse_each do |ctx|
583
+ stack << each_block(definition.body.instructions, input, ctx).to_enum
584
+ end
585
+ end
586
+
587
+ def with_call_frame
588
+ @call_depth += 1
589
+ max_depth = @opts.fetch(:max_call_depth, Runtime::DEFAULT_OPTIONS.fetch(:max_call_depth))
590
+ raise ResourceLimitError, "call depth limit exceeded (#{max_depth})" if max_depth && @call_depth > max_depth
591
+
592
+ yield
593
+ ensure
594
+ @call_depth -= 1
595
+ end
596
+
597
+ def charge_instruction!
598
+ @instruction_budget.charge!
599
+ end
600
+
601
+ def recurse_stream(input)
602
+ Enumerator.new do |yielder|
603
+ stack = [input]
604
+ until stack.empty?
605
+ value = stack.pop
606
+ yielder << value
607
+ children = value.is_a?(Array) ? value : value.is_a?(Hash) ? value.values : []
608
+ stack.concat(children.reverse)
609
+ end
610
+ end
611
+ end
612
+
613
+ def object_key(value)
614
+ return value if value.is_a?(String)
615
+
616
+ raise TypeError, "Cannot use #{Value.type_of(value)} (#{short_dump(value)}) as object key"
617
+ end
618
+
619
+ def execute_assignment(spec, input, context)
620
+ spec.fetch(:op) == '=' ? assign(spec, input, context) : update(spec, input, context)
621
+ end
622
+
623
+ def execute_assignment_with_rhs(spec, input, context, rhs)
624
+ return assign_value(spec.fetch(:left).instructions, Value.deep_copy(input), input, context, rhs) if spec.fetch(:op) == '='
625
+
626
+ update_with_rhs(spec, input, context, rhs)
627
+ end
628
+
629
+ def update_with_rhs(spec, input, context, rhs)
630
+ copy = Value.deep_copy(input)
631
+ paths_for_block(spec.fetch(:left).instructions, input, context).each do |path|
632
+ current = Path.get(copy, path)
633
+ value = if spec.fetch(:op) == '//='
634
+ Value.truthy?(current) ? current : rhs
635
+ else
636
+ apply_binary(spec.fetch(:op).delete_suffix('='), current, rhs)
637
+ end
638
+ copy = Path.set(copy, path, value)
639
+ end
640
+ copy
641
+ end
642
+
643
+ def assign(spec, input, context)
644
+ values = execute_filter(spec.fetch(:right), input, context)
645
+ return [] if values.empty?
646
+
647
+ values.map do |value|
648
+ copy = Value.deep_copy(input)
649
+ assign_value(spec.fetch(:left).instructions, copy, input, context, value)
650
+ end
651
+ end
652
+
653
+ def update(spec, input, context)
654
+ copy = Value.deep_copy(input)
655
+ deletions = []
656
+ paths_for_block(spec.fetch(:left).instructions, input, context).each do |path|
657
+ current = Path.get(copy, path)
658
+ value = update_value(spec.fetch(:op), spec.fetch(:right), current, input, context)
659
+ return [] if value.equal?(AssignmentSentinel.no_output)
660
+
661
+ if value.equal?(AssignmentSentinel.delete)
662
+ deletions << path
663
+ else
664
+ copy = Path.set(copy, path, value)
665
+ end
666
+ end
667
+ Builtins.ordered_delete_paths(deletions.uniq).each do |path|
668
+ Path.delete(copy, path)
669
+ end
670
+ [copy]
671
+ end
672
+
673
+ def update_value(op, right, current, input, context)
674
+ if op == '|='
675
+ values = take_block(right.instructions, current, context, 1)
676
+ return AssignmentSentinel.delete if values.empty?
677
+
678
+ return values.first
679
+ end
680
+
681
+ values = execute_filter(right, input, context)
682
+ return AssignmentSentinel.no_output if values.empty?
683
+ return Value.truthy?(current) ? current : values.first if op == '//='
684
+
685
+ apply_binary(op.delete_suffix('='), current, values.first)
686
+ end
687
+
688
+ def assign_value(instructions, copy, input, context, value)
689
+ return assign_slice(instructions, copy, input, context, value) if slice_assignment?(instructions)
690
+
691
+ paths_for_block(instructions, input, context).each { |path| copy = Path.set(copy, path, value) }
692
+ copy
693
+ end
694
+
695
+ def slice_assignment?(instructions)
696
+ %i[slice_const slice_filter].include?(instructions.last&.op)
697
+ end
698
+
699
+ def assign_slice(instructions, copy, input, context, replacement)
700
+ base = instructions[0...-1]
701
+ slice = instructions.last
702
+ paths_for_block(base, input, context).each do |path|
703
+ slice_bounds(slice, input, context).each do |start_index, finish_index|
704
+ target = Path.get(copy, path)
705
+ copy = Path.set(copy, path, replace_slice(target, start_index, finish_index, replacement))
706
+ end
707
+ end
708
+ copy
709
+ end
710
+
711
+ def slice_bounds(slice, input, context)
712
+ return [[slice.arg1, slice.arg2]] if slice.op == :slice_const
713
+
714
+ starts = slice.arg1[:start] ? execute_filter(slice.arg1.fetch(:start), input, context) : [nil]
715
+ finishes = slice.arg1[:finish] ? execute_filter(slice.arg1.fetch(:finish), input, context) : [nil]
716
+ starts.product(finishes)
717
+ end
718
+
719
+ def call_builtin_or_function(instruction, input, context)
720
+ name = instruction.arg1
721
+ arg_blocks = instruction.arg2
722
+
723
+ if arg_blocks.empty? && context.variables[filter_variable_name(name)].is_a?(BytecodeFilter)
724
+ return context.variables[filter_variable_name(name)].eval(input, context)
725
+ end
726
+
727
+ if context.functions.key?([name, arg_blocks.length])
728
+ return execute_user_function(input, context, context.functions.fetch([name, arg_blocks.length]), arg_blocks)
729
+ end
730
+
731
+ Builtins.call(name, input, context, arg_blocks.map { |block| BytecodeFilter.new(self, block) })
732
+ end
733
+
734
+ def execute_user_function(input, context, definition, arg_blocks)
735
+ call_contexts(input, context, definition, arg_blocks).flat_map do |ctx|
736
+ execute_filter(definition.body, input, ctx)
737
+ end
738
+ end
739
+
740
+ def call_contexts(input, context, definition, arg_blocks)
741
+ functions = (definition.closure || context.functions).merge([definition.name,
742
+ definition.params.length] => definition)
743
+ contexts = [context.with_functions(functions)]
744
+ definition.params.zip(arg_blocks).each do |param, block|
745
+ if param.start_with?('$')
746
+ values = execute_filter(block, input, context)
747
+ contexts = contexts.flat_map do |ctx|
748
+ values.map do |value|
749
+ ctx.with_variable(param.delete_prefix('$'), value)
750
+ end
751
+ end
752
+ else
753
+ filter = forwarded_filter(block, context) || BytecodeFilter.new(self, block, captured_context: context)
754
+ contexts = contexts.map { |ctx| ctx.with_variable(filter_variable_name(param), filter) }
755
+ end
756
+ end
757
+ contexts
758
+ end
759
+
760
+ def forwarded_filter(block, context)
761
+ return unless block.instructions.length == 1
762
+
763
+ instruction = block.instructions.first
764
+ return unless %i[call tail_call].include?(instruction.op) && instruction.arg2.empty?
765
+
766
+ context.variables[filter_variable_name(instruction.arg1)].then do |filter|
767
+ filter if filter.is_a?(BytecodeFilter)
768
+ end
769
+ end
770
+
771
+ def evaluate_variable(name, context, loc = nil)
772
+ return [ENV.to_h] if name == 'ENV'
773
+
774
+ if name == 'ARGS'
775
+ positional = context.variables.fetch('ARGS.positional', [])
776
+ named = context.variables.fetch('ARGS.named', {})
777
+ return [{ 'positional' => positional, 'named' => named }]
778
+ end
779
+ if name == '__loc__'
780
+ return [{ 'file' => loc&.filename || context.options.fetch(:source_path, '<top-level>'),
781
+ 'line' => loc&.line || 1 }]
782
+ end
783
+
784
+ raise RuntimeError, "variable $#{name} is not defined" unless context.variables.key?(name)
785
+
786
+ [context.variables[name]]
787
+ end
788
+
789
+ def evaluate_string(segments, input, context)
790
+ segments.reduce(['']) do |prefixes, segment|
791
+ suffixes =
792
+ if segment.fetch(:kind) == :text
793
+ [segment.fetch(:value)]
794
+ else
795
+ ctx = context_with_definitions(context, segment.fetch(:definitions))
796
+ execute_filter(segment.fetch(:block), input, ctx).map { |item| Builtins.to_string(item) }
797
+ end
798
+ prefixes.flat_map { |prefix| suffixes.map { |suffix| prefix + suffix } }
799
+ end
800
+ end
801
+
802
+ def evaluate_format(name, spec, input, context)
803
+ return Builtins.call(name, input, context, []) unless spec
804
+
805
+ if spec[:segments]
806
+ return spec.fetch(:segments).reduce(['']) do |prefixes, segment|
807
+ suffixes = format_segment(name, segment, input, context)
808
+ prefixes.flat_map { |prefix| suffixes.map { |suffix| prefix + suffix } }
809
+ end
810
+ end
811
+
812
+ execute_filter(spec.fetch(:block), input, context).flat_map { |value| Builtins.call(name, value, context, []) }
813
+ end
814
+
815
+ def format_segment(name, segment, input, context)
816
+ return [segment.fetch(:value)] if segment.fetch(:kind) == :text
817
+
818
+ ctx = context_with_definitions(context, segment.fetch(:definitions))
819
+ execute_filter(segment.fetch(:block), input, ctx).map do |item|
820
+ Builtins.to_string(Builtins.call(name, item, context, []).first)
821
+ end
822
+ end
823
+
824
+ def execute_path_instruction(instruction, stack, input, context)
825
+ charge_instruction!
826
+ case instruction.op
827
+ when :load_input then stack << [context.current_path]
828
+ when :variable
829
+ if context.binding_variable_paths.key?(instruction.arg1)
830
+ stack << [context.binding_variable_paths.fetch(instruction.arg1)]
831
+ else
832
+ result = execute_block([instruction], input, context).first
833
+ raise InvalidPathError.new(invalid_path_message([instruction], result, input, context), result)
834
+ end
835
+ when :field then stack << stack.pop.map { |path| path + [instruction.arg1] }
836
+ when :index_const then stack << stack.pop.map do |path|
837
+ path + [path_index(Path.get(input, path), instruction.arg1)]
838
+ end
839
+ when :index_filter then stack << index_filter_paths(stack.pop, instruction.arg1, input, context)
840
+ when :slice_const then stack << slice_paths(stack.pop, input, instruction.arg1, instruction.arg2)
841
+ when :slice_filter then stack << slice_filter_paths(stack.pop, instruction.arg1, input, context)
842
+ when :each then stack << each_paths(stack.pop, input)
843
+ when :pipe then stack << pipe_paths(stack.pop, instruction.arg1, input, context)
844
+ when :append
845
+ left = stack.pop
846
+ begin
847
+ stack << (left + paths_for_block(instruction.arg1.instructions, input, context))
848
+ rescue Rjq::RuntimeError => e
849
+ raise e.prepend_outputs(left)
850
+ end
851
+ when :binding then stack << binding_paths(instruction.arg1, input, context)
852
+ when :branch then stack << branch_paths(instruction, input, context)
853
+ when :optional then stack << optional_paths(instruction.arg1, input, context)
854
+ when :call, :tail_call then stack << call_paths(instruction, input, context)
855
+ when :recurse then stack << Path.paths(input, leaves_only: false)
856
+ when :scoped_def then stack << paths_for_block(instruction.arg2.instructions, input,
857
+ apply_definition(context, instruction.arg1))
858
+ else
859
+ result = execute_block([instruction], input, context)
860
+ result = result.first if result.length == 1
861
+ raise InvalidPathError.new(invalid_path_message([instruction], result, input, context), result)
862
+ end
863
+ end
864
+
865
+ def index_filter_paths(paths, block, input, context)
866
+ indices = execute_filter(block, input, context)
867
+ paths.flat_map { |path| indices.map { |index| path + [path_index(Path.get(input, path), index)] } }
868
+ end
869
+
870
+ def slice_paths(paths, input, start_index, finish_index)
871
+ paths.map { |path| path + [{ 'start' => start_index, 'end' => finish_index }] }
872
+ end
873
+
874
+ def slice_filter_paths(paths, spec, input, context)
875
+ starts = spec[:start] ? execute_filter(spec.fetch(:start), input, context) : [nil]
876
+ finishes = spec[:finish] ? execute_filter(spec.fetch(:finish), input, context) : [nil]
877
+ starts.product(finishes).flat_map do |start_index, finish_index|
878
+ slice_paths(paths, input, start_index, finish_index)
879
+ end
880
+ end
881
+
882
+ def each_paths(paths, input)
883
+ paths.flat_map do |path|
884
+ value = Path.get(input, path)
885
+ keys =
886
+ case value
887
+ when Array then (0...value.length).to_a
888
+ when Hash then value.keys
889
+ else raise TypeError, "cannot iterate over #{Value.type_of(value)}"
890
+ end
891
+ keys.map { |key| path + [key] }
892
+ end
893
+ rescue InvalidPathError => e
894
+ raise InvalidPathError.new(invalid_path_message([Instruction.new(op: :each)], e.result, input, nil), e.result)
895
+ end
896
+
897
+ def pipe_paths(paths, block, input, context)
898
+ paths.flat_map do |path|
899
+ value = Path.get(input, path)
900
+ nested = context.with_path_state(current_path: [])
901
+ paths_for_block(block.instructions, value, nested).map { |suffix| path + suffix }
902
+ end
903
+ rescue InvalidPathError => e
904
+ raise InvalidPathError.new(invalid_path_message(block.instructions, e.result, input, context), e.result)
905
+ end
906
+
907
+ def binding_paths(spec, input, context)
908
+ execute_filter(spec.fetch(:source), input, context).flat_map do |value|
909
+ pattern = spec.fetch(:pattern)
910
+ candidates = pattern[0] == :alternatives ? pattern[1] : [pattern]
911
+ candidate_index = 0
912
+ validated = []
913
+ loop do
914
+ candidate = candidates.fetch(candidate_index)
915
+ begin
916
+ bound, pattern_path, relative_variables =
917
+ AST.bind_pattern_candidate_with_path(context, pattern, candidate, value)
918
+ rescue Rjq::RuntimeError => e
919
+ candidate_index += 1
920
+ next if candidate_index < candidates.length
921
+
922
+ raise e.prepend_outputs(validated)
923
+ end
924
+
925
+ base_path = context.current_path + pattern_path
926
+ variable_paths = context.binding_variable_paths.to_h { |name, _path| [name, base_path] }
927
+ variable_paths.merge!(relative_variables.transform_values { |path| context.current_path + path })
928
+ AST.validate_pattern_path(input, base_path)
929
+ scoped = bound.with_path_state(current_path: base_path, variables: variable_paths)
930
+ results, paths, error = execute_with_replayed_paths(spec.fetch(:body), input, scoped)
931
+
932
+ if candidates[(candidate_index + 1)..].to_a.any? { |item| item[0] == :var }
933
+ paths = paths.each_with_index.map do |path, index|
934
+ path == base_path && !AST.binding_path_matches?(input, path, results[index]) ? context.current_path : path
935
+ end
936
+ end
937
+
938
+ missing_names = AST.pattern_variable_names(pattern) - AST.pattern_variable_names(candidate)
939
+ missing_paths = missing_names.filter_map { |name| variable_paths[name] }
940
+ switch_at = paths.index { |path| path != base_path && missing_paths.include?(path) } unless value.nil?
941
+ if switch_at && candidate_index + 1 < candidates.length
942
+ begin
943
+ validated.concat(AST.validate_binding_results(input, results.first(switch_at), paths.first(switch_at)))
944
+ rescue Rjq::RuntimeError => e
945
+ raise e.prepend_outputs(validated)
946
+ end
947
+ candidate_index += 1
948
+ next
949
+ end
950
+
951
+ begin
952
+ validated.concat(AST.validate_binding_results(input, results, paths))
953
+ rescue Rjq::RuntimeError => e
954
+ raise e.prepend_outputs(validated)
955
+ end
956
+ raise error.prepend_outputs(validated) if error
957
+
958
+ break validated
959
+ end
960
+ end
961
+ end
962
+
963
+ def execute_with_replayed_paths(block, input, context)
964
+ queue = context.options[:input_queue]
965
+ recording = queue && RecordingInputQueue.new(queue)
966
+ first_context = recording ? context.with_options(context.options.merge(input_queue: recording,
967
+ remaining_inputs: recording)) : context
968
+ values = []
969
+ error = nil
970
+ begin
971
+ values = execute_filter(block, input, first_context)
972
+ rescue Rjq::RuntimeError => e
973
+ values = e.take_outputs
974
+ error = e
975
+ end
976
+
977
+ playback = recording&.playback
978
+ second_context = playback ? context.with_options(context.options.merge(input_queue: playback,
979
+ remaining_inputs: playback)) : context
980
+ paths = []
981
+ begin
982
+ paths = paths_for_block(block.instructions, input, second_context)
983
+ rescue Rjq::RuntimeError => e
984
+ paths = e.take_outputs
985
+ error ||= e
986
+ end
987
+ [values, paths, error]
988
+ end
989
+
990
+ def branch_paths(instruction, input, context)
991
+ then_block, else_block = instruction.arg2
992
+ execute_filter(instruction.arg1, input, context).flat_map do |value|
993
+ paths_for_block((Value.truthy?(value) ? then_block : else_block).instructions, input, context)
994
+ end
995
+ end
996
+
997
+ def optional_paths(block, input, context)
998
+ paths_for_block(block.instructions, input, context)
999
+ rescue Rjq::ResourceLimitError
1000
+ raise
1001
+ rescue Rjq::RuntimeError
1002
+ []
1003
+ end
1004
+
1005
+ def call_paths(instruction, input, context)
1006
+ name = instruction.arg1
1007
+ arg_blocks = instruction.arg2
1008
+ return [context.current_path] if %w[debug stderr].include?(name)
1009
+ if name == 'select' && arg_blocks.length == 1
1010
+ return execute_filter(arg_blocks.first, input, context).filter_map { |value| [] if Value.truthy?(value) }
1011
+ end
1012
+ return [] if (name == 'select' && arg_blocks.length == 1) || (name == 'empty' && arg_blocks.empty?)
1013
+ return [[0]] if name == 'first' && arg_blocks.empty?
1014
+ return [[-1]] if name == 'last' && arg_blocks.empty?
1015
+ if name == 'getpath' && arg_blocks.length == 1
1016
+ return arg_blocks.first.then do |block|
1017
+ execute_filter(block, input, context).map do |path|
1018
+ Array(path)
1019
+ end
1020
+ end
1021
+ end
1022
+
1023
+ if arg_blocks.empty? && context.variables[filter_variable_name(name)].is_a?(BytecodeFilter)
1024
+ return context.variables[filter_variable_name(name)].paths(input, context)
1025
+ end
1026
+
1027
+ if context.functions.key?([name, arg_blocks.length])
1028
+ definition = context.functions.fetch([name, arg_blocks.length])
1029
+ return with_call_frame do
1030
+ call_contexts(input, context, definition, arg_blocks).flat_map do |ctx|
1031
+ paths_for_block(definition.body.instructions, input, ctx)
1032
+ end
1033
+ end
1034
+ end
1035
+
1036
+ result = call_builtin_or_function(instruction, input, context)
1037
+ return [] if result.empty?
1038
+
1039
+ result = result.first if result.length == 1
1040
+ raise InvalidPathError.new(invalid_path_message([instruction], result, input, context), result)
1041
+ end
1042
+
1043
+ def invalid_path_message(instructions, result, input, context)
1044
+ instruction = instructions.find { |item| item.op != :load_input }
1045
+ return "Invalid path expression with result #{JSON::Dumper.dump(result, indent: nil)}" unless instruction
1046
+
1047
+ case instruction.op
1048
+ when :field
1049
+ "Invalid path expression near attempt to access element #{instruction.arg1.inspect} of #{JSON::Dumper.dump(
1050
+ result, indent: nil
1051
+ )}"
1052
+ when :index_const
1053
+ "Invalid path expression near attempt to access element #{JSON::Dumper.dump(instruction.arg1,
1054
+ indent: nil)} of #{JSON::Dumper.dump(
1055
+ result, indent: nil
1056
+ )}"
1057
+ when :index_filter
1058
+ index = execute_filter(instruction.arg1, input, context).first
1059
+ "Invalid path expression near attempt to access element #{JSON::Dumper.dump(index,
1060
+ indent: nil)} of #{JSON::Dumper.dump(
1061
+ result, indent: nil
1062
+ )}"
1063
+ when :each
1064
+ "Invalid path expression near attempt to iterate through #{JSON::Dumper.dump(result, indent: nil)}"
1065
+ when :pipe
1066
+ invalid_path_message(instruction.arg1.instructions, result, input, context)
1067
+ else
1068
+ "Invalid path expression with result #{JSON::Dumper.dump(result, indent: nil)}"
1069
+ end
1070
+ end
1071
+
1072
+ def apply_unary(op, value)
1073
+ case op
1074
+ when '-'
1075
+ raise TypeError, "#{Value.type_of(value)} (#{short_dump(value)}) cannot be negated" unless value.is_a?(Numeric)
1076
+
1077
+ return -0.0 if value.zero?
1078
+
1079
+ value * -1
1080
+ when 'not'
1081
+ !Value.truthy?(value)
1082
+ else
1083
+ raise "unknown unary operator #{op}"
1084
+ end
1085
+ end
1086
+
1087
+ def apply_binary(op, left, right)
1088
+ case op
1089
+ when '+' then add_values(left, right)
1090
+ when '-' then subtract_values(left, right)
1091
+ when '*' then multiply_values(left, right)
1092
+ when '/' then divide_values(left, right)
1093
+ when '%' then modulo_values(left, right)
1094
+ when '==' then Value.equal?(left, right)
1095
+ when '!=' then !Value.equal?(left, right)
1096
+ when '<' then Value.compare(left, right).negative?
1097
+ when '<=' then Value.compare(left, right) <= 0
1098
+ when '>' then Value.compare(left, right).positive?
1099
+ when '>=' then Value.compare(left, right) >= 0
1100
+ else raise "unknown operator #{op}"
1101
+ end
1102
+ end
1103
+
1104
+ def add_values(left, right)
1105
+ return right if left.nil?
1106
+ return left if right.nil?
1107
+ return numeric_pair(left, right).then { |a, b| a + b } if left.is_a?(Numeric) && right.is_a?(Numeric)
1108
+ return left + right if left.is_a?(String) && right.is_a?(String)
1109
+ return left + right if left.is_a?(Array) && right.is_a?(Array)
1110
+ return left.merge(right) if left.is_a?(Hash) && right.is_a?(Hash)
1111
+
1112
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be added"
1113
+ end
1114
+
1115
+ def subtract_values(left, right)
1116
+ return numeric_pair(left, right).then { |a, b| a - b } if left.is_a?(Numeric) && right.is_a?(Numeric)
1117
+ if left.is_a?(Array) && right.is_a?(Array)
1118
+ return left.reject do |item|
1119
+ right.any? do |other|
1120
+ Value.equal?(item, other)
1121
+ end
1122
+ end
1123
+ end
1124
+
1125
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be subtracted"
1126
+ end
1127
+
1128
+ def multiply_values(left, right)
1129
+ return numeric_pair(left, right).then { |a, b| a * b } if left.is_a?(Numeric) && right.is_a?(Numeric)
1130
+ return repeat_string(left, right) if left.is_a?(String) && right.is_a?(Numeric)
1131
+ return repeat_string(right, left) if right.is_a?(String) && left.is_a?(Numeric)
1132
+ return recursive_merge(left, right) if left.is_a?(Hash) && right.is_a?(Hash)
1133
+
1134
+ raise TypeError, "#{Value.type_of(left)} and #{Value.type_of(right)} cannot be multiplied"
1135
+ end
1136
+
1137
+ def divide_values(left, right)
1138
+ if left.is_a?(String) && right.is_a?(String)
1139
+ return left.each_char.to_a if right.empty?
1140
+
1141
+ return left.split(right, -1)
1142
+ end
1143
+
1144
+ left_number, right_number = numeric_pair(numeric(left), numeric(right))
1145
+ raise TypeError, division_by_zero_message(left_number, right_number, 'divided') if right_number.zero?
1146
+
1147
+ left_number.fdiv(right_number)
1148
+ end
1149
+
1150
+ def modulo_values(left, right)
1151
+ left_number, right_number = numeric_pair(numeric(left), numeric(right))
1152
+ return Float::NAN if nan_number?(left_number) || nan_number?(right_number)
1153
+
1154
+ left_integer = jq_integer(left_number)
1155
+ right_integer = jq_integer(right_number)
1156
+ raise TypeError, division_by_zero_message(left_number, right_number, 'divided (remainder)') if right_integer.zero?
1157
+
1158
+ remainder = left_integer.remainder(right_integer)
1159
+ if remainder.zero? && left_number.is_a?(Float) && left_number.zero? && (1.0 / left_number).negative?
1160
+ return -0.0
1161
+ end
1162
+ return remainder.to_f.round(-3) if (nonfinite_number?(left_number) || nonfinite_number?(right_number)) &&
1163
+ unsafe_integer?(remainder)
1164
+
1165
+ unsafe_integer?(remainder) ? remainder.to_f : remainder
1166
+ end
1167
+
1168
+ def jq_integer(value)
1169
+ return (2**63) - 1 if value.respond_to?(:infinite?) && value.infinite? == 1
1170
+ return -(2**63) if value.respond_to?(:infinite?) && value.infinite? == -1
1171
+
1172
+ [[value.to_i, -(2**63)].max, (2**63) - 1].min
1173
+ end
1174
+
1175
+ def nonfinite_number?(value)
1176
+ value.respond_to?(:finite?) && !value.finite?
1177
+ end
1178
+
1179
+ def numeric_pair(left, right)
1180
+ return [left.to_f, right.to_f] if unsafe_integer?(left) || unsafe_integer?(right)
1181
+
1182
+ [left, right]
1183
+ end
1184
+
1185
+ def unsafe_integer?(value)
1186
+ value.is_a?(Integer) && value.abs > (2**53)
1187
+ end
1188
+
1189
+ def repeat_string(string, count)
1190
+ return nil if count.respond_to?(:nan?) && count.nan?
1191
+
1192
+ count = count.floor
1193
+ return nil if count.negative?
1194
+
1195
+ string * count
1196
+ end
1197
+
1198
+ def recursive_merge(left, right)
1199
+ Value.merge_objects(left, right)
1200
+ end
1201
+
1202
+ def replace_slice(target, start_index, finish_index, replacement)
1203
+ case target
1204
+ when Array
1205
+ raise TypeError, 'can only assign an array to an array slice' unless replacement.is_a?(Array)
1206
+
1207
+ range = range_for(target.length, start_index, finish_index)
1208
+ target[0...range.begin] + Value.deep_copy(replacement) + target[range.end..].to_a
1209
+ when String
1210
+ raise TypeError, 'Cannot update string slices'
1211
+ else
1212
+ raise TypeError, "cannot slice #{Value.type_of(target)}"
1213
+ end
1214
+ end
1215
+
1216
+ def read_field(value, name)
1217
+ return nil if value.nil?
1218
+ return value[name] if value.is_a?(Hash)
1219
+
1220
+ raise TypeError, "Cannot index #{Value.type_of(value)} with string #{name.inspect}"
1221
+ end
1222
+
1223
+ def read_index(value, index)
1224
+ Path.read_index(value, index)
1225
+ end
1226
+
1227
+ def read_slice(value, start_index, finish_index)
1228
+ return nil if value.nil?
1229
+
1230
+ case value
1231
+ when Array
1232
+ value[range_for(value.length, start_index, finish_index)] || []
1233
+ when String
1234
+ value.each_char.to_a[range_for(value.each_char.count, start_index, finish_index)].join
1235
+ else
1236
+ raise TypeError, "cannot slice #{Value.type_of(value)}"
1237
+ end
1238
+ end
1239
+
1240
+ def each_value(value)
1241
+ case value
1242
+ when Array then value
1243
+ when Hash then value.values
1244
+ else raise TypeError, "Cannot iterate over #{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)})"
1245
+ end
1246
+ end
1247
+
1248
+ def path_index(value, index)
1249
+ unless index.is_a?(String) || index.is_a?(Numeric)
1250
+ raise TypeError, "Cannot index #{Value.type_of(value)} with #{Value.type_of(index)}"
1251
+ end
1252
+ return index unless index.is_a?(Numeric)
1253
+ return index if index.respond_to?(:nan?) && index.nan?
1254
+ if index.respond_to?(:infinite?) && index.infinite?
1255
+ raise RuntimeError, 'Out of bounds negative array index' if index.negative? && !value.is_a?(Hash)
1256
+ raise TypeError, "Cannot index #{Value.type_of(value)} with number"
1257
+ end
1258
+
1259
+ index = index.floor
1260
+ return index unless index.negative?
1261
+
1262
+ length =
1263
+ case value
1264
+ when Array then value.length
1265
+ when String then value.each_char.count
1266
+ else 0
1267
+ end
1268
+ normalized = length + index
1269
+ raise RuntimeError, 'Out of bounds negative array index' if normalized.negative?
1270
+
1271
+ normalized
1272
+ end
1273
+
1274
+ def range_for(length, start_index, finish_index)
1275
+ from = start_index.nil? || nan_number?(start_index) ? 0 : normalize_boundary(start_index, length, :floor)
1276
+ to = finish_index.nil? || nan_number?(finish_index) ? length : normalize_boundary(finish_index, length, :ceil)
1277
+ from...to
1278
+ end
1279
+
1280
+ def normalize_boundary(index, length, rounding)
1281
+ raise TypeError, 'slice index must be a number' unless index.is_a?(Numeric)
1282
+
1283
+ rounded = rounding == :ceil ? index.ceil : index.floor
1284
+ normalized = index.negative? ? length + rounded : rounded
1285
+ [[normalized, 0].max, length].min
1286
+ end
1287
+
1288
+ def numeric(value)
1289
+ raise TypeError, "#{Value.type_of(value)} is not a number" unless value.is_a?(Numeric)
1290
+
1291
+ value
1292
+ end
1293
+
1294
+ def nan_number?(value)
1295
+ value.respond_to?(:nan?) && value.nan?
1296
+ end
1297
+
1298
+ def short_dump(value)
1299
+ dumped = JSON::Dumper.dump(value, indent: nil)
1300
+ dumped.length > 14 ? "#{dumped[0, 11]}..." : dumped
1301
+ end
1302
+
1303
+ def division_by_zero_message(left, right, verb)
1304
+ "number (#{left}) and number (#{right}) cannot be #{verb} because the divisor is zero"
1305
+ end
1306
+
1307
+ def filter_variable_name(name)
1308
+ "filter:#{name}"
1309
+ end
1310
+ end
1311
+
1312
+ module AssignmentSentinel
1313
+ module_function
1314
+
1315
+ def delete
1316
+ @delete ||= Object.new.freeze
1317
+ end
1318
+
1319
+ def no_output
1320
+ @no_output ||= Object.new.freeze
1321
+ end
1322
+ end
1323
+
1324
+ class BytecodeFilter < AST::Node
1325
+ def initialize(vm, block, captured_context: nil)
1326
+ @vm = vm
1327
+ @block = block
1328
+ @captured_context = captured_context
1329
+ end
1330
+
1331
+ def eval(input, context)
1332
+ @vm.execute_block(@block.instructions, input, @captured_context || context)
1333
+ end
1334
+
1335
+ def stream(input, context)
1336
+ @vm.stream_block(@block, input, @captured_context || context)
1337
+ end
1338
+
1339
+ def take(input, context, count)
1340
+ @vm.take_block(@block.instructions, input, @captured_context || context, count)
1341
+ end
1342
+
1343
+ def paths(input, context)
1344
+ @vm.paths_for_block(@block.instructions, input, @captured_context || context)
1345
+ end
1346
+
1347
+ def source_any?(input, context, &)
1348
+ @vm.source_any?(@block.instructions, input, @captured_context || context, &)
1349
+ end
1350
+
1351
+ def source_all?(input, context, &)
1352
+ @vm.source_all?(@block.instructions, input, @captured_context || context, &)
1353
+ end
1354
+
1355
+ def replacement_filters
1356
+ @vm.replacement_filters(@block, @captured_context)
1357
+ end
1358
+ end
1359
+ end