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.
- checksums.yaml +7 -0
- data/ARCHITECTURE.md +38 -0
- data/CHANGELOG.md +7 -0
- data/COMPATIBILITY.md +48 -0
- data/CONTRIBUTING.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +192 -0
- data/SECURITY.md +10 -0
- data/bin/rjq +9 -0
- data/lib/rjq/ast.rb +1475 -0
- data/lib/rjq/builtins/array.rb +3 -0
- data/lib/rjq/builtins/core.rb +3 -0
- data/lib/rjq/builtins/date.rb +3 -0
- data/lib/rjq/builtins/format.rb +3 -0
- data/lib/rjq/builtins/io.rb +3 -0
- data/lib/rjq/builtins/math.rb +3 -0
- data/lib/rjq/builtins/regex.rb +3 -0
- data/lib/rjq/builtins/sql.rb +3 -0
- data/lib/rjq/builtins/stream.rb +3 -0
- data/lib/rjq/builtins/string.rb +3 -0
- data/lib/rjq/builtins.rb +2103 -0
- data/lib/rjq/cli.rb +459 -0
- data/lib/rjq/color.rb +36 -0
- data/lib/rjq/compiler.rb +392 -0
- data/lib/rjq/errors.rb +76 -0
- data/lib/rjq/json/dumper.rb +191 -0
- data/lib/rjq/json/input_buffer.rb +99 -0
- data/lib/rjq/json/parser.rb +405 -0
- data/lib/rjq/json/stream_parser.rb +526 -0
- data/lib/rjq/json.rb +4 -0
- data/lib/rjq/lexer.rb +344 -0
- data/lib/rjq/math_functions.rb +168 -0
- data/lib/rjq/module_loader.rb +178 -0
- data/lib/rjq/modules.rb +88 -0
- data/lib/rjq/number.rb +189 -0
- data/lib/rjq/opcodes.rb +130 -0
- data/lib/rjq/parser.rb +755 -0
- data/lib/rjq/path.rb +250 -0
- data/lib/rjq/runtime.rb +377 -0
- data/lib/rjq/semantic_analyzer.rb +209 -0
- data/lib/rjq/value.rb +287 -0
- data/lib/rjq/version.rb +5 -0
- data/lib/rjq/vm.rb +1359 -0
- data/lib/rjq.rb +41 -0
- metadata +106 -0
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
|