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