schemurai 2.2.2 → 2.3.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 +4 -4
- data/README.md +14 -3
- data/ext/schemurai_native/Cargo.lock +99 -0
- data/ext/schemurai_native/Cargo.toml +21 -0
- data/ext/schemurai_native/build.rs +35 -0
- data/ext/schemurai_native/extconf.rb +63 -0
- data/ext/schemurai_native/glue.c +639 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +965 -0
- data/ext/schemurai_native/src/format.rs +357 -0
- data/ext/schemurai_native/src/lib.rs +2376 -0
- data/ext/schemurai_native/src/number.rs +1055 -0
- data/ext/schemurai_native/src/pattern.rs +288 -0
- data/ext/schemurai_native/src/scope.rs +210 -0
- data/ext/schemurai_native/src/tracking.rs +532 -0
- data/lib/schemurai/backend.rb +2 -1
- data/lib/schemurai/native/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +32 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/evaluator.rb +28 -32
- data/lib/schemurai.rb +8 -7
- data/sig/schemurai.rbs +1 -1
- metadata +22 -4
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
//! Evaluated locations stay in native buffers, scoped to one instance location.
|
|
2
|
+
//! Keys remain borrowed VALUEs; no Ruby objects or instance trees are allocated.
|
|
3
|
+
use super::*;
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
use std::hash::Hash;
|
|
6
|
+
|
|
7
|
+
#[derive(Clone, Copy, Debug)]
|
|
8
|
+
pub(super) struct JsonKey(pub Value, u64);
|
|
9
|
+
impl JsonKey {
|
|
10
|
+
pub(super) fn new(value: Value) -> Self {
|
|
11
|
+
Self(value, string_fingerprint(value))
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
impl PartialEq for JsonKey {
|
|
15
|
+
fn eq(&self, other: &Self) -> bool {
|
|
16
|
+
self.0 == other.0 || unsafe { sn_string_equal(self.0, other.0) != 0 }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
impl Eq for JsonKey {}
|
|
20
|
+
impl Hash for JsonKey {
|
|
21
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
22
|
+
state.write_u64(self.1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
pub(super) type KeySet = HashSet<JsonKey, BuildHasherDefault<BucketHasher>>;
|
|
26
|
+
|
|
27
|
+
pub(super) struct Tracked {
|
|
28
|
+
pub status: Verdict,
|
|
29
|
+
properties: KeySet,
|
|
30
|
+
known_properties: Vec<u64>,
|
|
31
|
+
items: Vec<u64>,
|
|
32
|
+
}
|
|
33
|
+
impl Tracked {
|
|
34
|
+
pub(super) fn new(status: Verdict) -> Self {
|
|
35
|
+
Self {
|
|
36
|
+
status,
|
|
37
|
+
properties: KeySet::default(),
|
|
38
|
+
known_properties: vec![],
|
|
39
|
+
items: vec![],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
pub(super) fn item(&mut self, index: usize) {
|
|
43
|
+
let word = index / 64;
|
|
44
|
+
if self.items.len() <= word {
|
|
45
|
+
self.items.resize(word + 1, 0);
|
|
46
|
+
}
|
|
47
|
+
self.items[word] |= 1_u64 << (index % 64);
|
|
48
|
+
}
|
|
49
|
+
pub(super) fn contains_item(&self, index: usize) -> bool {
|
|
50
|
+
self.items
|
|
51
|
+
.get(index / 64)
|
|
52
|
+
.is_some_and(|word| word & (1_u64 << (index % 64)) != 0)
|
|
53
|
+
}
|
|
54
|
+
fn known_property(&mut self, index: usize) {
|
|
55
|
+
let word = index / 64;
|
|
56
|
+
if self.known_properties.len() <= word {
|
|
57
|
+
self.known_properties.resize(word + 1, 0);
|
|
58
|
+
}
|
|
59
|
+
self.known_properties[word] |= 1_u64 << (index % 64);
|
|
60
|
+
}
|
|
61
|
+
fn contains_known_property(&self, index: usize) -> bool {
|
|
62
|
+
self.known_properties
|
|
63
|
+
.get(index / 64)
|
|
64
|
+
.is_some_and(|word| word & (1_u64 << (index % 64)) != 0)
|
|
65
|
+
}
|
|
66
|
+
pub(super) fn property_count(&self) -> usize {
|
|
67
|
+
self.properties.len()
|
|
68
|
+
+ self
|
|
69
|
+
.known_properties
|
|
70
|
+
.iter()
|
|
71
|
+
.map(|word| word.count_ones() as usize)
|
|
72
|
+
.sum::<usize>()
|
|
73
|
+
}
|
|
74
|
+
pub(super) fn item_count(&self) -> usize {
|
|
75
|
+
self.items
|
|
76
|
+
.iter()
|
|
77
|
+
.map(|word| word.count_ones() as usize)
|
|
78
|
+
.sum()
|
|
79
|
+
}
|
|
80
|
+
pub(super) fn merge(&mut self, other: Self) -> Verdict {
|
|
81
|
+
if other.status != Valid {
|
|
82
|
+
return other.status;
|
|
83
|
+
}
|
|
84
|
+
if self.properties.is_empty() {
|
|
85
|
+
self.properties = other.properties;
|
|
86
|
+
} else {
|
|
87
|
+
self.properties.extend(other.properties);
|
|
88
|
+
}
|
|
89
|
+
merge_bits(&mut self.known_properties, other.known_properties);
|
|
90
|
+
merge_bits(&mut self.items, other.items);
|
|
91
|
+
Valid
|
|
92
|
+
}
|
|
93
|
+
pub(super) fn merge_diagnostics(&mut self, other: Self) -> Result<(), Verdict> {
|
|
94
|
+
if other.status == Unsupported {
|
|
95
|
+
return Err(Unsupported);
|
|
96
|
+
}
|
|
97
|
+
if self.status != Valid || other.status != Valid {
|
|
98
|
+
*self = Self::new(Invalid);
|
|
99
|
+
} else {
|
|
100
|
+
self.merge(other);
|
|
101
|
+
}
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
104
|
+
pub(super) fn property(&mut self, engine: &Engine, value: Value) {
|
|
105
|
+
let key = JsonKey::new(value);
|
|
106
|
+
if let Some(&index) = engine.property_ids.get(&key) {
|
|
107
|
+
self.known_property(index);
|
|
108
|
+
} else {
|
|
109
|
+
self.properties.insert(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
pub(super) fn contains_property(&self, engine: &Engine, value: Value) -> bool {
|
|
113
|
+
let key = JsonKey::new(value);
|
|
114
|
+
engine
|
|
115
|
+
.property_ids
|
|
116
|
+
.get(&key)
|
|
117
|
+
.is_some_and(|&index| self.contains_known_property(index))
|
|
118
|
+
|| self.properties.contains(&key)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
fn merge_bits(target: &mut Vec<u64>, source: Vec<u64>) {
|
|
122
|
+
// Branch annotations own native buffers of location bits. Transfer an
|
|
123
|
+
// existing buffer into an empty accumulator instead of allocating a copy.
|
|
124
|
+
if target.is_empty() {
|
|
125
|
+
*target = source;
|
|
126
|
+
} else {
|
|
127
|
+
if target.len() < source.len() {
|
|
128
|
+
target.resize(source.len(), 0);
|
|
129
|
+
}
|
|
130
|
+
for (target, source) in target.iter_mut().zip(source) {
|
|
131
|
+
*target |= source;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
impl Engine {
|
|
136
|
+
pub(super) fn eval_tracked(
|
|
137
|
+
&self,
|
|
138
|
+
node: usize,
|
|
139
|
+
value: Value,
|
|
140
|
+
depth: usize,
|
|
141
|
+
active: scope::Context<'_>,
|
|
142
|
+
) -> Tracked {
|
|
143
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
144
|
+
return Tracked::new(Unsupported);
|
|
145
|
+
}
|
|
146
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
147
|
+
TYPE_INTEGRAL_NUMBER
|
|
148
|
+
} else {
|
|
149
|
+
unsafe { sn_kind(value) }
|
|
150
|
+
};
|
|
151
|
+
self.eval_tracked_kind(node, value, depth, active, kind)
|
|
152
|
+
}
|
|
153
|
+
// Applicator branches evaluate the same borrowed instance under the GVL.
|
|
154
|
+
// No Ruby callback can mutate it, so its guarded kind can be reused while
|
|
155
|
+
// each branch still enters its own resource scope and checks depth.
|
|
156
|
+
pub(super) fn eval_tracked_kind(
|
|
157
|
+
&self,
|
|
158
|
+
node: usize,
|
|
159
|
+
value: Value,
|
|
160
|
+
depth: usize,
|
|
161
|
+
active: scope::Context<'_>,
|
|
162
|
+
kind: i32,
|
|
163
|
+
) -> Tracked {
|
|
164
|
+
let frame = scope::ScopeFrame {
|
|
165
|
+
resource: self.metadata[node].resource,
|
|
166
|
+
parent: active.scope,
|
|
167
|
+
primary_binding: active
|
|
168
|
+
.scope
|
|
169
|
+
.and_then(|frame| frame.primary_binding)
|
|
170
|
+
.or(self.metadata[node].primary_binding),
|
|
171
|
+
};
|
|
172
|
+
let active = self.enter_scope(node, active, &frame);
|
|
173
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
174
|
+
return Tracked::new(Unsupported);
|
|
175
|
+
}
|
|
176
|
+
if kind == TYPE_UNSUPPORTED {
|
|
177
|
+
return Tracked::new(Unsupported);
|
|
178
|
+
}
|
|
179
|
+
let mut evaluation = Tracked::new(Valid);
|
|
180
|
+
for op in &self.nodes[node] {
|
|
181
|
+
let result = match op {
|
|
182
|
+
Op::Ref(target) => {
|
|
183
|
+
if RefFrame::contains(active.active, node, value) {
|
|
184
|
+
Valid
|
|
185
|
+
} else {
|
|
186
|
+
let frame = RefFrame {
|
|
187
|
+
source: node,
|
|
188
|
+
value,
|
|
189
|
+
parent: active.active,
|
|
190
|
+
};
|
|
191
|
+
evaluation.merge(self.eval_tracked_kind(
|
|
192
|
+
self.reference_target(*target, active),
|
|
193
|
+
value,
|
|
194
|
+
depth + 1,
|
|
195
|
+
scope::Context {
|
|
196
|
+
active: Some(&frame),
|
|
197
|
+
..active
|
|
198
|
+
},
|
|
199
|
+
kind,
|
|
200
|
+
))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
Op::All(nodes) => {
|
|
204
|
+
let mut result = Valid;
|
|
205
|
+
for &child in nodes {
|
|
206
|
+
result = evaluation.merge(self.eval_tracked_kind(
|
|
207
|
+
child,
|
|
208
|
+
value,
|
|
209
|
+
depth + 1,
|
|
210
|
+
active,
|
|
211
|
+
kind,
|
|
212
|
+
));
|
|
213
|
+
if result != Valid {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
result
|
|
218
|
+
}
|
|
219
|
+
Op::Any(nodes) | Op::One(nodes) => {
|
|
220
|
+
let mut matched = Tracked::new(Valid);
|
|
221
|
+
let mut count = 0;
|
|
222
|
+
let mut result = Valid;
|
|
223
|
+
for &(child, multiplicity) in nodes {
|
|
224
|
+
let branch = self.eval_tracked_kind(child, value, depth + 1, active, kind);
|
|
225
|
+
if branch.status == Unsupported {
|
|
226
|
+
result = Unsupported;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
if branch.status == Valid {
|
|
230
|
+
count += multiplicity;
|
|
231
|
+
matched.merge(branch);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if result != Valid {
|
|
235
|
+
result
|
|
236
|
+
} else if count == 0 || (matches!(op, Op::One(_)) && count != 1) {
|
|
237
|
+
Invalid
|
|
238
|
+
} else {
|
|
239
|
+
evaluation.merge(matched)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
Op::Not(child) => match self.eval(*child, value, depth + 1, active) {
|
|
243
|
+
Valid => Invalid,
|
|
244
|
+
Invalid => Valid,
|
|
245
|
+
Unsupported => Unsupported,
|
|
246
|
+
},
|
|
247
|
+
Op::Conditional(condition, yes, no) => {
|
|
248
|
+
let condition =
|
|
249
|
+
self.eval_tracked_kind(*condition, value, depth + 1, active, kind);
|
|
250
|
+
let status = condition.status;
|
|
251
|
+
if status == Unsupported {
|
|
252
|
+
Unsupported
|
|
253
|
+
} else {
|
|
254
|
+
let branch = if status == Valid {
|
|
255
|
+
evaluation.merge(condition);
|
|
256
|
+
yes
|
|
257
|
+
} else {
|
|
258
|
+
no
|
|
259
|
+
};
|
|
260
|
+
if let Some(child) = branch {
|
|
261
|
+
evaluation.merge(self.eval_tracked_kind(
|
|
262
|
+
*child,
|
|
263
|
+
value,
|
|
264
|
+
depth + 1,
|
|
265
|
+
active,
|
|
266
|
+
kind,
|
|
267
|
+
))
|
|
268
|
+
} else {
|
|
269
|
+
Valid
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
Op::Array(required, rules) => {
|
|
274
|
+
if *required != 0 && kind != TYPE_ARRAY {
|
|
275
|
+
Invalid
|
|
276
|
+
} else if kind != TYPE_ARRAY {
|
|
277
|
+
Valid
|
|
278
|
+
} else {
|
|
279
|
+
self.array_tracked(rules, value, depth + 1, active, &mut evaluation)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
Op::Object(required, rules) => {
|
|
283
|
+
if *required != 0 && kind != TYPE_OBJECT {
|
|
284
|
+
Invalid
|
|
285
|
+
} else if kind != TYPE_OBJECT {
|
|
286
|
+
Valid
|
|
287
|
+
} else {
|
|
288
|
+
self.object_tracked(rules, value, depth + 1, active, &mut evaluation)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
_ => self.leaf(op, kind, value, depth),
|
|
292
|
+
};
|
|
293
|
+
if result != Valid {
|
|
294
|
+
return Tracked::new(result);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
evaluation
|
|
298
|
+
}
|
|
299
|
+
fn array_tracked(
|
|
300
|
+
&self,
|
|
301
|
+
r: &Array,
|
|
302
|
+
value: Value,
|
|
303
|
+
depth: usize,
|
|
304
|
+
active: scope::Context<'_>,
|
|
305
|
+
evaluation: &mut Tracked,
|
|
306
|
+
) -> Verdict {
|
|
307
|
+
let len = unsafe { sn_len(value) };
|
|
308
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
309
|
+
return Invalid;
|
|
310
|
+
}
|
|
311
|
+
if r.unique {
|
|
312
|
+
let result = unique(value, depth, self.fixnum_flag);
|
|
313
|
+
if result != Valid {
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (index, &child) in r.prefix.iter().take(len).enumerate() {
|
|
318
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
319
|
+
if result != Valid {
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
evaluation.item(index);
|
|
323
|
+
}
|
|
324
|
+
for index in 0..len {
|
|
325
|
+
let child = if r.list {
|
|
326
|
+
r.items.get(index).copied().or(r.additional)
|
|
327
|
+
} else if index >= r.prefix.len() {
|
|
328
|
+
r.items.first().copied()
|
|
329
|
+
} else {
|
|
330
|
+
None
|
|
331
|
+
};
|
|
332
|
+
if let Some(child) = child {
|
|
333
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
334
|
+
if result != Valid {
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
evaluation.item(index);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if let Some(child) = r.contains {
|
|
341
|
+
let mut count = 0;
|
|
342
|
+
for index in 0..len {
|
|
343
|
+
match self.eval(child, unsafe { sn_entry(value, index) }, depth, active) {
|
|
344
|
+
Valid => {
|
|
345
|
+
count += 1;
|
|
346
|
+
evaluation.item(index);
|
|
347
|
+
}
|
|
348
|
+
Invalid => {}
|
|
349
|
+
Unsupported => return Unsupported,
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if bounds(count, Some(r.min_contains), r.max_contains) == Invalid {
|
|
353
|
+
return Invalid;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if let Some(child) = r.unevaluated.filter(|_| evaluation.item_count() < len) {
|
|
357
|
+
if matches!(self.nodes[child].as_slice(), [Op::Boolean(false)]) {
|
|
358
|
+
return Invalid;
|
|
359
|
+
}
|
|
360
|
+
for index in 0..len {
|
|
361
|
+
if evaluation.contains_item(index) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
365
|
+
if result != Valid {
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
evaluation.item(index);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
Valid
|
|
372
|
+
}
|
|
373
|
+
fn object_tracked(
|
|
374
|
+
&self,
|
|
375
|
+
r: &Object,
|
|
376
|
+
value: Value,
|
|
377
|
+
depth: usize,
|
|
378
|
+
active: scope::Context<'_>,
|
|
379
|
+
evaluation: &mut Tracked,
|
|
380
|
+
) -> Verdict {
|
|
381
|
+
let len = unsafe { sn_len(value) };
|
|
382
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
383
|
+
return Invalid;
|
|
384
|
+
}
|
|
385
|
+
for &key in &r.required {
|
|
386
|
+
if missing(value, key, self.undefined) {
|
|
387
|
+
return Invalid;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
let mut found = 0;
|
|
391
|
+
for (index, &(key, child)) in r.properties.iter().enumerate() {
|
|
392
|
+
let item = unsafe { sn_lookup(value, key) };
|
|
393
|
+
if item == self.undefined {
|
|
394
|
+
if r.required_properties[index] {
|
|
395
|
+
return Invalid;
|
|
396
|
+
}
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
found += 1;
|
|
400
|
+
let result = self.record_value(child, item, depth, active);
|
|
401
|
+
if result != Valid {
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
evaluation.known_property(r.property_indices[index]);
|
|
405
|
+
}
|
|
406
|
+
if r.names.is_some() || !r.patterns.is_empty() || (r.additional.is_some() && found < len) {
|
|
407
|
+
let result = self.tracked_object_walk(r, value, depth, active, evaluation, false);
|
|
408
|
+
if result != Valid {
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (name, required) in &r.dependent_required {
|
|
413
|
+
if !missing(value, *name, self.undefined) {
|
|
414
|
+
for &key in required {
|
|
415
|
+
if missing(value, key, self.undefined) {
|
|
416
|
+
return Invalid;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
for &(key, child) in &r.dependent_schemas {
|
|
422
|
+
if !missing(value, key, self.undefined) {
|
|
423
|
+
let result = evaluation.merge(self.eval_tracked(child, value, depth, active));
|
|
424
|
+
if result != Valid {
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if let Some(child) = r.unevaluated.filter(|_| evaluation.property_count() < len) {
|
|
430
|
+
if matches!(self.nodes[child].as_slice(), [Op::Boolean(false)]) {
|
|
431
|
+
return Invalid;
|
|
432
|
+
}
|
|
433
|
+
let result = self.tracked_object_walk(r, value, depth, active, evaluation, true);
|
|
434
|
+
if result != Valid {
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
Valid
|
|
439
|
+
}
|
|
440
|
+
fn tracked_object_walk(
|
|
441
|
+
&self,
|
|
442
|
+
rules: &Object,
|
|
443
|
+
value: Value,
|
|
444
|
+
depth: usize,
|
|
445
|
+
active: scope::Context<'_>,
|
|
446
|
+
evaluation: &mut Tracked,
|
|
447
|
+
unevaluated: bool,
|
|
448
|
+
) -> Verdict {
|
|
449
|
+
let mut c = TrackingContext {
|
|
450
|
+
engine: self,
|
|
451
|
+
rules,
|
|
452
|
+
depth,
|
|
453
|
+
active,
|
|
454
|
+
evaluation,
|
|
455
|
+
unevaluated,
|
|
456
|
+
};
|
|
457
|
+
decode(unsafe {
|
|
458
|
+
sn_hash_each(
|
|
459
|
+
value,
|
|
460
|
+
(&mut c as *mut TrackingContext).cast(),
|
|
461
|
+
tracking_entry,
|
|
462
|
+
)
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
struct TrackingContext<'a> {
|
|
467
|
+
engine: &'a Engine,
|
|
468
|
+
rules: &'a Object,
|
|
469
|
+
depth: usize,
|
|
470
|
+
active: scope::Context<'a>,
|
|
471
|
+
evaluation: &'a mut Tracked,
|
|
472
|
+
unevaluated: bool,
|
|
473
|
+
}
|
|
474
|
+
unsafe extern "C" fn tracking_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
475
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
476
|
+
let c = unsafe { &mut *context.cast::<TrackingContext>() };
|
|
477
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
478
|
+
return Unsupported as i32;
|
|
479
|
+
}
|
|
480
|
+
let key_location = JsonKey::new(key);
|
|
481
|
+
let known = c.engine.property_ids.get(&key_location).copied();
|
|
482
|
+
let child = if c.unevaluated {
|
|
483
|
+
if known.map_or_else(
|
|
484
|
+
|| c.evaluation.properties.contains(&key_location),
|
|
485
|
+
|index| c.evaluation.contains_known_property(index),
|
|
486
|
+
) {
|
|
487
|
+
return Valid as i32;
|
|
488
|
+
}
|
|
489
|
+
c.rules.unevaluated
|
|
490
|
+
} else {
|
|
491
|
+
if let Some(child) = c.rules.names {
|
|
492
|
+
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
493
|
+
if result != Valid {
|
|
494
|
+
return result as i32;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
let mut matched = c.rules.property_keys.contains(&key_location);
|
|
498
|
+
for (pattern, child) in &c.rules.patterns {
|
|
499
|
+
match pattern.eval(key) {
|
|
500
|
+
Unsupported => return Unsupported as i32,
|
|
501
|
+
Invalid => {}
|
|
502
|
+
Valid => {
|
|
503
|
+
matched = true;
|
|
504
|
+
let result = c.engine.eval(*child, value, c.depth, c.active);
|
|
505
|
+
if result != Valid {
|
|
506
|
+
return result as i32;
|
|
507
|
+
}
|
|
508
|
+
if let Some(index) = known {
|
|
509
|
+
c.evaluation.known_property(index);
|
|
510
|
+
} else {
|
|
511
|
+
c.evaluation.properties.insert(key_location);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if matched { None } else { c.rules.additional }
|
|
517
|
+
};
|
|
518
|
+
if let Some(child) = child {
|
|
519
|
+
let result = c.engine.eval(child, value, c.depth, c.active);
|
|
520
|
+
if result != Valid {
|
|
521
|
+
return result as i32;
|
|
522
|
+
}
|
|
523
|
+
if let Some(index) = known {
|
|
524
|
+
c.evaluation.known_property(index);
|
|
525
|
+
} else {
|
|
526
|
+
c.evaluation.properties.insert(key_location);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
Valid as i32
|
|
530
|
+
}))
|
|
531
|
+
.unwrap_or(Unsupported as i32)
|
|
532
|
+
}
|
data/lib/schemurai/backend.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Schemurai
|
|
4
4
|
module Backend
|
|
5
5
|
VM_FEATURE = "vm/evaluator"
|
|
6
|
-
CHOICES = %i[default ruby vm].freeze
|
|
6
|
+
CHOICES = %i[default ruby vm native].freeze
|
|
7
7
|
|
|
8
8
|
module_function def requested
|
|
9
9
|
value = ENV.fetch("SCHEMURAI_BACKEND", "default").to_sym
|
|
@@ -18,6 +18,7 @@ module Schemurai
|
|
|
18
18
|
|
|
19
19
|
selection = production_default if selection == :default
|
|
20
20
|
load_vm! if selection == :vm
|
|
21
|
+
require_relative "native/evaluator" if selection == :native
|
|
21
22
|
selection
|
|
22
23
|
end
|
|
23
24
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Schemurai
|
|
4
|
+
module Native
|
|
5
|
+
# Render native diagnostic output using the messages shared by every backend.
|
|
6
|
+
# The instance remains an original Ruby object; this layer never traverses it.
|
|
7
|
+
module Diagnostics
|
|
8
|
+
TYPE_NAMES = {Protocol::OP_TYPE_NULL => "null", Protocol::OP_TYPE_BOOLEAN => "boolean", Protocol::OP_TYPE_OBJECT => "object",
|
|
9
|
+
Protocol::OP_TYPE_ARRAY => "array", Protocol::OP_TYPE_NUMBER => "number", Protocol::OP_TYPE_INTEGER => "integer",
|
|
10
|
+
Protocol::OP_TYPE_STRING => "string", Protocol::OP_TYPED_NUMBER => "number", Protocol::OP_TYPED_INTEGER => "integer",
|
|
11
|
+
Protocol::OP_TYPED_STRING => "string", Protocol::OP_TYPED_ARRAY => "array", Protocol::OP_TYPED_OBJECT => "object"}.freeze
|
|
12
|
+
ATTRIBUTES = {"maximum" => :maximum, "minimum" => :minimum, "exclusiveMaximum" => :exclusive_maximum, "exclusiveMinimum" => :exclusive_minimum,
|
|
13
|
+
"maxLength" => :max_length, "minLength" => :min_length, "maxItems" => :max_items, "minItems" => :min_items,
|
|
14
|
+
"maxProperties" => :max_properties, "minProperties" => :min_properties}.freeze
|
|
15
|
+
private_constant :TYPE_NAMES, :ATTRIBUTES
|
|
16
|
+
|
|
17
|
+
module_function def error(row)
|
|
18
|
+
keyword, instance_path, schema_path, opcode, operand, instance, detail = row
|
|
19
|
+
ValidationError.new(keyword: keyword, instance_path: instance_path, schema_path: schema_path,
|
|
20
|
+
message: message(keyword, opcode, operand, instance, detail))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module_function def message(keyword, opcode, operand, instance, detail) # rubocop:disable Metrics/MethodLength
|
|
24
|
+
messages = Internal::ErrorMessage
|
|
25
|
+
case keyword
|
|
26
|
+
when "falseSchema" then messages.false_schema
|
|
27
|
+
when "type" then messages.type((opcode == Protocol::OP_TYPES) ? operand.names : TYPE_NAMES.fetch(opcode), instance)
|
|
28
|
+
when "const" then messages.const
|
|
29
|
+
when "enum" then messages.enum
|
|
30
|
+
when "maximum", "minimum", "exclusiveMaximum", "exclusiveMinimum"
|
|
31
|
+
messages.numeric_limit(keyword, operand.public_send(ATTRIBUTES.fetch(keyword)))
|
|
32
|
+
when "multipleOf" then messages.multiple_of(operand.multiple_of)
|
|
33
|
+
when "maxLength", "minLength", "maxItems", "minItems", "maxProperties", "minProperties"
|
|
34
|
+
messages.size(keyword, operand.public_send(ATTRIBUTES.fetch(keyword)), detail)
|
|
35
|
+
when "pattern" then messages.pattern(operand.pattern)
|
|
36
|
+
when "format" then messages.format(operand.format.name)
|
|
37
|
+
when "contentEncoding" then messages.content_encoding
|
|
38
|
+
when "contentMediaType" then messages.content_media_type
|
|
39
|
+
when "uniqueItems" then messages.unique_items
|
|
40
|
+
when "required" then messages.required(instance)
|
|
41
|
+
when "dependencies", "dependentRequired" then messages.dependent_required(operand, instance)
|
|
42
|
+
when "contains" then messages.contains(detail, operand.min_contains, operand.max_contains)
|
|
43
|
+
when "anyOf" then messages.any_of
|
|
44
|
+
when "oneOf" then messages.one_of(detail)
|
|
45
|
+
when "not" then messages.not
|
|
46
|
+
else raise "unknown native diagnostic #{keyword.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_class_method :message
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../vm/evaluator"
|
|
4
|
+
require "schemurai_native"
|
|
5
|
+
require_relative "diagnostics"
|
|
6
|
+
|
|
7
|
+
module Schemurai
|
|
8
|
+
module Native
|
|
9
|
+
class Evaluator
|
|
10
|
+
def backend = :native
|
|
11
|
+
|
|
12
|
+
def initialize(graph, compiler, root, content: false, format: false)
|
|
13
|
+
@program = Program.new(root, compiler, format, content)
|
|
14
|
+
@vm = VM::Evaluator.new(graph, compiler, root, content: content, format: format)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def valid?(instance)
|
|
18
|
+
result = @program.valid?(instance)
|
|
19
|
+
result.nil? ? @vm.valid?(instance) : result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validate(instance)
|
|
23
|
+
rows = @program.diagnostics(instance)
|
|
24
|
+
return @vm.validate(instance) if rows.nil?
|
|
25
|
+
|
|
26
|
+
Result.new(rows.empty? ? rows : rows.map { |row| Diagnostics.error(row) })
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private_constant :Native
|
|
32
|
+
end
|
data/lib/schemurai/version.rb
CHANGED