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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
class SemanticAnalyzer
|
|
5
|
+
PUSH_OPS = %i[
|
|
6
|
+
load_input load_const string_interp format variable path optional binding array object branch try reduce foreach
|
|
7
|
+
label unary binary assign call tail_call recurse scoped_def
|
|
8
|
+
].freeze
|
|
9
|
+
TRANSFORM_OPS = %i[
|
|
10
|
+
field index_const index_filter slice_const slice_filter each pipe append
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(program)
|
|
14
|
+
@program = program
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate!
|
|
18
|
+
global_functions = @program.definitions.map { |definition| signature(definition) }.to_h { |item| [item, true] }
|
|
19
|
+
@program.definitions.each { |definition| validate_definition(definition, global_functions, {}) }
|
|
20
|
+
validate_instructions(@program.instructions, global_functions, {}, {})
|
|
21
|
+
@program
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def validate_definition(definition, functions, labels)
|
|
27
|
+
local_functions = functions.merge(signature(definition) => true)
|
|
28
|
+
filter_parameters = definition.params.reject { |param| param.start_with?('$') }.to_h { |name| [name, true] }
|
|
29
|
+
validate_instructions(definition.body.instructions, local_functions, filter_parameters, labels)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_instructions(instructions, functions, filter_parameters, labels)
|
|
33
|
+
validate_stack(instructions)
|
|
34
|
+
instructions.each do |instruction|
|
|
35
|
+
validate_constant_object_keys(instruction) if instruction.op == :object
|
|
36
|
+
if %i[call tail_call].include?(instruction.op)
|
|
37
|
+
validate_call(instruction, functions, filter_parameters)
|
|
38
|
+
elsif instruction.op == :scoped_def
|
|
39
|
+
validate_scoped_definition(instruction, functions, filter_parameters, labels)
|
|
40
|
+
next
|
|
41
|
+
elsif instruction.op == :label
|
|
42
|
+
validate_nested(instruction.arg2, functions, filter_parameters, labels.merge(instruction.arg1 => true))
|
|
43
|
+
next
|
|
44
|
+
elsif instruction.op == :break
|
|
45
|
+
raise CompileError, "label $#{instruction.arg1} is not defined" unless labels[instruction.arg1]
|
|
46
|
+
|
|
47
|
+
next
|
|
48
|
+
end
|
|
49
|
+
validate_nested(instruction.arg1, functions, filter_parameters, labels)
|
|
50
|
+
validate_nested(instruction.arg2, functions, filter_parameters, labels)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_stack(instructions)
|
|
55
|
+
depth = 0
|
|
56
|
+
terminated = false
|
|
57
|
+
instructions.each do |instruction|
|
|
58
|
+
raise CompileError, "unknown opcode #{instruction.op}" unless Opcodes::ALL.include?(instruction.op)
|
|
59
|
+
raise CompileError, 'unreachable bytecode after break' if terminated
|
|
60
|
+
|
|
61
|
+
if PUSH_OPS.include?(instruction.op)
|
|
62
|
+
depth += 1
|
|
63
|
+
elsif TRANSFORM_OPS.include?(instruction.op)
|
|
64
|
+
raise CompileError, "bytecode stack underflow at #{instruction.op}" if depth.zero?
|
|
65
|
+
elsif instruction.op == :break
|
|
66
|
+
terminated = true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
return if terminated
|
|
70
|
+
raise CompileError, "bytecode stack has #{depth} values, expected 1" unless depth == 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_call(instruction, functions, filter_parameters)
|
|
74
|
+
name = instruction.arg1
|
|
75
|
+
arity = instruction.arg2.length
|
|
76
|
+
return if arity.zero? && filter_parameters[name]
|
|
77
|
+
return if functions[[name, arity]]
|
|
78
|
+
return if Builtins.valid_arity?(name, arity)
|
|
79
|
+
|
|
80
|
+
raise CompileError, "#{name}/#{arity} is not defined"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_scoped_definition(instruction, functions, filter_parameters, labels)
|
|
84
|
+
definition = instruction.arg1
|
|
85
|
+
local_functions = functions.merge(signature(definition) => true)
|
|
86
|
+
validate_definition(definition, local_functions, labels)
|
|
87
|
+
validate_nested(instruction.arg2, local_functions, filter_parameters, labels)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_nested(value, functions, filter_parameters, labels)
|
|
91
|
+
case value
|
|
92
|
+
when BytecodeBlock
|
|
93
|
+
validate_instructions(value.instructions, functions, filter_parameters, labels)
|
|
94
|
+
when BytecodeFunctionDefinition
|
|
95
|
+
validate_definition(value, functions, labels)
|
|
96
|
+
when Array
|
|
97
|
+
value.each { |item| validate_nested(item, functions, filter_parameters, labels) }
|
|
98
|
+
when Hash
|
|
99
|
+
value.each_value { |item| validate_nested(item, functions, filter_parameters, labels) }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def signature(definition)
|
|
104
|
+
[definition.name, definition.params.length]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate_constant_object_keys(instruction)
|
|
108
|
+
instruction.arg1.each do |pair|
|
|
109
|
+
key = pair.fetch(:key)
|
|
110
|
+
next unless key.fetch(:type) == :filter
|
|
111
|
+
|
|
112
|
+
known, value = constant_filter_value(key.fetch(:block))
|
|
113
|
+
next unless known
|
|
114
|
+
next if value.is_a?(String)
|
|
115
|
+
|
|
116
|
+
dumped = JSON::Dumper.dump(value, indent: nil)
|
|
117
|
+
raise CompileError, "Cannot use #{Value.type_of(value)} (#{dumped}) as object key"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def constant_filter_value(block)
|
|
122
|
+
instructions = block.instructions
|
|
123
|
+
if instructions.length > 1 && instructions.last.op == :pipe
|
|
124
|
+
left = BytecodeBlock.new(instructions: instructions[0...-1])
|
|
125
|
+
right = instructions.last.arg1
|
|
126
|
+
left_known, left_value = constant_filter_value(left)
|
|
127
|
+
return [false, nil] unless left_known
|
|
128
|
+
return [true, left_value] if right.instructions.length == 1 && right.instructions.first.op == :load_input
|
|
129
|
+
|
|
130
|
+
return constant_filter_value(right)
|
|
131
|
+
end
|
|
132
|
+
return [false, nil] unless instructions.length == 1
|
|
133
|
+
|
|
134
|
+
instruction = instructions.first
|
|
135
|
+
case instruction.op
|
|
136
|
+
when :load_const
|
|
137
|
+
[true, @program.constants.fetch(instruction.arg1)]
|
|
138
|
+
when :array
|
|
139
|
+
return [true, []] unless instruction.arg1
|
|
140
|
+
|
|
141
|
+
constant_generator_block?(instruction.arg1) ? [true, []] : [false, nil]
|
|
142
|
+
when :object
|
|
143
|
+
constant_object_value(instruction.arg1)
|
|
144
|
+
when :binary
|
|
145
|
+
constant_binary_value(instruction)
|
|
146
|
+
else
|
|
147
|
+
[false, nil]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def constant_generator_block?(block)
|
|
152
|
+
instructions = block.instructions
|
|
153
|
+
return constant_filter_value(block).first if instructions.length == 1
|
|
154
|
+
return false unless instructions.last&.op == :append
|
|
155
|
+
|
|
156
|
+
left = BytecodeBlock.new(instructions: instructions[0...-1])
|
|
157
|
+
constant_generator_block?(left) && constant_generator_block?(instructions.last.arg1)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def constant_object_value(pairs)
|
|
161
|
+
object = {}
|
|
162
|
+
pairs.each do |pair|
|
|
163
|
+
key = pair.fetch(:key)
|
|
164
|
+
return [false, nil] unless key.fetch(:type) == :literal
|
|
165
|
+
|
|
166
|
+
known, value = constant_filter_value(pair.fetch(:value))
|
|
167
|
+
return [false, nil] unless known
|
|
168
|
+
|
|
169
|
+
object[key.fetch(:value)] = value
|
|
170
|
+
end
|
|
171
|
+
[true, object]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def constant_binary_value(instruction)
|
|
175
|
+
left_known, left = constant_filter_value(instruction.arg2[0])
|
|
176
|
+
right_known, right = constant_filter_value(instruction.arg2[1])
|
|
177
|
+
return [false, nil] unless left_known && right_known
|
|
178
|
+
|
|
179
|
+
case instruction.arg1
|
|
180
|
+
when '+'
|
|
181
|
+
return [true, right] if left.nil?
|
|
182
|
+
return [true, left] if right.nil?
|
|
183
|
+
return [true, ''] if left.is_a?(String) && right.is_a?(String)
|
|
184
|
+
return [true, []] if left.is_a?(Array) && right.is_a?(Array)
|
|
185
|
+
return [true, {}] if left.is_a?(Hash) && right.is_a?(Hash)
|
|
186
|
+
return [true, 0] if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
187
|
+
when '-'
|
|
188
|
+
return [true, []] if left.is_a?(Array) && right.is_a?(Array)
|
|
189
|
+
return [true, 0] if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
190
|
+
when '*'
|
|
191
|
+
return [true, ''] if (left.is_a?(String) && right.is_a?(Numeric)) ||
|
|
192
|
+
(left.is_a?(Numeric) && right.is_a?(String))
|
|
193
|
+
return [true, {}] if left.is_a?(Hash) && right.is_a?(Hash)
|
|
194
|
+
return [true, 0] if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
195
|
+
when '/'
|
|
196
|
+
return [true, []] if left.is_a?(String) && right.is_a?(String)
|
|
197
|
+
return [false, nil] if right.is_a?(Numeric) && right.zero?
|
|
198
|
+
return [true, 0] if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
199
|
+
when '%'
|
|
200
|
+
return [false, nil] if right.is_a?(Numeric) && right.zero?
|
|
201
|
+
return [true, 0] if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
202
|
+
when '==', '!=', '<', '<=', '>', '>='
|
|
203
|
+
return [true, true]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
[false, nil]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
data/lib/rjq/value.rb
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
module Value
|
|
5
|
+
TYPE_ORDER = {
|
|
6
|
+
'null' => 0,
|
|
7
|
+
'boolean' => 1,
|
|
8
|
+
'number' => 2,
|
|
9
|
+
'string' => 3,
|
|
10
|
+
'array' => 4,
|
|
11
|
+
'object' => 5
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def type_of(value)
|
|
17
|
+
case value
|
|
18
|
+
when NilClass
|
|
19
|
+
'null'
|
|
20
|
+
when TrueClass, FalseClass
|
|
21
|
+
'boolean'
|
|
22
|
+
when Numeric
|
|
23
|
+
'number'
|
|
24
|
+
when String
|
|
25
|
+
'string'
|
|
26
|
+
when Array
|
|
27
|
+
'array'
|
|
28
|
+
when Hash
|
|
29
|
+
'object'
|
|
30
|
+
else
|
|
31
|
+
raise TypeError, "unsupported value type: #{value.class}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def truthy?(value)
|
|
36
|
+
!(value.nil? || value == false)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def validate!(value)
|
|
40
|
+
active = {}
|
|
41
|
+
stack = [[:value, value]]
|
|
42
|
+
until stack.empty?
|
|
43
|
+
type, item = stack.pop
|
|
44
|
+
if type == :leave
|
|
45
|
+
active.delete(item)
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
case item
|
|
50
|
+
when NilClass, TrueClass, FalseClass, Numeric
|
|
51
|
+
next
|
|
52
|
+
when String
|
|
53
|
+
raise TypeError, 'invalid UTF-8 string' unless item.encoding == Encoding::UTF_8 && item.valid_encoding?
|
|
54
|
+
when Array
|
|
55
|
+
enter_validation_container!(item, active)
|
|
56
|
+
stack << [:leave, item.object_id]
|
|
57
|
+
item.reverse_each { |child| stack << [:value, child] }
|
|
58
|
+
when Hash
|
|
59
|
+
invalid = item.keys.find { |key| !key.is_a?(String) }
|
|
60
|
+
raise TypeError, "object key must be a string, got #{invalid.class}" if invalid
|
|
61
|
+
|
|
62
|
+
enter_validation_container!(item, active)
|
|
63
|
+
stack << [:leave, item.object_id]
|
|
64
|
+
item.values.reverse_each { |child| stack << [:value, child] }
|
|
65
|
+
else
|
|
66
|
+
raise TypeError, "unsupported value type: #{item.class}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def enter_validation_container!(value, active)
|
|
73
|
+
raise TypeError, 'cyclic JSON value' if active[value.object_id]
|
|
74
|
+
|
|
75
|
+
active[value.object_id] = true
|
|
76
|
+
end
|
|
77
|
+
private_class_method :enter_validation_container!
|
|
78
|
+
|
|
79
|
+
def equal?(left, right)
|
|
80
|
+
stack = [[left, right]]
|
|
81
|
+
until stack.empty?
|
|
82
|
+
left_value, right_value = stack.pop
|
|
83
|
+
left_type = type_of(left_value)
|
|
84
|
+
right_type = type_of(right_value)
|
|
85
|
+
if left_type == 'number' && right_type == 'number'
|
|
86
|
+
return false unless numeric_equal?(left_value, right_value)
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
return false unless left_type == right_type
|
|
90
|
+
|
|
91
|
+
case left_type
|
|
92
|
+
when 'array'
|
|
93
|
+
return false unless left_value.length == right_value.length
|
|
94
|
+
|
|
95
|
+
(left_value.length - 1).downto(0) { |index| stack << [left_value[index], right_value[index]] }
|
|
96
|
+
when 'object'
|
|
97
|
+
left_keys = left_value.keys.sort
|
|
98
|
+
return false unless left_keys == right_value.keys.sort
|
|
99
|
+
|
|
100
|
+
left_keys.reverse_each { |key| stack << [left_value.fetch(key), right_value.fetch(key)] }
|
|
101
|
+
else
|
|
102
|
+
return false unless left_value == right_value
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def compare(left, right)
|
|
109
|
+
stack = [[:compare, left, right]]
|
|
110
|
+
until stack.empty?
|
|
111
|
+
action, left_value, right_value = stack.pop
|
|
112
|
+
if action == :result
|
|
113
|
+
return left_value unless left_value.zero?
|
|
114
|
+
next
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
left_type = type_of(left_value)
|
|
118
|
+
right_type = type_of(right_value)
|
|
119
|
+
type_cmp = TYPE_ORDER.fetch(left_type) <=> TYPE_ORDER.fetch(right_type)
|
|
120
|
+
return type_cmp unless type_cmp.zero?
|
|
121
|
+
|
|
122
|
+
cmp = case left_type
|
|
123
|
+
when 'null' then 0
|
|
124
|
+
when 'boolean' then bool_rank(left_value) <=> bool_rank(right_value)
|
|
125
|
+
when 'number' then numeric_compare(left_value, right_value)
|
|
126
|
+
when 'string' then left_value <=> right_value
|
|
127
|
+
when 'array'
|
|
128
|
+
push_array_comparison(stack, left_value, right_value)
|
|
129
|
+
0
|
|
130
|
+
when 'object'
|
|
131
|
+
push_object_comparison(stack, left_value, right_value)
|
|
132
|
+
0
|
|
133
|
+
end
|
|
134
|
+
return cmp unless cmp.zero?
|
|
135
|
+
end
|
|
136
|
+
0
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def merge_objects(left, right)
|
|
140
|
+
merged = left.merge(right)
|
|
141
|
+
stack = [[left, right, merged]]
|
|
142
|
+
until stack.empty?
|
|
143
|
+
left_object, right_object, output = stack.pop
|
|
144
|
+
right_object.each do |key, right_value|
|
|
145
|
+
left_value = left_object[key]
|
|
146
|
+
next unless left_value.is_a?(Hash) && right_value.is_a?(Hash)
|
|
147
|
+
|
|
148
|
+
child = left_value.merge(right_value)
|
|
149
|
+
output[key] = child
|
|
150
|
+
stack << [left_value, right_value, child]
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
merged
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def deep_copy(value)
|
|
157
|
+
root = []
|
|
158
|
+
active = {}
|
|
159
|
+
stack = [[:copy, value, [:array, root, 0]]]
|
|
160
|
+
until stack.empty?
|
|
161
|
+
type, source, target = stack.pop
|
|
162
|
+
case type
|
|
163
|
+
when :leave
|
|
164
|
+
active.delete(source)
|
|
165
|
+
when :array_items
|
|
166
|
+
original, copy, index = source
|
|
167
|
+
next if index >= original.length
|
|
168
|
+
|
|
169
|
+
stack << [:array_items, [original, copy, index + 1], nil]
|
|
170
|
+
stack << [:copy, original[index], [:array, copy, index]]
|
|
171
|
+
when :object_items
|
|
172
|
+
original, copy, keys, index = source
|
|
173
|
+
next if index >= keys.length
|
|
174
|
+
|
|
175
|
+
key = keys[index]
|
|
176
|
+
stack << [:object_items, [original, copy, keys, index + 1], nil]
|
|
177
|
+
stack << [:copy, original.fetch(key), [:object, copy, key.dup]]
|
|
178
|
+
when :copy
|
|
179
|
+
copied = copy_scalar(source)
|
|
180
|
+
if source.is_a?(Array)
|
|
181
|
+
enter_copy_container!(source, active)
|
|
182
|
+
copied = Array.new(source.length)
|
|
183
|
+
stack << [:leave, source.object_id, nil]
|
|
184
|
+
stack << [:array_items, [source, copied, 0], nil]
|
|
185
|
+
elsif source.is_a?(Hash)
|
|
186
|
+
validate_copy_keys!(source)
|
|
187
|
+
enter_copy_container!(source, active)
|
|
188
|
+
copied = {}
|
|
189
|
+
stack << [:leave, source.object_id, nil]
|
|
190
|
+
stack << [:object_items, [source, copied, source.keys, 0], nil]
|
|
191
|
+
end
|
|
192
|
+
assign_copy(target, copied)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
root.fetch(0)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def copy_scalar(value)
|
|
199
|
+
case value
|
|
200
|
+
when String then value.dup
|
|
201
|
+
when NilClass, TrueClass, FalseClass, Numeric, Array, Hash then value
|
|
202
|
+
else raise TypeError, "unsupported value type: #{value.class}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
private_class_method :copy_scalar
|
|
206
|
+
|
|
207
|
+
def enter_copy_container!(value, active)
|
|
208
|
+
raise TypeError, 'cannot copy a cyclic JSON value' if active[value.object_id]
|
|
209
|
+
|
|
210
|
+
active[value.object_id] = true
|
|
211
|
+
end
|
|
212
|
+
private_class_method :enter_copy_container!
|
|
213
|
+
|
|
214
|
+
def validate_copy_keys!(value)
|
|
215
|
+
invalid = value.keys.find { |key| !key.is_a?(String) }
|
|
216
|
+
raise TypeError, "object key must be a string, got #{invalid.class}" if invalid
|
|
217
|
+
end
|
|
218
|
+
private_class_method :validate_copy_keys!
|
|
219
|
+
|
|
220
|
+
def assign_copy(target, value)
|
|
221
|
+
_type, container, key = target
|
|
222
|
+
container[key] = value
|
|
223
|
+
end
|
|
224
|
+
private_class_method :assign_copy
|
|
225
|
+
|
|
226
|
+
def numeric_equal?(left, right)
|
|
227
|
+
return false if nan_number?(left) || nan_number?(right)
|
|
228
|
+
|
|
229
|
+
numeric_compare(left, right).zero?
|
|
230
|
+
end
|
|
231
|
+
private_class_method :numeric_equal?
|
|
232
|
+
|
|
233
|
+
def unsafe_integer?(value)
|
|
234
|
+
value.is_a?(Integer) && value.abs > (2**53)
|
|
235
|
+
end
|
|
236
|
+
private_class_method :unsafe_integer?
|
|
237
|
+
|
|
238
|
+
def bool_rank(value)
|
|
239
|
+
value ? 1 : 0
|
|
240
|
+
end
|
|
241
|
+
private_class_method :bool_rank
|
|
242
|
+
|
|
243
|
+
def numeric_compare(left, right)
|
|
244
|
+
left_nan = nan_number?(left)
|
|
245
|
+
right_nan = nan_number?(right)
|
|
246
|
+
return 0 if left_nan && right_nan
|
|
247
|
+
return -1 if left_nan
|
|
248
|
+
return 1 if right_nan
|
|
249
|
+
|
|
250
|
+
return left.decimal_compare(right) if left.is_a?(Number) && right.is_a?(Number)
|
|
251
|
+
return left.decimal_compare(Number.parse(right.to_s)) if left.is_a?(Number) && right.is_a?(Integer)
|
|
252
|
+
return -right.decimal_compare(Number.parse(left.to_s)) if left.is_a?(Integer) && right.is_a?(Number)
|
|
253
|
+
|
|
254
|
+
comparable_number(left) <=> comparable_number(right)
|
|
255
|
+
end
|
|
256
|
+
private_class_method :numeric_compare
|
|
257
|
+
|
|
258
|
+
def nan_number?(value)
|
|
259
|
+
value.respond_to?(:nan?) && value.nan?
|
|
260
|
+
end
|
|
261
|
+
private_class_method :nan_number?
|
|
262
|
+
|
|
263
|
+
def comparable_number(value)
|
|
264
|
+
return value.to_f if value.is_a?(Number) || unsafe_integer?(value)
|
|
265
|
+
|
|
266
|
+
value
|
|
267
|
+
end
|
|
268
|
+
private_class_method :comparable_number
|
|
269
|
+
|
|
270
|
+
def push_array_comparison(stack, left, right)
|
|
271
|
+
length = [left.length, right.length].min
|
|
272
|
+
stack << [:result, left.length <=> right.length, nil]
|
|
273
|
+
(length - 1).downto(0) { |index| stack << [:compare, left[index], right[index]] }
|
|
274
|
+
end
|
|
275
|
+
private_class_method :push_array_comparison
|
|
276
|
+
|
|
277
|
+
def push_object_comparison(stack, left, right)
|
|
278
|
+
left_keys = left.keys.sort
|
|
279
|
+
right_keys = right.keys.sort
|
|
280
|
+
if left_keys == right_keys
|
|
281
|
+
left_keys.reverse_each { |key| stack << [:compare, left.fetch(key), right.fetch(key)] }
|
|
282
|
+
end
|
|
283
|
+
push_array_comparison(stack, left_keys, right_keys)
|
|
284
|
+
end
|
|
285
|
+
private_class_method :push_object_comparison
|
|
286
|
+
end
|
|
287
|
+
end
|