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.
@@ -0,0 +1,2103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'date'
5
+ require 'time'
6
+ require 'uri'
7
+
8
+ module Rjq
9
+ module Builtins
10
+ ZERO_ARITY_BUILTINS = %w[
11
+ empty length utf8bytelength type keys keys_unsorted values arrays objects iterables scalars booleans nulls
12
+ numbers strings not error halt halt_error input inputs debug stderr input_filename input_line_number null true
13
+ false infinite nan isinfinite isnan isnormal add any all flatten floor ceil round sqrt log log2 log10 exp exp2 exp10
14
+ pow10 atan abs cos sin tan acos asin cosh sinh tanh acosh asinh atanh cbrt significand logb gamma tgamma
15
+ lgamma lgamma_r frexp modf fabs nearbyint trunc rint j0 j1 y0 y1 erf erfc expm1 log1p isfinite finites normals
16
+ get_jq_origin get_prog_origin get_search_list to_entries from_entries to_number tonumber
17
+ tostring tojson fromjson ascii explode implode ascii_downcase ascii_upcase recurse recurse_down paths leaf_paths
18
+ tostream min max sort unique reverse combinations transpose first last env now gmtime localtime mktime fromdate
19
+ todate fromdateiso8601 todateiso8601 date builtins modulemeta
20
+ ].freeze
21
+ ONE_ARITY_BUILTINS = %w[
22
+ has in IN INDEX error halt_error debug flatten range any all with_entries select map map_values split join
23
+ ltrimstr rtrimstr startswith endswith index rindex indices recurse recurse_down path paths leaf_paths getpath
24
+ delpaths del pick walk fromstream truncate_stream min_by max_by sort_by group_by GROUP_BY unique_by UNIQUE_BY
25
+ contains inside combinations bsearch first last nth repeat isempty strftime strflocaltime strptime dateadd datesub
26
+ test match capture scan splits format
27
+ ].freeze
28
+ TWO_ARITY_BUILTINS = %w[
29
+ IN INDEX JOIN any all range recurse recurse_down pow atan2 ldexp scalb scalbln drem setpath nth limit until while split test match
30
+ scan splits sub gsub capture copysign fdim fmax fmin fmod hypot jn nextafter nexttoward remainder yn
31
+ ].freeze
32
+ THREE_ARITY_BUILTINS = %w[JOIN range fma sub gsub].freeze
33
+ FOUR_ARITY_BUILTINS = %w[JOIN].freeze
34
+
35
+ BUILTIN_ARITIES = [
36
+ [0, ZERO_ARITY_BUILTINS],
37
+ [1, ONE_ARITY_BUILTINS],
38
+ [2, TWO_ARITY_BUILTINS],
39
+ [3, THREE_ARITY_BUILTINS],
40
+ [4, FOUR_ARITY_BUILTINS]
41
+ ].each_with_object({}) do |(arity, names), registry|
42
+ names.each { |name| (registry[name] ||= []) << arity }
43
+ end.transform_values(&:freeze).freeze
44
+ BUILTIN_NAMES = BUILTIN_ARITIES.keys.freeze
45
+ EXTENSION_NAMES = %w[
46
+ GROUP_BY UNIQUE_BY ascii date dateadd datesub false leaf_paths null recurse_down to_number true
47
+ ].freeze
48
+ JQ_BUILTIN_NAMES = (BUILTIN_NAMES - EXTENSION_NAMES).freeze
49
+ EXTENSION_ARITIES = EXTENSION_NAMES.to_h { |name| [name, BUILTIN_ARITIES.fetch(name)] }.freeze
50
+ FORMAT_NAMES = %w[@text @json @html @uri @csv @tsv @sh @base64 @base64d @base32 @base32d].freeze
51
+ REGISTRY = BUILTIN_NAMES.to_h { |name| [name, true] }.freeze
52
+ FILTER_ARGUMENT_POSITIONS = {
53
+ 'IN' => [0, 1], 'INDEX' => [0, 1], 'JOIN' => [1, 2, 3],
54
+ 'any' => [0, 1], 'all' => [0, 1], 'with_entries' => [0], 'select' => [0], 'map' => [0],
55
+ 'map_values' => [0], 'recurse' => [0, 1], 'recurse_down' => [0, 1], 'path' => [0], 'paths' => [0],
56
+ 'leaf_paths' => [0], 'del' => [0], 'pick' => [0], 'walk' => [0], 'fromstream' => [0],
57
+ 'truncate_stream' => [0], 'min_by' => [0], 'max_by' => [0], 'sort_by' => [0], 'group_by' => [0],
58
+ 'GROUP_BY' => [0], 'unique_by' => [0], 'UNIQUE_BY' => [0], 'first' => [0], 'last' => [0], 'nth' => [0, 1],
59
+ 'limit' => [1], 'until' => [0, 1], 'while' => [0, 1], 'repeat' => [0], 'isempty' => [0],
60
+ 'split' => [1], 'splits' => [1], 'sub' => [1], 'gsub' => [1]
61
+ }.transform_values(&:freeze).freeze
62
+ LEFT_OUTER_ARGUMENT_BUILTINS = %w[gsub range scan split splits sub].freeze
63
+ FLATTEN_UNBOUNDED = Object.new.freeze
64
+
65
+ module_function
66
+
67
+ def call(name, input, context, args)
68
+ call_stream(name, input, context, args).to_a
69
+ end
70
+
71
+ def call_stream(name, input, context, args)
72
+ Enumerator.new do |yielder|
73
+ each_resolved_argument_set(name, args, input, context) do |resolved_args|
74
+ dispatch(name, input, context, resolved_args).each { |value| yielder << value }
75
+ end
76
+ end
77
+ end
78
+
79
+ def dispatch(name, input, context, args)
80
+ return call(name, args.fetch(0).eval(input, context).first, context, []) if name.start_with?('@') && !args.empty?
81
+
82
+ case name
83
+ when 'empty'
84
+ []
85
+ when 'length'
86
+ [length(input)]
87
+ when 'utf8bytelength'
88
+ [utf8_byte_length(input)]
89
+ when 'type'
90
+ [Value.type_of(input)]
91
+ when 'keys'
92
+ [keys(input, sorted: true)]
93
+ when 'keys_unsorted'
94
+ [keys(input, sorted: false)]
95
+ when 'values'
96
+ input.nil? ? [] : [input]
97
+ when 'arrays'
98
+ input.is_a?(Array) ? [input] : []
99
+ when 'objects'
100
+ input.is_a?(Hash) ? [input] : []
101
+ when 'iterables'
102
+ input.is_a?(Array) || input.is_a?(Hash) ? [input] : []
103
+ when 'scalars'
104
+ input.is_a?(Array) || input.is_a?(Hash) ? [] : [input]
105
+ when 'booleans'
106
+ [true, false].include?(input) ? [input] : []
107
+ when 'nulls'
108
+ input.nil? ? [input] : []
109
+ when 'numbers'
110
+ input.is_a?(Numeric) ? [input] : []
111
+ when 'strings'
112
+ input.is_a?(String) ? [input] : []
113
+ when 'has'
114
+ [has?(input, eval_arg(args, 0, input, context))]
115
+ when 'in'
116
+ [has?(eval_arg(args, 0, input, context), input)]
117
+ when 'IN'
118
+ [in_sql?(input, context, args)]
119
+ when 'INDEX'
120
+ [index_sql(input, context, args)]
121
+ when 'JOIN'
122
+ [join_sql(input, context, args)]
123
+ when 'not'
124
+ [!Value.truthy?(input)]
125
+ when 'error'
126
+ raise ErrorValue, args.empty? ? input : eval_arg(args, 0, input, context)
127
+ when 'halt'
128
+ raise HaltError, nil
129
+ when 'halt_error'
130
+ raise HaltError.new(input, args.empty? ? 5 : eval_arg(args, 0, input, context).to_i)
131
+ when 'input'
132
+ input_builtin(context)
133
+ when 'inputs'
134
+ inputs_builtin(context)
135
+ when 'input_filename'
136
+ filename = current_input_record(context)&.filename || context.options.fetch(:current_filename, '<stdin>')
137
+ [filename || '<stdin>']
138
+ when 'input_line_number'
139
+ [current_input_record(context)&.line || context.options.fetch(:current_line, 1)]
140
+ when 'debug', 'stderr'
141
+ emit_diagnostic(name, input, context, args)
142
+ when 'null'
143
+ [nil]
144
+ when 'true'
145
+ [true]
146
+ when 'false'
147
+ [false]
148
+ when 'infinite'
149
+ [Float::INFINITY]
150
+ when 'nan'
151
+ [Float::NAN]
152
+ when 'isinfinite'
153
+ [input.is_a?(Float) && input.infinite? ? true : false]
154
+ when 'isnan'
155
+ [input.is_a?(Float) && input.nan?]
156
+ when 'isnormal'
157
+ [normal_number?(input)]
158
+ when 'isfinite'
159
+ [input.is_a?(Numeric) && input.to_f.finite?]
160
+ when 'finites'
161
+ input.is_a?(Numeric) && input.to_f.finite? ? [input] : []
162
+ when 'normals'
163
+ normal_number?(input) ? [input] : []
164
+ when 'add'
165
+ [add(input)]
166
+ when 'abs'
167
+ [absolute(input)]
168
+ when 'any'
169
+ [any?(input, context, args)]
170
+ when 'all'
171
+ [all?(input, context, args)]
172
+ when 'flatten'
173
+ args.empty? ? [flatten(input)] : args.fetch(0).eval(input, context).map { |depth| flatten(input, depth) }
174
+ when 'range'
175
+ range(input, context, args)
176
+ when 'floor', 'ceil', 'round', 'sqrt', 'log', 'log2', 'log10', 'exp', 'sin', 'cos', 'tan',
177
+ 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', 'cbrt',
178
+ 'trunc', 'fabs', 'gamma', 'tgamma', 'lgamma', 'significand', 'logb', 'nearbyint',
179
+ 'rint', 'frexp', 'modf', 'lgamma_r', 'j0', 'j1', 'y0', 'y1', 'erf', 'erfc', 'expm1', 'log1p'
180
+ [math_unary(name, input)]
181
+ when 'pow', 'atan2', 'ldexp', 'scalb', 'scalbln', 'fma', 'drem', 'copysign', 'fdim', 'fmax', 'fmin',
182
+ 'fmod', 'hypot', 'jn', 'nextafter', 'nexttoward', 'remainder', 'yn'
183
+ [math_nary(name, input, context, args)]
184
+ when 'exp2'
185
+ [2**numeric(input)]
186
+ when 'exp10', 'pow10'
187
+ [10**numeric(input)]
188
+ when 'to_entries'
189
+ [to_entries(input)]
190
+ when 'from_entries'
191
+ [from_entries(assert_array(input))]
192
+ when 'with_entries'
193
+ [from_entries(map_entries(input, context, args.fetch(0)))]
194
+ when 'select'
195
+ select(input, context, args.fetch(0))
196
+ when 'map'
197
+ [map_filter(input, context, args.fetch(0))]
198
+ when 'map_values'
199
+ [map_values(input, context, args.fetch(0))]
200
+ when 'to_number', 'tonumber'
201
+ [to_number(input)]
202
+ when 'tostring'
203
+ [to_string(input)]
204
+ when 'tojson', '@json'
205
+ [JSON::Dumper.dump(input, indent: nil)]
206
+ when 'fromjson'
207
+ [JSON::Parser.parse_one(assert_string(input))]
208
+ when 'ascii'
209
+ [JSON::Dumper.dump(to_string(input), indent: nil, ascii: true)[1...-1]]
210
+ when 'explode'
211
+ [assert_string(input).each_codepoint.to_a]
212
+ when 'implode'
213
+ [implode(input)]
214
+ when 'split'
215
+ [split(input, context, args)]
216
+ when 'join'
217
+ args.empty? ? [join(input, '')] : args.fetch(0).eval(input, context).map { |separator| join(input, separator) }
218
+ when 'ltrimstr'
219
+ [assert_string(input).delete_prefix(assert_string(eval_arg(args, 0, input, context)))]
220
+ when 'rtrimstr'
221
+ [assert_string(input).delete_suffix(assert_string(eval_arg(args, 0, input, context)))]
222
+ when 'ascii_downcase'
223
+ [assert_string(input).tr('A-Z', 'a-z')]
224
+ when 'ascii_upcase'
225
+ [assert_string(input).tr('a-z', 'A-Z')]
226
+ when 'startswith'
227
+ [assert_string(input).start_with?(assert_string(eval_arg(args, 0, input, context)))]
228
+ when 'endswith'
229
+ [assert_string(input).end_with?(assert_string(eval_arg(args, 0, input, context)))]
230
+ when 'index'
231
+ args.fetch(0).eval(input, context).map { |needle| index_of(input, needle) }
232
+ when 'rindex'
233
+ args.fetch(0).eval(input, context).map { |needle| rindex_of(input, needle) }
234
+ when 'indices'
235
+ args.fetch(0).eval(input, context).map { |needle| indices_of(input, needle) }
236
+ when 'recurse', 'recurse_down'
237
+ recurse(input, context, args)
238
+ when 'path'
239
+ args.fetch(0).paths(input, context)
240
+ when 'paths'
241
+ paths_builtin(input, context, args, leaves_only: false)
242
+ when 'leaf_paths'
243
+ paths_builtin(input, context, args, leaves_only: true)
244
+ when 'getpath'
245
+ [Path.get(input, eval_arg(args, 0, input, context))]
246
+ when 'setpath'
247
+ [Path.set(Value.deep_copy(input), eval_arg(args, 0, input, context), eval_arg(args, 1, input, context))]
248
+ when 'delpaths'
249
+ [delpaths(input, eval_arg(args, 0, input, context))]
250
+ when 'del'
251
+ [delete_paths(input, context, args)]
252
+ when 'pick'
253
+ [pick(input, context, args)]
254
+ when 'walk'
255
+ walk(input, context, args.fetch(0))
256
+ when 'tostream'
257
+ to_stream(input)
258
+ when 'fromstream'
259
+ from_stream(filter_stream(args.fetch(0), input, context))
260
+ when 'truncate_stream'
261
+ truncate_stream(input, context, args)
262
+ when 'min'
263
+ [extreme(input, :min)]
264
+ when 'max'
265
+ [extreme(input, :max)]
266
+ when 'min_by'
267
+ [extreme_by(input, context, args.fetch(0), :min)]
268
+ when 'max_by'
269
+ [extreme_by(input, context, args.fetch(0), :max)]
270
+ when 'sort'
271
+ [assert_array(input).sort { |a, b| Value.compare(a, b) }]
272
+ when 'sort_by'
273
+ [sort_by_filter(input, context, args.fetch(0))]
274
+ when 'group_by', 'GROUP_BY'
275
+ [group_by_filter(input, context, args.fetch(0))]
276
+ when 'unique'
277
+ [unique_values(assert_array(input))]
278
+ when 'unique_by', 'UNIQUE_BY'
279
+ [unique_by_filter(input, context, args.fetch(0))]
280
+ when 'reverse'
281
+ [assert_array(input).reverse]
282
+ when 'contains'
283
+ [contains?(input, eval_arg(args, 0, input, context))]
284
+ when 'inside'
285
+ [contains?(eval_arg(args, 0, input, context), input)]
286
+ when 'combinations'
287
+ combinations(input, context, args)
288
+ when 'transpose'
289
+ [transpose(input)]
290
+ when 'bsearch'
291
+ bsearch(input, context, args)
292
+ when 'first'
293
+ first_builtin(input, context, args)
294
+ when 'last'
295
+ last_builtin(input, context, args)
296
+ when 'nth'
297
+ nth(input, context, args)
298
+ when 'limit'
299
+ limit(input, context, args)
300
+ when 'until'
301
+ until_filter(input, context, args)
302
+ when 'while'
303
+ while_filter(input, context, args)
304
+ when 'repeat'
305
+ repeat_filter(input, context, args)
306
+ when 'isempty'
307
+ [args.fetch(0).take(input, context, 1).empty?]
308
+ when 'builtins'
309
+ [JQ_BUILTIN_NAMES.flat_map { |builtin| builtin_arities(builtin) }.sort]
310
+ when 'modulemeta'
311
+ [modulemeta(input, context)]
312
+ when 'env'
313
+ [ENV.to_h]
314
+ when 'now'
315
+ [Time.now.to_f]
316
+ when 'gmtime'
317
+ [time_array(Time.at(numeric(input)).utc)]
318
+ when 'localtime'
319
+ [time_array(Time.at(numeric(input)).localtime)]
320
+ when 'mktime'
321
+ [mktime(input)]
322
+ when 'strftime'
323
+ [strftime_builtin(input, context, args)]
324
+ when 'strflocaltime'
325
+ [strftime_builtin(input, context, args, local: true)]
326
+ when 'strptime'
327
+ [strptime(input, context, args)]
328
+ when 'fromdate', 'fromdateiso8601'
329
+ [Time.iso8601(assert_string(input)).to_f]
330
+ when 'todate', 'todateiso8601'
331
+ [Time.at(numeric(input)).utc.iso8601]
332
+ when 'date'
333
+ [Time.now.utc.iso8601]
334
+ when 'dateadd'
335
+ [Time.at(numeric(input) + numeric(eval_arg(args, 0, input, context))).to_f]
336
+ when 'datesub'
337
+ [Time.at(numeric(input) - numeric(eval_arg(args, 0, input, context))).to_f]
338
+ when 'test'
339
+ regex, flags = regexp(input, context, args)
340
+ match = regex.match(assert_string(input))
341
+ [match ? !(flags.include?('n') && match[0].empty?) : false]
342
+ when 'match'
343
+ match_builtin(input, context, args)
344
+ when 'capture'
345
+ capture_builtin(input, context, args)
346
+ when 'format'
347
+ format_builtin(input, context, args)
348
+ when 'scan'
349
+ scan_builtin(input, context, args)
350
+ when 'splits'
351
+ splits_builtin(input, context, args)
352
+ when 'sub'
353
+ substitute(input, context, args, global: false)
354
+ when 'gsub'
355
+ substitute(input, context, args, global: true)
356
+ when '@text'
357
+ [to_string(input)]
358
+ when '@html'
359
+ [html_escape(to_string(input))]
360
+ when '@uri'
361
+ [uri_escape(to_string(input))]
362
+ when '@base64'
363
+ [[to_string(input)].pack('m0')]
364
+ when '@base64d'
365
+ [decode_base64(input)]
366
+ when '@base32'
367
+ [base32_encode(to_string(input))]
368
+ when '@base32d'
369
+ [base32_decode(assert_string(input))]
370
+ when '@csv'
371
+ [format_csv(input)]
372
+ when '@tsv'
373
+ [format_tsv(input)]
374
+ when '@sh'
375
+ [format_sh(input)]
376
+ when 'get_jq_origin'
377
+ [context.options.fetch(:jq_origin, File.expand_path('../..', __dir__))]
378
+ when 'get_prog_origin'
379
+ source_path = context.options[:source_path]
380
+ [source_path ? File.dirname(File.expand_path(source_path)) : Dir.pwd]
381
+ when 'get_search_list'
382
+ [search_list(context)]
383
+ else
384
+ raise CompileError, "#{name}/#{args.length} is not defined"
385
+ end
386
+ rescue RegexpError => e
387
+ raise unless defined?(Regexp::TimeoutError) && e.is_a?(Regexp::TimeoutError)
388
+
389
+ raise Rjq::RuntimeError, 'regular expression match timeout'
390
+ end
391
+
392
+ def length(value)
393
+ case value
394
+ when NilClass
395
+ 0
396
+ when String
397
+ value.each_char.count
398
+ when Array, Hash
399
+ value.length
400
+ when Numeric
401
+ value.abs
402
+ else
403
+ raise TypeError, "cannot get length of #{Value.type_of(value)}"
404
+ end
405
+ end
406
+
407
+ def utf8_byte_length(value)
408
+ return value.bytesize if value.is_a?(String)
409
+
410
+ raise TypeError,
411
+ "#{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)}) only strings have UTF-8 byte length"
412
+ end
413
+
414
+ def keys(value, sorted:)
415
+ case value
416
+ when Array
417
+ (0...value.length).to_a
418
+ when Hash
419
+ sorted ? value.keys.sort : value.keys
420
+ else
421
+ raise TypeError, "cannot get keys of #{Value.type_of(value)}"
422
+ end
423
+ end
424
+
425
+ def has?(container, key)
426
+ case container
427
+ when Array
428
+ unless key.is_a?(Numeric)
429
+ raise TypeError, "Cannot check whether array has a #{Value.type_of(key)} key"
430
+ end
431
+
432
+ return false unless key.finite?
433
+
434
+ index = key.to_i
435
+ index >= 0 && index < container.length
436
+ when Hash
437
+ unless key.is_a?(String)
438
+ raise TypeError, "Cannot check whether object has a #{Value.type_of(key)} key"
439
+ end
440
+
441
+ container.key?(key)
442
+ when NilClass
443
+ false
444
+ else
445
+ raise TypeError,
446
+ "Cannot check whether #{Value.type_of(container)} has a #{Value.type_of(key)} key"
447
+ end
448
+ end
449
+
450
+ def input_builtin(context)
451
+ queue = context.options[:input_queue]
452
+ return [queue.shift] if queue && !queue.empty?
453
+
454
+ remaining = context.options.fetch(:remaining_inputs, [])
455
+ raise RuntimeError, 'break' if remaining.empty?
456
+
457
+ [remaining.first]
458
+ end
459
+
460
+ def inputs_builtin(context)
461
+ queue = context.options[:input_queue]
462
+ return queue.each_remaining if queue
463
+
464
+ context.options.fetch(:remaining_inputs, [])
465
+ end
466
+
467
+ def current_input_record(context)
468
+ context.options[:input_queue]&.current_record
469
+ end
470
+
471
+ def emit_diagnostic(name, input, context, args)
472
+ io = context.options[:stderr] || $stderr
473
+ diagnostic = args.empty? ? input : eval_arg(args, 0, input, context)
474
+ if name == 'debug'
475
+ io.puts(JSON::Dumper.dump(['DEBUG:', diagnostic], indent: nil))
476
+ else
477
+ io.puts(to_string(diagnostic))
478
+ end
479
+ [input]
480
+ end
481
+
482
+ def add(value)
483
+ assert_array(value).reduce(nil) do |sum, item|
484
+ sum.nil? ? item : AST::BinaryOp.new(AST::Literal.new(sum), '+', AST::Literal.new(item)).eval(nil, AST::Context.new).first
485
+ end
486
+ end
487
+
488
+ def any?(input, context, args)
489
+ if args.length == 2
490
+ return source_any?(args[0], input, context) do |value|
491
+ source_any?(args[1], value, context) { |result| Value.truthy?(result) }
492
+ end
493
+ end
494
+
495
+ values = args.empty? ? iterable_values(input) : input_values(input, context, args.first)
496
+ values.any? { |value| Value.truthy?(value) }
497
+ end
498
+
499
+ def all?(input, context, args)
500
+ if args.length == 2
501
+ return source_all?(args[0], input, context) do |value|
502
+ source_all?(args[1], value, context) { |result| Value.truthy?(result) }
503
+ end
504
+ end
505
+
506
+ values = args.empty? ? iterable_values(input) : input_values(input, context, args.first)
507
+ values.all? { |value| Value.truthy?(value) }
508
+ end
509
+
510
+ def flatten(value, depth = FLATTEN_UNBOUNDED)
511
+ if !depth.equal?(FLATTEN_UNBOUNDED) && Value.compare(depth, 0).negative?
512
+ raise RuntimeError, 'flatten depth must not be negative'
513
+ end
514
+
515
+ flatten_items(iterable_values(value), depth)
516
+ end
517
+
518
+ def flatten_items(items, depth)
519
+ output = []
520
+ stack = items.to_a.reverse_each.map { |item| [item, depth] }
521
+ until stack.empty?
522
+ item, item_depth = stack.pop
523
+ unbounded = item_depth.equal?(FLATTEN_UNBOUNDED)
524
+ unless item.is_a?(Array) && (unbounded || !Value.equal?(item_depth, 0))
525
+ output << item
526
+ next
527
+ end
528
+
529
+ next_depth = unbounded ? FLATTEN_UNBOUNDED : subtract_flatten_depth(item_depth)
530
+ item.reverse_each { |child| stack << [child, next_depth] }
531
+ end
532
+ output
533
+ end
534
+
535
+ def subtract_flatten_depth(depth)
536
+ AST::BinaryOp.new(AST::Literal.new(depth), '-', AST::Literal.new(1))
537
+ .eval(nil, AST::Context.new).first
538
+ end
539
+
540
+ def range(input, context, args)
541
+ numbers = args.map { |arg| numeric(arg.eval(input, context).first) }
542
+ from, to, step =
543
+ case numbers.length
544
+ when 1
545
+ [0, numbers[0], 1]
546
+ when 2
547
+ [numbers[0], numbers[1], 1]
548
+ when 3
549
+ numbers
550
+ else
551
+ raise RuntimeError, 'range expects 1 to 3 arguments'
552
+ end
553
+ raise RuntimeError, 'range step cannot be zero' if step.zero?
554
+
555
+ range_values(from, to, step)
556
+ end
557
+
558
+ def range_values(from, to, step)
559
+ Enumerator.new do |yielder|
560
+ current = from
561
+ comparison = step.positive? ? -> { current < to } : -> { current > to }
562
+ while comparison.call
563
+ yielder << current
564
+ current += step
565
+ end
566
+ end
567
+ end
568
+
569
+ def cartesian(sets)
570
+ return [[]] if sets.empty?
571
+
572
+ sets.reduce([[]]) do |acc, values|
573
+ acc.flat_map { |prefix| values.map { |value| prefix + [value] } }
574
+ end
575
+ end
576
+
577
+ def math_unary(name, input)
578
+ value = numeric(input)
579
+ case name
580
+ when 'floor' then value.floor
581
+ when 'ceil' then value.ceil
582
+ when 'round' then value.round
583
+ when 'sqrt' then Math.sqrt(value)
584
+ when 'log' then Math.log(value)
585
+ when 'log2' then Math.log2(value)
586
+ when 'log10' then Math.log10(value)
587
+ when 'exp' then Math.exp(value)
588
+ when 'sin' then Math.sin(value)
589
+ when 'cos' then Math.cos(value)
590
+ when 'tan' then Math.tan(value)
591
+ when 'asin' then Math.asin(value)
592
+ when 'acos' then Math.acos(value)
593
+ when 'atan' then Math.atan(value)
594
+ when 'sinh' then Math.sinh(value)
595
+ when 'cosh' then Math.cosh(value)
596
+ when 'tanh' then Math.tanh(value)
597
+ when 'asinh' then Math.asinh(value)
598
+ when 'acosh' then Math.acosh(value)
599
+ when 'atanh' then Math.atanh(value)
600
+ when 'cbrt' then Math.cbrt(value)
601
+ when 'trunc' then value.truncate
602
+ when 'fabs' then value.abs
603
+ when 'gamma', 'tgamma' then Math.gamma(value)
604
+ when 'lgamma' then Math.lgamma(value).first
605
+ when 'lgamma_r' then Math.lgamma(value)
606
+ when 'frexp' then Math.frexp(value)
607
+ when 'modf'
608
+ integral = value.truncate
609
+ [value - integral, integral]
610
+ when 'significand'
611
+ return 0 if value.zero?
612
+
613
+ fraction, = Math.frexp(value)
614
+ fraction * 2
615
+ when 'logb'
616
+ return -Float::INFINITY if value.zero?
617
+
618
+ Math.log2(value.abs).floor
619
+ when 'nearbyint', 'rint' then round_to_even(value)
620
+ when 'j0', 'j1', 'y0', 'y1' then MathFunctions.bessel(name, value)
621
+ when 'erf' then Math.erf(value)
622
+ when 'erfc' then Math.erfc(value)
623
+ when 'expm1' then Math.expm1(value)
624
+ when 'log1p' then Math.log1p(value)
625
+ end
626
+ rescue Math::DomainError
627
+ Float::NAN
628
+ end
629
+
630
+ def absolute(value)
631
+ if value.nil? || value == true || value == false
632
+ raise TypeError, "#{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)}) cannot be negated"
633
+ end
634
+ return value unless value.is_a?(Numeric)
635
+ if value.is_a?(Number)
636
+ return value unless value.literal.start_with?('-')
637
+ return value if value.literal.match?(/\A-0+(?:\.0+)?(?:[eE][+-]?\d+)?\z/)
638
+
639
+ return value.to_f.abs
640
+ end
641
+ return value if value.zero? || !value.negative?
642
+
643
+ value.abs
644
+ end
645
+
646
+ def math_nary(name, input, context, args)
647
+ values = args.empty? ? [numeric(input)] : args.map { |arg| numeric(arg.eval(input, context).first) }
648
+ case name
649
+ when 'pow' then values[0]**values[1]
650
+ when 'atan2' then Math.atan2(values[0], values[1])
651
+ when 'ldexp' then Math.ldexp(values[0], values[1].to_i)
652
+ when 'scalb' then MathFunctions.scalb(values[0], values[1])
653
+ when 'scalbln' then MathFunctions.scalbln(values[0], values[1])
654
+ when 'fma' then MathFunctions.fma(values[0], values[1], values[2])
655
+ when 'drem' then MathFunctions.remainder(values[0], values[1])
656
+ when 'copysign' then copy_sign(values[0], values[1])
657
+ when 'fdim' then values.any? { |value| value.to_f.nan? } ? Float::NAN : [values[0] - values[1], 0].max
658
+ when 'fmax' then float_extreme(values[0], values[1], :max)
659
+ when 'fmin' then float_extreme(values[0], values[1], :min)
660
+ when 'fmod' then values[0].remainder(values[1])
661
+ when 'hypot' then Math.hypot(values[0], values[1])
662
+ when 'jn', 'yn' then MathFunctions.bessel(name, values[0].to_i, values[1])
663
+ when 'nextafter', 'nexttoward' then next_float_toward(values[0], values[1])
664
+ when 'remainder' then MathFunctions.remainder(values[0], values[1])
665
+ end
666
+ rescue Math::DomainError, FloatDomainError, ZeroDivisionError
667
+ Float::NAN
668
+ end
669
+
670
+ def normal_number?(value)
671
+ return false unless value.is_a?(Numeric)
672
+
673
+ float = value.to_f
674
+ float.finite? && float.abs >= Float::MIN
675
+ end
676
+
677
+ def round_to_even(value)
678
+ rounded = value.to_f.round(half: :even)
679
+ rounded.zero? && value.to_f.negative? ? -0.0 : rounded
680
+ end
681
+
682
+ def copy_sign(magnitude, sign)
683
+ negative = sign.to_f.negative? || (sign.to_f.zero? && (1.0 / sign.to_f).negative?)
684
+ negative ? -magnitude.to_f.abs : magnitude.to_f.abs
685
+ end
686
+
687
+ def float_extreme(left, right, mode)
688
+ return right if left.to_f.nan?
689
+ return left if right.to_f.nan?
690
+
691
+ [left, right].public_send(mode)
692
+ end
693
+
694
+ def next_float_toward(value, target)
695
+ value = value.to_f
696
+ target = target.to_f
697
+ return target if value == target
698
+ return Float::NAN if value.nan? || target.nan?
699
+
700
+ value < target ? value.next_float : value.prev_float
701
+ end
702
+
703
+ def ieee_remainder(left, right)
704
+ quotient = (left.to_f / right.to_f).round(half: :even)
705
+ left.to_f - (right.to_f * quotient)
706
+ end
707
+
708
+ def to_entries(value)
709
+ case value
710
+ when Hash
711
+ value.map { |key, item| { 'key' => key, 'value' => item } }
712
+ when Array
713
+ value.each_with_index.map { |item, index| { 'key' => index, 'value' => item } }
714
+ else
715
+ raise TypeError, "cannot convert #{Value.type_of(value)} to entries"
716
+ end
717
+ end
718
+
719
+ def from_entries(entries)
720
+ entries.each_with_object({}) do |entry, object|
721
+ key = %w[key Key name Name].lazy.map { |name| Path.read_index(entry, name) }
722
+ .find { |candidate| Value.truthy?(candidate) }
723
+ unless key.is_a?(String)
724
+ raise TypeError,
725
+ "Cannot use #{Value.type_of(key)} (#{JSON::Dumper.dump(key, indent: nil)}) as object key"
726
+ end
727
+ value = entry.key?('value') ? entry['value'] : entry['Value']
728
+ object[key] = value
729
+ end
730
+ end
731
+
732
+ def map_entries(input, context, filter)
733
+ to_entries(input).flat_map do |entry|
734
+ collect_filter(filter, entry, context)
735
+ end
736
+ end
737
+
738
+ def select(input, context, filter)
739
+ Enumerator.new do |yielder|
740
+ filter_stream(filter, input, context).each do |value|
741
+ yielder << input if Value.truthy?(value)
742
+ end
743
+ end
744
+ end
745
+
746
+ def map_filter(value, context, filter)
747
+ items = value.is_a?(Hash) ? value.values : assert_array(value)
748
+ items.flat_map { |item| collect_filter(filter, item, context) }
749
+ end
750
+
751
+ def map_values(value, context, filter)
752
+ case value
753
+ when Array
754
+ value.each_with_object([]) do |item, out|
755
+ outputs = filter_stream(filter, item, context).take(1)
756
+ out << outputs.first unless outputs.empty?
757
+ end
758
+ when Hash
759
+ value.each_with_object({}) do |(key, item), out|
760
+ outputs = filter_stream(filter, item, context).take(1)
761
+ out[key] = outputs.first unless outputs.empty?
762
+ end
763
+ else
764
+ raise TypeError,
765
+ "Cannot iterate over #{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)})"
766
+ end
767
+ end
768
+
769
+ def to_number(value)
770
+ return value if value.is_a?(Numeric)
771
+ raise TypeError, "cannot convert #{Value.type_of(value)} to number" unless value.is_a?(String)
772
+
773
+ value.match?(/[.eE]/) ? Float(value) : Integer(value, 10)
774
+ rescue ArgumentError
775
+ raise TypeError, 'invalid numeric string'
776
+ end
777
+
778
+ def to_string(value)
779
+ case value
780
+ when String
781
+ value
782
+ else
783
+ JSON::Dumper.dump(value, indent: nil)
784
+ end
785
+ end
786
+
787
+ def split(input, context, args)
788
+ string = assert_string(input)
789
+ if args.length > 1
790
+ return split_at_matches(string, regexp_matches(input, context, args, string))
791
+ end
792
+
793
+ separator = assert_string(eval_arg(args, 0, input, context))
794
+ return string.each_char.to_a if separator.empty?
795
+
796
+ string.split(separator, -1)
797
+ end
798
+
799
+ def split_at_matches(string, matches)
800
+ return [string] if matches.empty?
801
+
802
+ offset = 0
803
+ matches.map do |match|
804
+ part = string[offset...match.begin(0)].to_s
805
+ offset = match.end(0)
806
+ part
807
+ end << string[offset..].to_s
808
+ end
809
+
810
+ def implode(input)
811
+ raise TypeError, 'implode input must be an array' unless input.is_a?(Array)
812
+
813
+ input.map do |item|
814
+ unless item.is_a?(Numeric) && !item.to_f.nan?
815
+ raise TypeError,
816
+ "#{Value.type_of(item)} (#{JSON::Dumper.dump(item,
817
+ indent: nil)}) can't be imploded, unicode codepoint needs to be numeric"
818
+ end
819
+
820
+ codepoint = item.to_f.finite? ? item.to_i : -1
821
+ codepoint = 0xFFFD if codepoint.negative? || codepoint > 0x10FFFF || codepoint.between?(0xD800, 0xDFFF)
822
+ [codepoint].pack('U')
823
+ end.join
824
+ end
825
+
826
+ def join(input, separator)
827
+ out = +''
828
+ assert_array(input).each_with_index do |item, index|
829
+ out << separator.to_s if index.positive?
830
+ case item
831
+ when nil
832
+ nil
833
+ when String, Numeric, TrueClass, FalseClass
834
+ out << to_string(item)
835
+ else
836
+ raise TypeError,
837
+ "string (#{short_dump(out)}) and #{Value.type_of(item)} (#{short_dump(item)}) cannot be added"
838
+ end
839
+ end
840
+ out
841
+ end
842
+
843
+ def short_dump(value)
844
+ dumped = JSON::Dumper.dump(value, indent: nil)
845
+ return "#{dumped[0, 11]}..." if value.is_a?(Hash) && dumped.length > 14
846
+
847
+ dumped.length > 18 ? "#{dumped[0, 15]}..." : dumped
848
+ end
849
+
850
+ def recurse(input, context, args)
851
+ Enumerator.new do |yielder|
852
+ stack = [[input].each]
853
+ until stack.empty?
854
+ begin
855
+ value = stack.last.next
856
+ rescue StopIteration
857
+ stack.pop
858
+ next
859
+ end
860
+ yielder << value
861
+ children = if args.empty?
862
+ case value
863
+ when Array then value.each
864
+ when Hash then value.each_value
865
+ else [].each
866
+ end
867
+ else
868
+ filter_stream(args.first, value, context)
869
+ end
870
+ if args.length > 1
871
+ condition = args[1]
872
+ source_children = children
873
+ children = Enumerator.new do |child_yielder|
874
+ source_children.each do |child|
875
+ filter_stream(condition, child, context).each do |result|
876
+ child_yielder << child if Value.truthy?(result)
877
+ end
878
+ end
879
+ end
880
+ end
881
+ stack << children.each
882
+ end
883
+ end
884
+ end
885
+
886
+ def delpaths(input, paths)
887
+ raise TypeError, 'Paths must be specified as an array' unless paths.is_a?(Array)
888
+
889
+ copy = Value.deep_copy(input)
890
+ return nil if paths.any?(&:empty?)
891
+
892
+ ordered_delete_paths(paths).each { |path| Path.delete(copy, path) }
893
+ copy
894
+ end
895
+
896
+ def delete_paths(input, context, args)
897
+ copy = Value.deep_copy(input)
898
+ paths = args.flat_map { |arg| collect_paths(arg, input, context) }
899
+ return nil if paths.any?(&:empty?)
900
+
901
+ ordered_delete_paths(paths).each { |path| Path.delete(copy, path) }
902
+ copy
903
+ end
904
+
905
+ def pick(input, context, args)
906
+ paths = args.flat_map { |arg| collect_paths(arg, input, context) }
907
+ return Value.deep_copy(input) if paths.any?(&:empty?)
908
+
909
+ paths.each { |path| validate_pick_path(path) }
910
+ root = nil
911
+ paths.each do |path|
912
+ root ||= container_for_path(path)
913
+ Path.set(root, path, Value.deep_copy(Path.get(input, path)))
914
+ end
915
+ root
916
+ end
917
+
918
+ def validate_pick_path(path)
919
+ return unless path.any? { |part| part.is_a?(Integer) && part.negative? }
920
+
921
+ raise RuntimeError, 'Out of bounds negative array index'
922
+ end
923
+
924
+ def container_for_path(path)
925
+ path.first.is_a?(Numeric) ? [] : {}
926
+ end
927
+
928
+ def paths_builtin(input, context, args, leaves_only:)
929
+ paths = Path.paths(input, leaves_only: leaves_only)
930
+ return paths.reject(&:empty?) if args.empty?
931
+
932
+ filter = args.fetch(0)
933
+ Enumerator.new do |yielder|
934
+ paths.each do |path|
935
+ filter_stream(filter, Path.get(input, path), context).each do |value|
936
+ yielder << path if !path.empty? && Value.truthy?(value)
937
+ end
938
+ end
939
+ end
940
+ end
941
+
942
+ def walk(input, context, filter)
943
+ Enumerator.new do |yielder|
944
+ transformed =
945
+ case input
946
+ when Array
947
+ input.flat_map { |item| walk(item, context, filter).to_a }
948
+ when Hash
949
+ input.each_with_object({}) do |(key, value), out|
950
+ values = walk(value, context, filter).take(1)
951
+ out[key] = values.first unless values.empty?
952
+ end
953
+ else
954
+ input
955
+ end
956
+ filter_stream(filter, transformed, context).each { |value| yielder << value }
957
+ end
958
+ end
959
+
960
+ def to_stream(value)
961
+ Enumerator.new do |yielder|
962
+ stack = [[:visit, value, []]]
963
+ until stack.empty?
964
+ type, current, path = stack.pop
965
+ if type == :emit
966
+ yielder << current
967
+ next
968
+ end
969
+
970
+ children = if current.is_a?(Array)
971
+ current.each_with_index.map { |item, index| [item, path + [index]] }
972
+ elsif current.is_a?(Hash)
973
+ current.map { |key, item| [item, path + [key]] }
974
+ end
975
+ if children.nil?
976
+ yielder << [path, current]
977
+ elsif children.empty?
978
+ yielder << [path, current.class.new]
979
+ else
980
+ last_component = current.is_a?(Array) ? current.length - 1 : current.keys.last
981
+ stack << [:emit, [path + [last_component]], nil]
982
+ children.reverse_each { |child, child_path| stack << [:visit, child, child_path] }
983
+ end
984
+ end
985
+ end
986
+ end
987
+
988
+ def from_stream(stream)
989
+ Enumerator.new do |yielder|
990
+ root = nil
991
+ stream.each do |event|
992
+ event = assert_array(event)
993
+ path = assert_array(event.first)
994
+ if event.length == 1
995
+ if path.length == 1 && !root.nil?
996
+ yielder << root
997
+ root = nil
998
+ end
999
+ next
1000
+ end
1001
+
1002
+ value = event[1]
1003
+ if path.empty?
1004
+ yielder << value
1005
+ root = nil
1006
+ else
1007
+ root ||= container_for_path(path)
1008
+ Path.set(root, path, value)
1009
+ end
1010
+ end
1011
+ yielder << root unless root.nil?
1012
+ end
1013
+ end
1014
+
1015
+ def truncate_stream(input, context, args)
1016
+ depth = input
1017
+ Enumerator.new do |yielder|
1018
+ filter_stream(args.fetch(0), nil, context).each do |event|
1019
+ path = Path.read_index(event, 0)
1020
+ next unless Value.compare(length(path), depth).positive?
1021
+
1022
+ updated = event.nil? ? [] : event.dup
1023
+ updated[0] = truncate_path(path, depth)
1024
+ yielder << updated
1025
+ end
1026
+ end
1027
+ end
1028
+
1029
+ def truncate_path(path, depth)
1030
+ return nil if path.nil?
1031
+ unless path.is_a?(Array) || path.is_a?(String)
1032
+ raise TypeError, "Cannot index #{Value.type_of(path)} with object"
1033
+ end
1034
+
1035
+ start = truncate_boundary(depth, path.is_a?(String) ? path.each_char.count : path.length)
1036
+ return path.each_char.drop(start).join if path.is_a?(String)
1037
+
1038
+ path.drop(start)
1039
+ end
1040
+
1041
+ def truncate_boundary(depth, length)
1042
+ return 0 if depth.nil? || (depth.respond_to?(:nan?) && depth.nan?)
1043
+ raise TypeError, 'Array/string slice indices must be integers' unless depth.is_a?(Numeric)
1044
+ return depth.negative? ? 0 : length if depth.respond_to?(:infinite?) && depth.infinite?
1045
+
1046
+ index = depth.floor
1047
+ index += length if index.negative?
1048
+ [[index, 0].max, length].min
1049
+ end
1050
+
1051
+ def extreme(input, mode)
1052
+ array = assert_array(input)
1053
+ return nil if array.empty?
1054
+
1055
+ array.public_send(mode) { |a, b| Value.compare(a, b) }
1056
+ end
1057
+
1058
+ def extreme_by(input, context, filter, mode)
1059
+ array = assert_array(input)
1060
+ return nil if array.empty?
1061
+
1062
+ best = array.first
1063
+ best_key = filter_key(best, context, filter)
1064
+ array.drop(1).each do |item|
1065
+ key = filter_key(item, context, filter)
1066
+ comparison = Value.compare(key, best_key)
1067
+ if (mode == :min && comparison.negative?) || (mode == :max && comparison >= 0)
1068
+ best = item
1069
+ best_key = key
1070
+ end
1071
+ end
1072
+ best
1073
+ end
1074
+
1075
+ def sort_by_filter(input, context, filter)
1076
+ decorated_sort(input, context, filter).map(&:first)
1077
+ end
1078
+
1079
+ def group_by_filter(input, context, filter)
1080
+ decorated_sort(input, context, filter).each_with_object([]) do |(item, key), groups|
1081
+ if groups.empty? || !Value.equal?(groups.last.fetch(:key), key)
1082
+ groups << { key: key, values: [item] }
1083
+ else
1084
+ groups.last.fetch(:values) << item
1085
+ end
1086
+ end.map { |group| group.fetch(:values) }
1087
+ end
1088
+
1089
+ def unique_values(array)
1090
+ array = array.sort { |a, b| Value.compare(a, b) }
1091
+ array.each_with_object([]) do |item, out|
1092
+ out << item if out.empty? || !Value.equal?(out.last, item)
1093
+ end
1094
+ end
1095
+
1096
+ def unique_by_filter(input, context, filter)
1097
+ unique = decorated_sort(input, context, filter).each_with_object([]) do |(item, key), out|
1098
+ out << [item, key] if out.empty? || !Value.equal?(out.last.last, key)
1099
+ end
1100
+ unique.map(&:first)
1101
+ end
1102
+
1103
+ def decorated_sort(input, context, filter)
1104
+ assert_array(input).each_with_index.map do |item, index|
1105
+ [item, filter_key(item, context, filter), index]
1106
+ end.sort do |left, right|
1107
+ comparison = Value.compare(left[1], right[1])
1108
+ comparison.zero? ? left[2] <=> right[2] : comparison
1109
+ end
1110
+ end
1111
+
1112
+ def index_sql(input, context, args)
1113
+ source =
1114
+ if args.length == 2
1115
+ collect_filter(args[0], input, context)
1116
+ else
1117
+ assert_array(input)
1118
+ end
1119
+ filter = args.length == 2 ? args[1] : args.fetch(0)
1120
+ source.to_h do |item|
1121
+ [to_string(filter_key(item, context, filter)), item]
1122
+ end
1123
+ end
1124
+
1125
+ def join_sql(input, context, args)
1126
+ index = eval_arg(args, 0, input, context)
1127
+ filter = args.fetch(1)
1128
+ assert_array(input).map do |item|
1129
+ key = to_string(filter_key(item, context, filter))
1130
+ [item, index[key]]
1131
+ end
1132
+ end
1133
+
1134
+ def in_sql?(input, context, args)
1135
+ if args.length == 1
1136
+ values = collect_filter(args.fetch(0), input, context)
1137
+ values = values.first if values.length == 1 && values.first.is_a?(Array)
1138
+ return values.any? { |item| Value.equal?(item, input) }
1139
+ end
1140
+
1141
+ source_any?(args.fetch(0), input, context) do |item|
1142
+ source_any?(args.fetch(1), input, context) { |needle| Value.equal?(item, needle) }
1143
+ end
1144
+ end
1145
+
1146
+ def filter_key(value, context, filter)
1147
+ result = collect_filter(filter, value, context)
1148
+ result.length == 1 ? result.first : result
1149
+ end
1150
+
1151
+ def contains?(container, contained)
1152
+ tasks = [[:evaluate, container, contained]]
1153
+ results = []
1154
+ until tasks.empty?
1155
+ action, *values = tasks.pop
1156
+ case action
1157
+ when :evaluate
1158
+ candidate, needle = values
1159
+ if candidate.is_a?(Hash) && needle.is_a?(Hash)
1160
+ entries = needle.to_a
1161
+ unless entries.all? { |key, _value| candidate.key?(key) }
1162
+ results << false
1163
+ next
1164
+ end
1165
+
1166
+ tasks << [:hash_all, candidate, entries, 0]
1167
+ elsif candidate.is_a?(Array) && needle.is_a?(Array)
1168
+ tasks << [:array_all, candidate, needle, 0]
1169
+ elsif candidate.is_a?(String) && needle.is_a?(String)
1170
+ results << candidate.include?(needle)
1171
+ else
1172
+ results << Value.equal?(candidate, needle)
1173
+ end
1174
+ when :hash_all
1175
+ candidate, entries, index = values
1176
+ if index >= entries.length
1177
+ results << true
1178
+ else
1179
+ key, needle = entries[index]
1180
+ tasks << [:hash_after, candidate, entries, index]
1181
+ tasks << [:evaluate, candidate.fetch(key), needle]
1182
+ end
1183
+ when :hash_after
1184
+ candidate, entries, index = values
1185
+ if results.pop
1186
+ tasks << [:hash_all, candidate, entries, index + 1]
1187
+ else
1188
+ results << false
1189
+ end
1190
+ when :array_all
1191
+ candidate, needles, needle_index = values
1192
+ if needle_index >= needles.length
1193
+ results << true
1194
+ elsif candidate.empty?
1195
+ results << false
1196
+ else
1197
+ tasks << [:array_all_after, candidate, needles, needle_index]
1198
+ tasks << [:array_any, candidate, needles.fetch(needle_index), 0]
1199
+ end
1200
+ when :array_all_after
1201
+ candidate, needles, needle_index = values
1202
+ if results.pop
1203
+ tasks << [:array_all, candidate, needles, needle_index + 1]
1204
+ else
1205
+ results << false
1206
+ end
1207
+ when :array_any
1208
+ candidate, needle, candidate_index = values
1209
+ if candidate_index >= candidate.length
1210
+ results << false
1211
+ else
1212
+ tasks << [:array_any_after, candidate, needle, candidate_index]
1213
+ tasks << [:evaluate, candidate.fetch(candidate_index), needle]
1214
+ end
1215
+ when :array_any_after
1216
+ candidate, needle, candidate_index = values
1217
+ if results.pop
1218
+ results << true
1219
+ else
1220
+ tasks << [:array_any, candidate, needle, candidate_index + 1]
1221
+ end
1222
+ end
1223
+ end
1224
+ results.fetch(0)
1225
+ end
1226
+
1227
+ def index_of(input, needle)
1228
+ if input.is_a?(String)
1229
+ validate_string_search_needle(input, needle)
1230
+ return nil if needle.empty?
1231
+
1232
+ return input.index(needle)
1233
+ end
1234
+
1235
+ return unsupported_search_result(input, needle) unless input.is_a?(Array)
1236
+
1237
+ needle = [needle] unless needle.is_a?(Array)
1238
+ max = input.length - assert_array(needle).length
1239
+ return nil if needle.empty?
1240
+
1241
+ (0..max).find { |index| array_slice_equal?(input, needle, index) }
1242
+ end
1243
+
1244
+ def rindex_of(input, needle)
1245
+ if input.is_a?(String)
1246
+ validate_string_search_needle(input, needle)
1247
+ return nil if needle.empty?
1248
+
1249
+ return input.rindex(needle)
1250
+ end
1251
+
1252
+ return unsupported_search_result(input, needle) unless input.is_a?(Array)
1253
+
1254
+ needle = [needle] unless needle.is_a?(Array)
1255
+ max = input.length - assert_array(needle).length
1256
+ return nil if needle.empty?
1257
+
1258
+ max.downto(0).find { |index| array_slice_equal?(input, needle, index) }
1259
+ end
1260
+
1261
+ def indices_of(input, needle)
1262
+ if input.is_a?(String)
1263
+ positions = []
1264
+ offset = 0
1265
+ validate_string_search_needle(input, needle)
1266
+ return [] if needle.empty?
1267
+
1268
+ while (found = input.index(needle, offset))
1269
+ positions << found
1270
+ offset = found + 1
1271
+ end
1272
+ return positions
1273
+ end
1274
+ return unsupported_search_result(input, needle) unless input.is_a?(Array)
1275
+
1276
+ needle = [needle] unless needle.is_a?(Array)
1277
+ return [] if needle.empty?
1278
+
1279
+ max = input.length - needle.length
1280
+ (0..max).select { |index| array_slice_equal?(input, needle, index) }
1281
+ end
1282
+
1283
+ def validate_string_search_needle(input, needle)
1284
+ return if needle.is_a?(String)
1285
+ raise TypeError, 'Array/string slice indices must be integers' if needle.is_a?(Hash)
1286
+
1287
+ Path.read_index(input, needle)
1288
+ end
1289
+
1290
+ def unsupported_search_result(input, needle)
1291
+ if input.is_a?(Hash)
1292
+ Path.read_index(input, needle) unless needle.is_a?(String)
1293
+ return nil
1294
+ end
1295
+ if input.nil?
1296
+ return nil if needle.is_a?(String) || needle.is_a?(Numeric) || needle.is_a?(Hash)
1297
+
1298
+ Path.read_index(input, needle)
1299
+ end
1300
+
1301
+ Path.read_index(input, needle)
1302
+ nil
1303
+ end
1304
+
1305
+ def array_slice_equal?(input, needle, index)
1306
+ needle.each_with_index.all? { |item, offset| Value.equal?(input[index + offset], item) }
1307
+ end
1308
+
1309
+ def combinations(input, context, args)
1310
+ if args.length == 1
1311
+ raw_count = eval_arg(args, 0, input, context)
1312
+ raise RuntimeError, 'Range bounds must be numeric' unless raw_count.is_a?(Numeric)
1313
+
1314
+ count = raw_count.ceil
1315
+ return [[]] if count.negative?
1316
+
1317
+ arrays = Array.new(count) { combination_array(input) }
1318
+ else
1319
+ source = args.empty? ? assert_array(input) : eval_arg(args, 0, input, context)
1320
+ arrays = assert_array(source)
1321
+ end
1322
+ arrays.reduce([[]]) do |acc, array|
1323
+ assert_array(array)
1324
+ acc.flat_map { |prefix| array.map { |item| prefix + [item] } }
1325
+ end
1326
+ end
1327
+
1328
+ def transpose(input)
1329
+ rows = assert_array(input).map { |row| assert_array(row) }
1330
+ max = rows.map(&:length).max || 0
1331
+ (0...max).map { |index| rows.map { |row| row[index] } }
1332
+ end
1333
+
1334
+ def combination_array(value)
1335
+ return value if value.is_a?(Array)
1336
+
1337
+ raise TypeError,
1338
+ "Cannot iterate over #{Value.type_of(value)} (#{JSON::Dumper.dump(value, indent: nil)})"
1339
+ end
1340
+
1341
+ def bsearch(input, context, args)
1342
+ array = assert_array(input)
1343
+ args.flat_map do |arg|
1344
+ arg.eval(input, context).map do |needle|
1345
+ found = array.bsearch_index { |item| Value.compare(item, needle) >= 0 }
1346
+ found && Value.equal?(array[found], needle) ? found : -((found || array.length) + 1)
1347
+ end
1348
+ end
1349
+ end
1350
+
1351
+ def first_builtin(input, context, args)
1352
+ values = args.empty? ? assert_array(input) : args.fetch(0).take(input, context, 1)
1353
+ values.empty? ? [] : [values.first]
1354
+ end
1355
+
1356
+ def last_builtin(input, context, args)
1357
+ values = args.empty? ? assert_array(input) : collect_filter(args.fetch(0), input, context)
1358
+ values.empty? ? [] : [values.last]
1359
+ end
1360
+
1361
+ def nth(input, context, args)
1362
+ Enumerator.new do |yielder|
1363
+ filter_stream(args.fetch(0), input, context).each do |raw_index|
1364
+ if args.length == 1
1365
+ yielder << nth_index_value(input, raw_index)
1366
+ next
1367
+ end
1368
+
1369
+ index = nth_filter_index(raw_index)
1370
+ if index.respond_to?(:infinite?) && index.infinite?
1371
+ filter_stream(args[1], input, context).each { |_value| nil }
1372
+ next
1373
+ end
1374
+
1375
+ values = args[1].take(input, context, index + 1)
1376
+ yielder << values[index] if index < values.length
1377
+ end
1378
+ end
1379
+ end
1380
+
1381
+ def nth_index_value(input, raw_index)
1382
+ index = if raw_index.is_a?(Numeric) && (!raw_index.respond_to?(:finite?) || raw_index.finite?)
1383
+ raw_index.to_i
1384
+ else
1385
+ raw_index
1386
+ end
1387
+ Path.read_index(input, index)
1388
+ end
1389
+
1390
+ def nth_filter_index(value)
1391
+ if value.is_a?(Numeric)
1392
+ raise RuntimeError, "nth doesn't support negative indices" if value.respond_to?(:nan?) && value.nan?
1393
+ raise RuntimeError, "nth doesn't support negative indices" if value < 0
1394
+ return value if value.respond_to?(:infinite?) && value.infinite?
1395
+
1396
+ return value.ceil
1397
+ end
1398
+ if value.nil? || value == true || value == false
1399
+ raise RuntimeError, "nth doesn't support negative indices"
1400
+ end
1401
+
1402
+ raise TypeError,
1403
+ "#{Value.type_of(value)} (#{short_dump(value)}) and number (1) cannot be added"
1404
+ end
1405
+
1406
+ def limit(input, context, args)
1407
+ Enumerator.new do |yielder|
1408
+ filter_stream(args.fetch(0), input, context).each do |raw_count|
1409
+ count = numeric(raw_count).ceil
1410
+ next if count <= 0
1411
+
1412
+ emitted = 0
1413
+ filter_stream(args.fetch(1), input, context).each do |value|
1414
+ yielder << value
1415
+ emitted += 1
1416
+ break if emitted >= count
1417
+ end
1418
+ end
1419
+ end
1420
+ end
1421
+
1422
+ def until_filter(input, context, args)
1423
+ condition, update = args
1424
+ Enumerator.new do |yielder|
1425
+ tasks = [[:visit, input]]
1426
+ until tasks.empty?
1427
+ type, value, state = tasks.pop
1428
+ if type == :emit
1429
+ yielder << value
1430
+ next
1431
+ end
1432
+ if type == :visit
1433
+ tasks << [:condition, value, filter_stream(condition, value, context)]
1434
+ next
1435
+ end
1436
+
1437
+ if type == :condition
1438
+ begin
1439
+ result = state.next
1440
+ tasks << [:condition, value, state]
1441
+ if Value.truthy?(result)
1442
+ tasks << [:emit, value]
1443
+ else
1444
+ tasks << [:updates, value, filter_stream(update, value, context)]
1445
+ end
1446
+ rescue StopIteration
1447
+ nil
1448
+ end
1449
+ elsif type == :updates
1450
+ begin
1451
+ next_value = state.next
1452
+ tasks << [:updates, value, state]
1453
+ tasks << [:visit, next_value]
1454
+ rescue StopIteration
1455
+ nil
1456
+ end
1457
+ end
1458
+ end
1459
+ end
1460
+ end
1461
+
1462
+ def while_filter(input, context, args)
1463
+ condition, update = args
1464
+ Enumerator.new do |yielder|
1465
+ tasks = [[:visit, input]]
1466
+ until tasks.empty?
1467
+ type, value, state = tasks.pop
1468
+ if type == :emit
1469
+ yielder << value
1470
+ next
1471
+ end
1472
+ if type == :visit
1473
+ tasks << [:condition, value, filter_stream(condition, value, context)]
1474
+ next
1475
+ end
1476
+
1477
+ if type == :condition
1478
+ begin
1479
+ result = state.next
1480
+ tasks << [:condition, value, state]
1481
+ if Value.truthy?(result)
1482
+ tasks << [:updates, value, filter_stream(update, value, context)]
1483
+ tasks << [:emit, value]
1484
+ end
1485
+ rescue StopIteration
1486
+ nil
1487
+ end
1488
+ elsif type == :updates
1489
+ begin
1490
+ next_value = state.next
1491
+ tasks << [:updates, value, state]
1492
+ tasks << [:visit, next_value]
1493
+ rescue StopIteration
1494
+ nil
1495
+ end
1496
+ end
1497
+ end
1498
+ end
1499
+ end
1500
+
1501
+ def repeat_filter(input, context, args)
1502
+ Enumerator.new do |yielder|
1503
+ loop do
1504
+ filter_stream(args.fetch(0), input, context).each { |value| yielder << value }
1505
+ end
1506
+ end
1507
+ end
1508
+
1509
+ def time_array(time)
1510
+ [time.year, time.month - 1, time.day, time.hour, time.min, time.sec, time.wday, time.yday - 1]
1511
+ end
1512
+
1513
+ def mktime(input)
1514
+ values = assert_array(input)
1515
+ raise TypeError, 'mktime requires parsed datetime inputs' unless values.first(6).all?(Numeric)
1516
+
1517
+ Time.utc(values[0], values[1] + 1, values[2], values[3], values[4], values[5]).to_f
1518
+ rescue ArgumentError
1519
+ raise TypeError, 'mktime requires parsed datetime inputs'
1520
+ end
1521
+
1522
+ def strftime_builtin(input, context, args, local: false)
1523
+ format = assert_string(eval_arg(args, 0, input, context))
1524
+ time =
1525
+ if input.is_a?(Array)
1526
+ values = input
1527
+ unless values.first(6).all?(Numeric)
1528
+ raise TypeError,
1529
+ "#{local ? 'strflocaltime' : 'strftime'}/1 requires parsed datetime inputs"
1530
+ end
1531
+
1532
+ if local
1533
+ Time.local(values[0], values[1] + 1, values[2], values[3], values[4],
1534
+ values[5])
1535
+ else
1536
+ Time.utc(values[0], values[1] + 1, values[2], values[3], values[4], values[5])
1537
+ end
1538
+ else
1539
+ raise TypeError, 'strflocaltime/1 requires parsed datetime inputs' if local
1540
+
1541
+ Time.at(numeric(input)).utc
1542
+ end
1543
+ time.strftime(format)
1544
+ rescue ArgumentError
1545
+ raise TypeError, "#{local ? 'strflocaltime' : 'strftime'}/1 requires parsed datetime inputs"
1546
+ end
1547
+
1548
+ def strptime(input, context, args)
1549
+ parsed = DateTime.strptime(assert_string(input), assert_string(eval_arg(args, 0, input, context)))
1550
+ [parsed.year, parsed.month - 1, parsed.day, parsed.hour, parsed.min, parsed.sec, parsed.wday, parsed.yday - 1]
1551
+ rescue Date::Error
1552
+ raise RuntimeError, 'date does not match format'
1553
+ end
1554
+
1555
+ def regexp(input, context, args)
1556
+ pattern, flags = regexp_parts(input, context, args)
1557
+ unknown_flags = flags.each_char.uniq - %w[g i m n p s l x]
1558
+ raise RuntimeError, "unsupported regular expression flag: #{unknown_flags.first}" unless unknown_flags.empty?
1559
+
1560
+ dot_matches_newline = flags.include?('m') || flags.include?('p')
1561
+ pattern = jq_regexp_pattern(pattern, dot_matches_newline: dot_matches_newline)
1562
+ options = 0
1563
+ options |= Regexp::IGNORECASE if flags.include?('i')
1564
+ options |= Regexp::MULTILINE if flags.include?('m') || flags.include?('p')
1565
+ options |= Regexp::EXTENDED if flags.include?('x')
1566
+ timeout = context.options[:regexp_timeout]
1567
+ regex = if timeout.nil?
1568
+ Regexp.new(pattern, options)
1569
+ elsif Regexp.respond_to?(:timeout)
1570
+ Regexp.new(pattern, options, timeout: timeout)
1571
+ else
1572
+ raise RuntimeError, 'regular expression timeout is not supported by this Ruby'
1573
+ end
1574
+ [regex, flags]
1575
+ rescue RegexpError, ArgumentError => e
1576
+ raise RuntimeError, e.message.to_s
1577
+ end
1578
+
1579
+ def jq_regexp_pattern(pattern, dot_matches_newline:)
1580
+ chars = pattern.each_char.to_a
1581
+ transformed, = transform_regexp_segment(chars, 0, line_anchors: false,
1582
+ dot_matches_newline: dot_matches_newline)
1583
+ transformed
1584
+ end
1585
+
1586
+ def transform_regexp_segment(chars, index, line_anchors:, dot_matches_newline:, stop_at_group_end: false)
1587
+ output = +''
1588
+ while index < chars.length
1589
+ char = chars[index]
1590
+ if char == '\\'
1591
+ output << char
1592
+ index += 1
1593
+ output << chars[index] if index < chars.length
1594
+ elsif char == '['
1595
+ character_class, index = consume_regexp_character_class(chars, index)
1596
+ output << character_class
1597
+ next
1598
+ elsif char == '(' && (inline = scoped_regexp_options(chars, index))
1599
+ enabled, disabled, body_index = inline
1600
+ child_line_anchors = option_state(line_anchors, enabled, disabled, 'm')
1601
+ child_dot_matches_newline = option_state(dot_matches_newline, enabled, disabled, 's')
1602
+ body, next_index, closed = transform_regexp_segment(
1603
+ chars, body_index, line_anchors: child_line_anchors,
1604
+ dot_matches_newline: child_dot_matches_newline, stop_at_group_end: true
1605
+ )
1606
+ output << if closed
1607
+ ruby_regexp_group(enabled, disabled, dot_matches_newline, child_dot_matches_newline, body)
1608
+ else
1609
+ chars[index...body_index].join + body
1610
+ end
1611
+ index = next_index
1612
+ next
1613
+ elsif char == '('
1614
+ body, next_index, closed = transform_regexp_segment(
1615
+ chars, index + 1, line_anchors: line_anchors,
1616
+ dot_matches_newline: dot_matches_newline, stop_at_group_end: true
1617
+ )
1618
+ output << "(#{body}"
1619
+ output << ')' if closed
1620
+ index = next_index
1621
+ next
1622
+ elsif char == ')' && stop_at_group_end
1623
+ return [output, index + 1, true]
1624
+ elsif char == '^'
1625
+ output << (line_anchors ? '^' : '\\A')
1626
+ elsif char == '$'
1627
+ output << (line_anchors ? '$' : '\\Z')
1628
+ else
1629
+ output << char
1630
+ end
1631
+ index += 1
1632
+ end
1633
+ [output, index, !stop_at_group_end]
1634
+ end
1635
+
1636
+ def consume_regexp_character_class(chars, index)
1637
+ output = +'['
1638
+ index += 1
1639
+ if chars[index] == '^'
1640
+ output << '^'
1641
+ index += 1
1642
+ end
1643
+ if chars[index] == ']'
1644
+ output << '\\]'
1645
+ index += 1
1646
+ end
1647
+ while index < chars.length
1648
+ char = chars[index]
1649
+ if char == '\\'
1650
+ output << char
1651
+ index += 1
1652
+ output << chars[index] if index < chars.length
1653
+ elsif char == '[' && %w[: . =].include?(chars[index + 1])
1654
+ marker = chars[index + 1]
1655
+ closing = "#{marker}]"
1656
+ while index < chars.length
1657
+ output << chars[index]
1658
+ index += 1
1659
+ next unless output.end_with?(closing)
1660
+
1661
+ break
1662
+ end
1663
+ next
1664
+ elsif char == ']'
1665
+ output << char
1666
+ return [output, index + 1]
1667
+ else
1668
+ output << char
1669
+ end
1670
+ index += 1
1671
+ end
1672
+ [output, index]
1673
+ end
1674
+
1675
+ def scoped_regexp_options(chars, index)
1676
+ return unless chars[index, 2] == ['(', '?']
1677
+
1678
+ cursor = index + 2
1679
+ enabled = +''
1680
+ while cursor < chars.length && %w[i m s x].include?(chars[cursor])
1681
+ enabled << chars[cursor]
1682
+ cursor += 1
1683
+ end
1684
+ disabled = +''
1685
+ if chars[cursor] == '-'
1686
+ cursor += 1
1687
+ while cursor < chars.length && %w[i m s x].include?(chars[cursor])
1688
+ disabled << chars[cursor]
1689
+ cursor += 1
1690
+ end
1691
+ end
1692
+ return unless chars[cursor] == ':' && !(enabled.empty? && disabled.empty?)
1693
+
1694
+ [enabled, disabled, cursor + 1]
1695
+ end
1696
+
1697
+ def option_state(current, enabled, disabled, option)
1698
+ return true if enabled.include?(option)
1699
+ return false if disabled.include?(option)
1700
+
1701
+ current
1702
+ end
1703
+
1704
+ def ruby_regexp_group(enabled, disabled, parent_dotall, child_dotall, body)
1705
+ ruby_enabled = enabled.each_char.select { |option| %w[i x].include?(option) }
1706
+ ruby_disabled = disabled.each_char.select { |option| %w[i x].include?(option) }
1707
+ ruby_enabled << 'm' if child_dotall && !parent_dotall
1708
+ ruby_disabled << 'm' if parent_dotall && !child_dotall
1709
+ options = ruby_enabled.join
1710
+ options += "-#{ruby_disabled.join}" unless ruby_disabled.empty?
1711
+ options.empty? ? "(?:#{body})" : "(?#{options}:#{body})"
1712
+ end
1713
+
1714
+ def format_builtin(input, context, args)
1715
+ format_name = assert_string(eval_arg(args, 0, input, context))
1716
+ supported = %w[text json html uri csv tsv sh base64 base64d]
1717
+ raise RuntimeError, "format #{format_name.inspect} is not supported" unless supported.include?(format_name)
1718
+
1719
+ dispatch("@#{format_name}", input, context, [])
1720
+ end
1721
+
1722
+ def search_list(context)
1723
+ configured = context.options.fetch(:library_path, [])
1724
+ return configured.map { |path| File.expand_path(path) } unless configured.empty?
1725
+
1726
+ environment = ENV.fetch('JQ_LIBRARY_PATH', '').split(File::PATH_SEPARATOR).reject(&:empty?)
1727
+ (environment + [File.expand_path('~/.jq'), File.expand_path('~/.rjq')]).uniq
1728
+ end
1729
+
1730
+ def regexp_parts(input, context, args)
1731
+ raw = eval_arg(args, 0, input, context)
1732
+ if raw.is_a?(Array)
1733
+ [assert_string(raw[0]), raw[1] ? assert_string(raw[1]) : '']
1734
+ else
1735
+ [assert_string(raw), args.length > 1 ? assert_string(eval_arg(args, 1, input, context)) : '']
1736
+ end
1737
+ end
1738
+
1739
+ def regexp_matches(input, context, args, string)
1740
+ return matches_for_regexp(input, context, args, string) unless args.length > 1
1741
+
1742
+ matches = []
1743
+ filter_stream(args.fetch(1), input, context).each do |flags|
1744
+ flag_args = [args.fetch(0), AST::Literal.new(flags)]
1745
+ matches.concat(matches_for_regexp(input, context, flag_args, string))
1746
+ end
1747
+ matches
1748
+ end
1749
+
1750
+ def matches_for_regexp(input, context, args, string)
1751
+ regex, flags = regexp(input, context, args)
1752
+ matches = string.to_enum(:scan, regex).map { Regexp.last_match }
1753
+ matches.reject { |match| flags.include?('n') && match[0].empty? }
1754
+ end
1755
+
1756
+ def match_builtin(input, context, args)
1757
+ string = assert_string(input)
1758
+ regex, flags = regexp(input, context, args)
1759
+ global = flags.include?('g')
1760
+ matches = global ? string.to_enum(:scan, regex).map { Regexp.last_match } : [regex.match(string)].compact
1761
+ matches = matches.reject { |match| match[0].empty? } if flags.include?('n')
1762
+ matches.map { |match| match_object(match) }
1763
+ end
1764
+
1765
+ def match_object(match)
1766
+ {
1767
+ 'offset' => match.begin(0),
1768
+ 'length' => match[0].length,
1769
+ 'string' => match[0],
1770
+ 'captures' => (1...match.length).map do |index|
1771
+ value = match[index]
1772
+ if value.nil? && match[0].empty?
1773
+ value = ''
1774
+ offset = match.begin(0)
1775
+ else
1776
+ offset = value ? match.begin(index) : -1
1777
+ end
1778
+ { 'offset' => offset, 'length' => value ? value.length : 0, 'string' => value,
1779
+ 'name' => capture_name(match, index) }
1780
+ end
1781
+ }
1782
+ end
1783
+
1784
+ def capture_name(match, index)
1785
+ match.names.find { |name| match.regexp.named_captures.fetch(name).include?(index) }
1786
+ end
1787
+
1788
+ def capture_builtin(input, context, args)
1789
+ regex, = regexp(input, context, args)
1790
+ match = regex.match(assert_string(input))
1791
+ return [] unless match
1792
+
1793
+ [match.names.to_h { |name| [name, match[name]] }]
1794
+ end
1795
+
1796
+ def scan_builtin(input, context, args)
1797
+ regex, = regexp(input, context, args)
1798
+ assert_string(input).scan(regex).map { |item| item.is_a?(Array) && item.length == 1 ? item.first : item }
1799
+ end
1800
+
1801
+ def substitute(input, context, args, global:)
1802
+ string = assert_string(input)
1803
+ regex, = regexp(input, context, [args.fetch(0)] + args[2..].to_a)
1804
+ replacement_filters(args.fetch(1)).flat_map do |replacement_filter|
1805
+ if global
1806
+ gsub_with_filter(string, regex, replacement_filter,
1807
+ context)
1808
+ else
1809
+ sub_with_filter(string, regex, replacement_filter, context)
1810
+ end
1811
+ end
1812
+ end
1813
+
1814
+ def replacement_filters(node)
1815
+ return node.replacement_filters if node.respond_to?(:replacement_filters)
1816
+ return replacement_filters(node.left) + replacement_filters(node.right) if node.is_a?(AST::Comma)
1817
+
1818
+ [node]
1819
+ end
1820
+
1821
+ def sub_with_filter(string, regex, replacement_filter, context)
1822
+ match = regex.match(string)
1823
+ return [string] unless match
1824
+
1825
+ replacements_for(replacement_filter, match, context).map do |replacement|
1826
+ string[0...match.begin(0)] + replacement + string[match.end(0)..].to_s
1827
+ end
1828
+ end
1829
+
1830
+ def gsub_with_filter(string, regex, replacement_filter, context)
1831
+ matches = string.to_enum(:scan, regex).map { Regexp.last_match }
1832
+ return [string] if matches.empty?
1833
+
1834
+ first_replacements = replacements_for(replacement_filter, matches.first, context)
1835
+ first_replacements.each_index.filter_map do |branch|
1836
+ out = +''
1837
+ offset = 0
1838
+ complete = matches.each_with_index.all? do |match, index|
1839
+ replacements = index.zero? ? first_replacements : replacements_for(replacement_filter, match, context)
1840
+ replacement = replacements[branch]
1841
+ next false unless replacement
1842
+
1843
+ out << string[offset...match.begin(0)].to_s
1844
+ out << replacement
1845
+ offset = match.end(0)
1846
+ true
1847
+ end
1848
+ next unless complete
1849
+
1850
+ out << string[offset..].to_s
1851
+ end
1852
+ end
1853
+
1854
+ def replacements_for(replacement_filter, match, context)
1855
+ replacement_filter.eval(capture_values(match), context).map { |value| assert_string(value) }
1856
+ end
1857
+
1858
+ def capture_values(match)
1859
+ match.names.to_h { |name| [name, match[name]] }
1860
+ end
1861
+
1862
+ def splits_builtin(input, context, args)
1863
+ string = assert_string(input)
1864
+ split_at_matches(string, regexp_matches(input, context, args, string))
1865
+ end
1866
+
1867
+ def format_csv(input)
1868
+ assert_array(input).map { |item| csv_field(item) }.join(',')
1869
+ end
1870
+
1871
+ def html_escape(input)
1872
+ CGI.escapeHTML(input).gsub('&#39;', '&apos;')
1873
+ end
1874
+
1875
+ def csv_field(item)
1876
+ return '' if item.nil?
1877
+ return to_string(item) if item.is_a?(Numeric) || item == true || item == false
1878
+ unless item.is_a?(String)
1879
+ raise TypeError, "#{Value.type_of(item)} (#{short_dump(item)}) is not valid in a csv row"
1880
+ end
1881
+
1882
+ "\"#{item.gsub('"', '""')}\""
1883
+ end
1884
+
1885
+ def format_tsv(input)
1886
+ assert_array(input).map do |item|
1887
+ next '' if item.nil?
1888
+ next to_string(item) if item.is_a?(Numeric) || item == true || item == false
1889
+ unless item.is_a?(String)
1890
+ raise TypeError, "#{Value.type_of(item)} (#{short_dump(item)}) is not valid in a tsv row"
1891
+ end
1892
+
1893
+ item.gsub("\t", '\\t').gsub("\n", '\\n').gsub("\r", '\\r')
1894
+ end.join("\t")
1895
+ end
1896
+
1897
+ def format_sh(input)
1898
+ values = input.is_a?(Array) ? input : [input]
1899
+ values.map do |item|
1900
+ case item
1901
+ when String then sh_quote(item)
1902
+ when Numeric, TrueClass, FalseClass, NilClass then to_string(item)
1903
+ else
1904
+ raise TypeError, "#{Value.type_of(item)} (#{short_dump(item)}) can not be escaped for shell"
1905
+ end
1906
+ end.join(' ')
1907
+ end
1908
+
1909
+ def sh_quote(input)
1910
+ "'#{input.gsub("'", "'\\\\''")}'"
1911
+ end
1912
+
1913
+ def uri_escape(input)
1914
+ input.bytes.map do |byte|
1915
+ char = byte.chr
1916
+ char.match?(/[A-Za-z0-9_.~-]/) ? char : '%%%02X' % byte
1917
+ end.join
1918
+ end
1919
+
1920
+ def base32_encode(input)
1921
+ alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
1922
+ encoded = +''
1923
+ buffer = 0
1924
+ bits = 0
1925
+ input.each_byte do |byte|
1926
+ buffer = (buffer << 8) | byte
1927
+ bits += 8
1928
+ while bits >= 5
1929
+ bits -= 5
1930
+ encoded << alphabet[(buffer >> bits) & 31]
1931
+ end
1932
+ buffer &= (1 << bits) - 1
1933
+ end
1934
+ encoded << alphabet[(buffer << (5 - bits)) & 31] if bits.positive?
1935
+ encoded + ('=' * ((8 - (encoded.length % 8)) % 8))
1936
+ end
1937
+
1938
+ def decode_base64(input)
1939
+ string = assert_string(input)
1940
+ string.unpack1('m0').force_encoding(Encoding::UTF_8)
1941
+ rescue ArgumentError
1942
+ raise RuntimeError, "string (#{JSON::Dumper.dump(input, indent: nil)}) is not valid base64 data"
1943
+ end
1944
+
1945
+ def base32_decode(input)
1946
+ alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
1947
+ clean = input.upcase.delete('=')
1948
+ output = +''.b
1949
+ buffer = 0
1950
+ bits = 0
1951
+ clean.each_char do |char|
1952
+ index = alphabet.index(char)
1953
+ raise RuntimeError, "invalid base32 character #{char.inspect}" unless index
1954
+
1955
+ buffer = (buffer << 5) | index
1956
+ bits += 5
1957
+ if bits >= 8
1958
+ bits -= 8
1959
+ output << ((buffer >> bits) & 0xFF)
1960
+ buffer &= (1 << bits) - 1
1961
+ end
1962
+ end
1963
+ output
1964
+ end
1965
+
1966
+ def input_values(input, context, filter)
1967
+ Enumerator.new do |yielder|
1968
+ iterable_values(input).each do |item|
1969
+ filter_stream(filter, item, context).each { |value| yielder << value }
1970
+ end
1971
+ end
1972
+ end
1973
+
1974
+ def iterable_values(input)
1975
+ return input.each if input.is_a?(Array)
1976
+ return input.each_value if input.is_a?(Hash)
1977
+
1978
+ raise TypeError,
1979
+ "Cannot iterate over #{Value.type_of(input)} (#{JSON::Dumper.dump(input, indent: nil)})"
1980
+ end
1981
+
1982
+ def source_any?(node, input, context, &)
1983
+ return node.source_any?(input, context, &) if node.respond_to?(:source_any?)
1984
+
1985
+ if node.is_a?(AST::Comma)
1986
+ return true if source_any?(node.left, input, context, &)
1987
+
1988
+ return source_any?(node.right, input, context, &)
1989
+ end
1990
+ node.eval(input, context).any?(&)
1991
+ end
1992
+
1993
+ def source_all?(node, input, context, &)
1994
+ return node.source_all?(input, context, &) if node.respond_to?(:source_all?)
1995
+
1996
+ if node.is_a?(AST::Comma)
1997
+ return false unless source_all?(node.left, input, context, &)
1998
+
1999
+ return source_all?(node.right, input, context, &)
2000
+ end
2001
+ node.eval(input, context).all?(&)
2002
+ end
2003
+
2004
+ def eval_arg(args, index, input, context)
2005
+ raise RuntimeError, "missing argument #{index}" unless args[index]
2006
+
2007
+ args[index].eval(input, context).first
2008
+ end
2009
+
2010
+ def filter_stream(filter, input, context)
2011
+ return filter.stream(input, context) if filter.respond_to?(:stream)
2012
+
2013
+ filter.eval(input, context).each
2014
+ end
2015
+
2016
+ def collect_paths(filter, input, context)
2017
+ filter.paths(input, context)
2018
+ rescue Rjq::RuntimeError => e
2019
+ e.take_outputs
2020
+ raise
2021
+ end
2022
+
2023
+ def each_resolved_argument_set(name, args, input, context, &block)
2024
+ indices = (0...args.length).to_a
2025
+ indices.reverse! unless LEFT_OUTER_ARGUMENT_BUILTINS.include?(name)
2026
+ resolve_argument_indices(name, args, indices, input, context, [], &block)
2027
+ end
2028
+
2029
+ def resolve_argument_indices(name, args, indices, input, context, resolved, &block)
2030
+ return yield(resolved) if indices.empty?
2031
+
2032
+ index = indices.first
2033
+ remaining = indices.drop(1)
2034
+ argument = args.fetch(index)
2035
+ if FILTER_ARGUMENT_POSITIONS.fetch(name, []).include?(index)
2036
+ copy = resolved.dup
2037
+ copy[index] = argument
2038
+ return resolve_argument_indices(name, args, remaining, input, context, copy, &block)
2039
+ end
2040
+
2041
+ filter_stream(argument, input, context).each do |value|
2042
+ copy = resolved.dup
2043
+ copy[index] = AST::Literal.new(value)
2044
+ resolve_argument_indices(name, args, remaining, input, context, copy, &block)
2045
+ end
2046
+ end
2047
+
2048
+ def collect_filter(filter, input, context)
2049
+ filter_stream(filter, input, context).to_a
2050
+ rescue Rjq::RuntimeError => e
2051
+ e.take_outputs
2052
+ raise
2053
+ end
2054
+
2055
+ def ordered_delete_paths(paths)
2056
+ paths.sort do |left, right|
2057
+ parent_cmp = Value.compare(left[0...-1], right[0...-1])
2058
+ next parent_cmp unless parent_cmp.zero?
2059
+
2060
+ left_key = left.last
2061
+ right_key = right.last
2062
+ if left_key.is_a?(Integer) && right_key.is_a?(Integer)
2063
+ right_key <=> left_key
2064
+ else
2065
+ Value.compare(right, left)
2066
+ end
2067
+ end
2068
+ end
2069
+
2070
+ def builtin_arities(name)
2071
+ BUILTIN_ARITIES.fetch(name, []).map { |arity| "#{name}/#{arity}" }
2072
+ end
2073
+
2074
+ def valid_arity?(name, arity)
2075
+ BUILTIN_ARITIES.fetch(name, []).include?(arity)
2076
+ end
2077
+
2078
+ def modulemeta(input, context)
2079
+ metadata = context.options.fetch(:module_metadata, {})
2080
+ raise RuntimeError, "module not found: #{input}" unless metadata.key?(input)
2081
+
2082
+ metadata.fetch(input)
2083
+ end
2084
+
2085
+ def assert_array(value)
2086
+ raise TypeError, "expected array, got #{Value.type_of(value)}" unless value.is_a?(Array)
2087
+
2088
+ value
2089
+ end
2090
+
2091
+ def assert_string(value)
2092
+ raise TypeError, "expected string, got #{Value.type_of(value)}" unless value.is_a?(String)
2093
+
2094
+ value
2095
+ end
2096
+
2097
+ def numeric(value)
2098
+ raise TypeError, "expected number, got #{Value.type_of(value)}" unless value.is_a?(Numeric)
2099
+
2100
+ value
2101
+ end
2102
+ end
2103
+ end