schemurai 3.1.0-aarch64-linux-gnu

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +395 -0
  4. data/lib/schemurai/backend.rb +34 -0
  5. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  6. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  7. data/lib/schemurai/container_checks.rb +534 -0
  8. data/lib/schemurai/decimal.rb +11 -0
  9. data/lib/schemurai/dialect.rb +106 -0
  10. data/lib/schemurai/dialect_keywords.rb +91 -0
  11. data/lib/schemurai/dialects/draft2019_09.rb +27 -0
  12. data/lib/schemurai/dialects/draft2020_12.rb +27 -0
  13. data/lib/schemurai/dialects/draft7.rb +27 -0
  14. data/lib/schemurai/ecma_regexp.rb +46 -0
  15. data/lib/schemurai/error_message.rb +102 -0
  16. data/lib/schemurai/evaluation.rb +63 -0
  17. data/lib/schemurai/evaluation_limits.rb +65 -0
  18. data/lib/schemurai/evaluation_pool.rb +66 -0
  19. data/lib/schemurai/evaluator.rb +861 -0
  20. data/lib/schemurai/formats.rb +403 -0
  21. data/lib/schemurai/json_pointer.rb +11 -0
  22. data/lib/schemurai/limit_budget.rb +78 -0
  23. data/lib/schemurai/limit_exceeded.rb +18 -0
  24. data/lib/schemurai/limited_evaluator.rb +43 -0
  25. data/lib/schemurai/meta_schemas/draft2019_09.rb +149 -0
  26. data/lib/schemurai/meta_schemas/draft2020_12.rb +149 -0
  27. data/lib/schemurai/meta_schemas/draft7.rb +121 -0
  28. data/lib/schemurai/meta_schemas.rb +28 -0
  29. data/lib/schemurai/native/4.0/schemurai_native.so +0 -0
  30. data/lib/schemurai/native/diagnostics.rb +53 -0
  31. data/lib/schemurai/native/evaluator.rb +87 -0
  32. data/lib/schemurai/native/null_numeric.rb +23 -0
  33. data/lib/schemurai/number_checks.rb +128 -0
  34. data/lib/schemurai/schema_domain.rb +95 -0
  35. data/lib/schemurai/schema_graph.rb +538 -0
  36. data/lib/schemurai/schema_node.rb +55 -0
  37. data/lib/schemurai/string_checks.rb +166 -0
  38. data/lib/schemurai/type_checks.rb +59 -0
  39. data/lib/schemurai/unique_items.rb +68 -0
  40. data/lib/schemurai/value_equality.rb +72 -0
  41. data/lib/schemurai/version.rb +6 -0
  42. data/lib/schemurai/vm/compiler.rb +410 -0
  43. data/lib/schemurai/vm/evaluator.rb +663 -0
  44. data/lib/schemurai/vm/limited_evaluator.rb +27 -0
  45. data/lib/schemurai.rb +307 -0
  46. data/lib/schemurai_native.rb +4 -0
  47. data/sig/schemurai.rbs +108 -0
  48. metadata +121 -0
@@ -0,0 +1,410 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../decimal"
4
+ require_relative "../number_checks"
5
+ require_relative "../type_checks"
6
+
7
+ module Schemurai
8
+ module VM
9
+ NumberRules = Data.define(
10
+ :mask,
11
+ :maximum,
12
+ :minimum,
13
+ :exclusive_maximum,
14
+ :exclusive_minimum,
15
+ :multiple_of
16
+ )
17
+
18
+ MAXIMUM = Internal::NumberChecks::MAXIMUM
19
+ MINIMUM = Internal::NumberChecks::MINIMUM
20
+ EXCLUSIVE_MAXIMUM = Internal::NumberChecks::EXCLUSIVE_MAXIMUM
21
+ EXCLUSIVE_MINIMUM = Internal::NumberChecks::EXCLUSIVE_MINIMUM
22
+ MULTIPLE_OF = Internal::NumberChecks::MULTIPLE_OF
23
+
24
+ TypeRules = Data.define(:mask, :names)
25
+
26
+ TYPE_NULL = Internal::TypeChecks::NULL
27
+ TYPE_BOOLEAN = Internal::TypeChecks::BOOLEAN
28
+ TYPE_OBJECT = Internal::TypeChecks::OBJECT
29
+ TYPE_ARRAY = Internal::TypeChecks::ARRAY
30
+ TYPE_NUMBER = Internal::TypeChecks::NUMBER
31
+ TYPE_INTEGER = Internal::TypeChecks::INTEGER
32
+ TYPE_STRING = Internal::TypeChecks::STRING
33
+
34
+ TRACKS_EVALUATION = 1 << 0
35
+ TRACKS_DYNAMIC_SCOPE = 1 << 1
36
+
37
+ DIALECT = Schemurai.const_get(:Internal)::Dialect
38
+ KEYWORD_TYPE = DIALECT::TYPE
39
+ KEYWORD_ENUM = DIALECT::ENUM
40
+ KEYWORD_COMBINER = DIALECT::COMBINER
41
+ KEYWORD_NUMBER = DIALECT::NUMBER
42
+ KEYWORD_STRING = DIALECT::STRING
43
+ KEYWORD_ARRAY = DIALECT::ARRAY
44
+ KEYWORD_OBJECT = DIALECT::OBJECT
45
+ private_constant :DIALECT
46
+
47
+ TYPE_BITS = Internal::TypeChecks::BITS
48
+
49
+ TYPE_OPCODES = {
50
+ "null" => :type_null,
51
+ "boolean" => :type_boolean,
52
+ "object" => :type_object,
53
+ "array" => :type_array,
54
+ "number" => :type_number,
55
+ "integer" => :type_integer,
56
+ "string" => :type_string
57
+ }.freeze
58
+
59
+ ReferenceRules = Data.define(:value, :fragment)
60
+
61
+ StringRules = Data.define(
62
+ :max_length,
63
+ :min_length,
64
+ :pattern,
65
+ :format,
66
+ :format_assertion,
67
+ :decode_base64,
68
+ :parse_json
69
+ )
70
+
71
+ ArrayRules = Data.define(
72
+ :max_items,
73
+ :min_items,
74
+ :unique,
75
+ :prefix_items,
76
+ :items,
77
+ :items_list,
78
+ :additional,
79
+ :contains,
80
+ :min_contains,
81
+ :max_contains,
82
+ :count_contains,
83
+ :unevaluated
84
+ )
85
+
86
+ ObjectRules = Data.define(
87
+ :max_properties,
88
+ :min_properties,
89
+ :required,
90
+ :properties,
91
+ :patterns,
92
+ :additional,
93
+ :property_names,
94
+ :dependencies,
95
+ :dependent_required,
96
+ :dependent_schemas,
97
+ :unevaluated
98
+ )
99
+
100
+ ConditionalRules = Data.define(
101
+ :condition,
102
+ :then_branch,
103
+ :else_branch
104
+ )
105
+
106
+ class Program
107
+ attr_reader :node, :code, :dynamic_anchor
108
+
109
+ def initialize(node)
110
+ @node = node
111
+ @tracks_evaluation = false
112
+ @tracks_dynamic_scope = false
113
+ schema = node.schema
114
+ if schema.instance_of?(Hash)
115
+ @recursive_anchor = schema["$recursiveAnchor"] == true
116
+ dynamic_anchor = schema["$dynamicAnchor"]
117
+ @dynamic_anchor = -dynamic_anchor if dynamic_anchor.instance_of?(String)
118
+ else
119
+ @recursive_anchor = false
120
+ end
121
+ end
122
+
123
+ def emit(code, opcode, operand)
124
+ code.push(opcode, operand)
125
+ end
126
+
127
+ def track_evaluation!
128
+ @tracks_evaluation = true
129
+ end
130
+
131
+ def track_dynamic_scope!
132
+ @tracks_dynamic_scope = true
133
+ end
134
+
135
+ def finish(code)
136
+ flags = 0
137
+ flags |= TRACKS_EVALUATION if @tracks_evaluation
138
+ flags |= TRACKS_DYNAMIC_SCOPE if @tracks_dynamic_scope
139
+ code[0] = flags
140
+ @code = code.freeze
141
+ freeze
142
+ end
143
+
144
+ def recursive_anchor?
145
+ @recursive_anchor
146
+ end
147
+
148
+ def tracks_dynamic_scope?
149
+ @tracks_dynamic_scope
150
+ end
151
+ end
152
+
153
+ class Compiler
154
+ def initialize(graph)
155
+ @graph = graph
156
+ @programs = {}.compare_by_identity
157
+ end
158
+
159
+ def compile(node)
160
+ program = @programs[node]
161
+ return program if program
162
+
163
+ program = Program.new(node)
164
+ @programs[node] = program
165
+ program.finish(compile_code(node, program))
166
+ end
167
+
168
+ def compile_all
169
+ @graph.nodes.each { |node| compile(node) }
170
+ self
171
+ end
172
+
173
+ def resolve(program, reference)
174
+ compile(@graph.resolve(program.node, reference))
175
+ end
176
+
177
+ private def compile_child(node, parent)
178
+ child = compile(node)
179
+ parent.track_dynamic_scope! if child.tracks_dynamic_scope?
180
+ child
181
+ end
182
+
183
+ private def compile_code(node, program)
184
+ schema = node.schema
185
+ return program.emit([0], :boolean, schema) if schema.equal?(true) || schema.equal?(false)
186
+ return [0] unless schema.is_a?(Hash)
187
+
188
+ code = [0]
189
+ if schema.key?("$ref")
190
+ program.track_dynamic_scope!
191
+ program.emit(code, :ref, compile_reference(schema["$ref"]))
192
+ return code unless node.dialect.ref_siblings?
193
+ end
194
+ if schema.key?("$recursiveRef")
195
+ program.track_dynamic_scope!
196
+ program.emit(code, :recursive_ref, compile_reference(schema["$recursiveRef"]))
197
+ end
198
+ if schema.key?("$dynamicRef")
199
+ program.track_dynamic_scope!
200
+ program.emit(code, :dynamic_ref, compile_reference(schema["$dynamicRef"]))
201
+ end
202
+
203
+ mask = node.keyword_mask
204
+ compile_type(code, program, schema["type"]) if (mask & KEYWORD_TYPE) != 0 && schema.key?("type")
205
+ if (mask & KEYWORD_ENUM) != 0
206
+ program.emit(code, :enum, snapshot(schema["enum"])) if schema.key?("enum")
207
+ program.emit(code, :const, snapshot(schema["const"])) if schema.key?("const")
208
+ end
209
+ compile_combiners(code, program, node, schema) if (mask & KEYWORD_COMBINER) != 0
210
+ program.emit(code, :number, compile_number(schema)) if (mask & KEYWORD_NUMBER) != 0
211
+ if (mask & KEYWORD_STRING) != 0 || node.format
212
+ program.emit(code, :string, compile_string(node, schema))
213
+ end
214
+ if (mask & KEYWORD_ARRAY) != 0
215
+ rules = compile_array(node, schema, program)
216
+ program.track_evaluation! if rules.unevaluated
217
+ program.emit(code, :array, rules)
218
+ end
219
+ if (mask & KEYWORD_OBJECT) != 0
220
+ rules = compile_object(node, schema, program)
221
+ program.track_evaluation! if rules.unevaluated
222
+ program.emit(code, :object, rules)
223
+ end
224
+ fuse_type_instruction(code)
225
+ end
226
+
227
+ private def fuse_type_instruction(code)
228
+ index = 1
229
+ while index + 3 < code.length
230
+ type_opcode = code[index]
231
+ rules_opcode = code[index + 2]
232
+ fused_opcode = case type_opcode
233
+ when :type_object then :typed_object if rules_opcode == :object
234
+ when :type_array then :typed_array if rules_opcode == :array
235
+ when :type_string then :typed_string if rules_opcode == :string
236
+ when :type_number then :typed_number if rules_opcode == :number
237
+ when :type_integer then :typed_integer if rules_opcode == :number
238
+ end
239
+ if fused_opcode
240
+ code[index] = fused_opcode
241
+ code[index + 1] = code[index + 3]
242
+ code.slice!(index + 2, 2)
243
+ break
244
+ end
245
+ index += 2
246
+ end
247
+ code
248
+ end
249
+
250
+ private def compile_combiners(code, program, node, schema)
251
+ %w[allOf anyOf oneOf].each do |keyword|
252
+ next unless schema.key?(keyword)
253
+
254
+ children = schema[keyword].each_index.map do |index|
255
+ compile_child(node.child(keyword, index), program)
256
+ end
257
+ program.emit(code, keyword.to_sym, children)
258
+ end
259
+ program.emit(code, :not, compile_child(node.child("not"), program)) if schema.key?("not")
260
+ return unless schema.key?("if")
261
+
262
+ program.emit(
263
+ code,
264
+ :conditional,
265
+ ConditionalRules.new(
266
+ compile_child(node.child("if"), program),
267
+ schema.key?("then") ? compile_child(node.child("then"), program) : nil,
268
+ schema.key?("else") ? compile_child(node.child("else"), program) : nil
269
+ )
270
+ )
271
+ end
272
+
273
+ private def compile_number(schema)
274
+ mask = 0
275
+ mask |= MAXIMUM if schema.key?("maximum")
276
+ mask |= MINIMUM if schema.key?("minimum")
277
+ mask |= EXCLUSIVE_MAXIMUM if schema.key?("exclusiveMaximum")
278
+ mask |= EXCLUSIVE_MINIMUM if schema.key?("exclusiveMinimum")
279
+ mask |= MULTIPLE_OF if schema.key?("multipleOf")
280
+ NumberRules.new(
281
+ mask,
282
+ compile_decimal(schema["maximum"]),
283
+ compile_decimal(schema["minimum"]),
284
+ compile_decimal(schema["exclusiveMaximum"]),
285
+ compile_decimal(schema["exclusiveMinimum"]),
286
+ compile_decimal(schema["multipleOf"])
287
+ )
288
+ end
289
+
290
+ private def compile_reference(reference)
291
+ value = snapshot(reference)
292
+ separator = value.index("#")
293
+ fragment = separator ? -value[(separator + 1)..] : nil
294
+ ReferenceRules.new(value, fragment)
295
+ end
296
+
297
+ private def compile_type(code, program, types)
298
+ return program.emit(code, TYPE_OPCODES.fetch(types), nil) unless types.is_a?(Array)
299
+
300
+ mask = types.reduce(0) { |result, type| result | TYPE_BITS.fetch(type) }
301
+ program.emit(code, :types, TypeRules.new(mask, snapshot(types)))
302
+ end
303
+
304
+ private def compile_string(node, schema)
305
+ StringRules.new(
306
+ schema["maxLength"],
307
+ schema["minLength"],
308
+ snapshot(schema["pattern"]),
309
+ node.format,
310
+ node.dialect.format_assertion?,
311
+ schema["contentEncoding"] == "base64",
312
+ schema["contentMediaType"] == "application/json"
313
+ )
314
+ end
315
+
316
+ private def compile_array(node, schema, program)
317
+ prefix_items = if schema["prefixItems"].is_a?(Array)
318
+ schema["prefixItems"].each_index.map do |index|
319
+ compile_child(node.child("prefixItems", index), program)
320
+ end.freeze
321
+ end
322
+ items = if schema["items"].is_a?(Array)
323
+ schema["items"].each_index.map do |index|
324
+ compile_child(node.child("items", index), program)
325
+ end.freeze
326
+ elsif !schema["items"].nil?
327
+ compile_child(node.child("items"), program)
328
+ end
329
+ ArrayRules.new(
330
+ schema["maxItems"],
331
+ schema["minItems"],
332
+ schema["uniqueItems"],
333
+ prefix_items,
334
+ items,
335
+ items.is_a?(Array),
336
+ schema.key?("additionalItems") ? compile_child(node.child("additionalItems"), program) : nil,
337
+ schema.key?("contains") ? compile_child(node.child("contains"), program) : nil,
338
+ schema.fetch("minContains", 1),
339
+ schema.fetch("maxContains", Float::INFINITY),
340
+ node.dialect.keywords.key?("minContains"),
341
+ schema.key?("unevaluatedItems") ? compile_child(node.child("unevaluatedItems"), program) : nil
342
+ )
343
+ end
344
+
345
+ private def compile_object(node, schema, program)
346
+ ObjectRules.new(
347
+ schema["maxProperties"],
348
+ schema["minProperties"],
349
+ snapshot(schema["required"]),
350
+ compile_map(node, schema, "properties", program),
351
+ compile_optional_map(node, schema, "patternProperties", program),
352
+ schema.key?("additionalProperties") ? compile_child(node.child("additionalProperties"), program) : nil,
353
+ schema.key?("propertyNames") ? compile_child(node.child("propertyNames"), program) : nil,
354
+ compile_dependencies(node, schema, program),
355
+ schema.fetch("dependentRequired", {}).map do |name, required_names|
356
+ [snapshot(name), snapshot(required_names)].freeze
357
+ end.then { |entries| entries.empty? ? nil : entries.freeze },
358
+ compile_optional_map(node, schema, "dependentSchemas", program),
359
+ schema.key?("unevaluatedProperties") ? compile_child(node.child("unevaluatedProperties"), program) : nil
360
+ )
361
+ end
362
+
363
+ private def compile_map(node, schema, keyword, program)
364
+ schema.fetch(keyword, {}).each_key.to_h do |name|
365
+ [snapshot(name), compile_child(node.child(keyword, name), program)]
366
+ end.freeze
367
+ end
368
+
369
+ private def compile_optional_map(node, schema, keyword, program)
370
+ compiled = compile_map(node, schema, keyword, program)
371
+ compiled unless compiled.empty?
372
+ end
373
+
374
+ private def compile_dependencies(node, schema, program)
375
+ compiled = schema.fetch("dependencies", {}).map do |name, dependency|
376
+ compiled = if dependency.is_a?(Array)
377
+ snapshot(dependency)
378
+ else
379
+ compile_child(node.child("dependencies", name), program)
380
+ end
381
+ [snapshot(name), compiled].freeze
382
+ end
383
+ compiled.freeze unless compiled.empty?
384
+ end
385
+
386
+ # Keep aliases inside one operand while copying away from caller-owned
387
+ # containers. A fresh identity table also rechecks later compilations.
388
+ private def snapshot(value, copies = {}.compare_by_identity)
389
+ return copies[value] if copies.key?(value)
390
+
391
+ case value
392
+ when Hash
393
+ copies[value] = value.to_h { |key, item| [snapshot(key, copies), snapshot(item, copies)] }.freeze
394
+ when Array
395
+ copies[value] = value.map { |item| snapshot(item, copies) }.freeze
396
+ when String
397
+ -value
398
+ else
399
+ value
400
+ end
401
+ end
402
+
403
+ private def compile_decimal(value)
404
+ return value if value.nil? || value.is_a?(Integer) || value.is_a?(Rational)
405
+
406
+ Internal::Decimal.parse(value)
407
+ end
408
+ end
409
+ end
410
+ end