schemurai 2.2.3 → 3.0.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 +81 -7
- 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 +780 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +1135 -0
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/format.rs +357 -0
- data/ext/schemurai_native/src/graph.rs +50 -0
- data/ext/schemurai_native/src/instance_graph.rs +84 -0
- data/ext/schemurai_native/src/lib.rs +3139 -0
- data/ext/schemurai_native/src/memo.rs +1149 -0
- data/ext/schemurai_native/src/memo_scope.rs +240 -0
- data/ext/schemurai_native/src/number.rs +1054 -0
- data/ext/schemurai_native/src/pattern.rs +431 -0
- data/ext/schemurai_native/src/scope.rs +420 -0
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +687 -0
- data/lib/schemurai/backend.rb +3 -1
- data/lib/schemurai/evaluator.rb +45 -3
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +100 -0
- data/lib/schemurai/native/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +59 -0
- data/lib/schemurai/schema_domain.rb +12 -7
- data/lib/schemurai/schema_graph.rb +10 -1
- data/lib/schemurai/unique_items.rb +2 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +7 -3
- data/lib/schemurai/vm/evaluator.rb +50 -3
- data/lib/schemurai/vm/limited_evaluator.rb +97 -0
- data/lib/schemurai.rb +81 -33
- data/sig/schemurai.rbs +21 -13
- metadata +36 -6
|
@@ -0,0 +1,687 @@
|
|
|
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
|
+
// Store only nonempty words. The common single-word case needs no allocation,
|
|
28
|
+
// even when its property IDs are near the end of a large compiled engine.
|
|
29
|
+
#[derive(Clone, Default)]
|
|
30
|
+
enum PropertyWords {
|
|
31
|
+
#[default]
|
|
32
|
+
Empty,
|
|
33
|
+
Single(usize, u64),
|
|
34
|
+
Dense(usize, Vec<u64>),
|
|
35
|
+
Many(HashMap<usize, u64>),
|
|
36
|
+
}
|
|
37
|
+
#[derive(Clone, Default)]
|
|
38
|
+
struct PropertyBits {
|
|
39
|
+
words: PropertyWords,
|
|
40
|
+
count: usize,
|
|
41
|
+
}
|
|
42
|
+
impl PropertyBits {
|
|
43
|
+
fn word(&self, index: usize) -> u64 {
|
|
44
|
+
match &self.words {
|
|
45
|
+
PropertyWords::Empty => 0,
|
|
46
|
+
PropertyWords::Single(at, bits) => {
|
|
47
|
+
if *at == index {
|
|
48
|
+
*bits
|
|
49
|
+
} else {
|
|
50
|
+
0
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
PropertyWords::Dense(first, words) => index
|
|
54
|
+
.checked_sub(*first)
|
|
55
|
+
.and_then(|offset| words.get(offset))
|
|
56
|
+
.copied()
|
|
57
|
+
.unwrap_or(0),
|
|
58
|
+
PropertyWords::Many(words) => words.get(&index).copied().unwrap_or(0),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
fn insert_word(&mut self, index: usize, bits: u64) {
|
|
62
|
+
let previous = self.word(index);
|
|
63
|
+
self.count += (bits & !previous).count_ones() as usize;
|
|
64
|
+
match &mut self.words {
|
|
65
|
+
PropertyWords::Empty => self.words = PropertyWords::Single(index, bits),
|
|
66
|
+
PropertyWords::Single(at, existing) if *at == index => *existing |= bits,
|
|
67
|
+
PropertyWords::Single(at, existing) if at.abs_diff(index) < 128 => {
|
|
68
|
+
let first = (*at).min(index);
|
|
69
|
+
let mut words = vec![0; at.abs_diff(index) + 1];
|
|
70
|
+
words[*at - first] = *existing;
|
|
71
|
+
words[index - first] = bits;
|
|
72
|
+
self.words = PropertyWords::Dense(first, words);
|
|
73
|
+
}
|
|
74
|
+
PropertyWords::Single(at, existing) => {
|
|
75
|
+
self.words = PropertyWords::Many(HashMap::from([(*at, *existing), (index, bits)]));
|
|
76
|
+
}
|
|
77
|
+
PropertyWords::Dense(first, words)
|
|
78
|
+
if index >= *first && index - *first < words.len() =>
|
|
79
|
+
{
|
|
80
|
+
words[index - *first] |= bits;
|
|
81
|
+
}
|
|
82
|
+
PropertyWords::Dense(first, words)
|
|
83
|
+
if index.max(*first + words.len() - 1) - index.min(*first) < 128 =>
|
|
84
|
+
{
|
|
85
|
+
if index < *first {
|
|
86
|
+
let offset = *first - index;
|
|
87
|
+
words.splice(0..0, std::iter::repeat_n(0, offset));
|
|
88
|
+
*first = index;
|
|
89
|
+
} else {
|
|
90
|
+
words.resize(index - *first + 1, 0);
|
|
91
|
+
}
|
|
92
|
+
words[index - *first] |= bits;
|
|
93
|
+
}
|
|
94
|
+
PropertyWords::Dense(first, words) => {
|
|
95
|
+
let mut sparse: HashMap<_, _> = words
|
|
96
|
+
.iter()
|
|
97
|
+
.enumerate()
|
|
98
|
+
.filter(|&(_, &word)| word != 0)
|
|
99
|
+
.map(|(offset, &word)| (*first + offset, word))
|
|
100
|
+
.collect();
|
|
101
|
+
sparse.insert(index, bits);
|
|
102
|
+
self.words = PropertyWords::Many(sparse);
|
|
103
|
+
}
|
|
104
|
+
PropertyWords::Many(words) => *words.entry(index).or_default() |= bits,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
fn insert(&mut self, index: usize) {
|
|
108
|
+
self.insert_word(index / 64, 1_u64 << (index % 64));
|
|
109
|
+
}
|
|
110
|
+
fn contains(&self, index: usize) -> bool {
|
|
111
|
+
self.word(index / 64) & (1_u64 << (index % 64)) != 0
|
|
112
|
+
}
|
|
113
|
+
fn merge(&mut self, other: Self) {
|
|
114
|
+
if matches!(self.words, PropertyWords::Empty) {
|
|
115
|
+
*self = other;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
match other.words {
|
|
119
|
+
PropertyWords::Empty => {}
|
|
120
|
+
PropertyWords::Single(index, bits) => self.insert_word(index, bits),
|
|
121
|
+
PropertyWords::Dense(first, words) => {
|
|
122
|
+
for (offset, bits) in words.into_iter().enumerate() {
|
|
123
|
+
if bits != 0 {
|
|
124
|
+
self.insert_word(first + offset, bits);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
PropertyWords::Many(words) => {
|
|
129
|
+
for (index, bits) in words {
|
|
130
|
+
self.insert_word(index, bits);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[derive(Clone)]
|
|
138
|
+
pub(super) struct Tracked {
|
|
139
|
+
pub status: Verdict,
|
|
140
|
+
properties: KeySet,
|
|
141
|
+
known_properties: PropertyBits,
|
|
142
|
+
items: Vec<u64>,
|
|
143
|
+
}
|
|
144
|
+
impl Tracked {
|
|
145
|
+
// Bound retained annotation slots independently of the number of results.
|
|
146
|
+
pub(super) fn memo_slots(&self) -> usize {
|
|
147
|
+
self.properties.capacity()
|
|
148
|
+
+ self.items.capacity()
|
|
149
|
+
+ match &self.known_properties.words {
|
|
150
|
+
PropertyWords::Empty | PropertyWords::Single(..) => 0,
|
|
151
|
+
PropertyWords::Dense(_, words) => words.capacity(),
|
|
152
|
+
PropertyWords::Many(words) => words.capacity(),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
pub(super) fn new(status: Verdict) -> Self {
|
|
156
|
+
Self {
|
|
157
|
+
status,
|
|
158
|
+
properties: KeySet::default(),
|
|
159
|
+
known_properties: PropertyBits::default(),
|
|
160
|
+
items: vec![],
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
pub(super) fn item(&mut self, index: usize) {
|
|
164
|
+
let word = index / 64;
|
|
165
|
+
if self.items.len() <= word {
|
|
166
|
+
self.items.resize(word + 1, 0);
|
|
167
|
+
}
|
|
168
|
+
self.items[word] |= 1_u64 << (index % 64);
|
|
169
|
+
}
|
|
170
|
+
pub(super) fn contains_item(&self, index: usize) -> bool {
|
|
171
|
+
self.items
|
|
172
|
+
.get(index / 64)
|
|
173
|
+
.is_some_and(|word| word & (1_u64 << (index % 64)) != 0)
|
|
174
|
+
}
|
|
175
|
+
fn known_property(&mut self, index: usize) {
|
|
176
|
+
self.known_properties.insert(index);
|
|
177
|
+
}
|
|
178
|
+
fn contains_known_property(&self, index: usize) -> bool {
|
|
179
|
+
self.known_properties.contains(index)
|
|
180
|
+
}
|
|
181
|
+
pub(super) fn property_count(&self) -> usize {
|
|
182
|
+
self.properties.len() + self.known_properties.count
|
|
183
|
+
}
|
|
184
|
+
pub(super) fn item_count(&self) -> usize {
|
|
185
|
+
self.items
|
|
186
|
+
.iter()
|
|
187
|
+
.map(|word| word.count_ones() as usize)
|
|
188
|
+
.sum()
|
|
189
|
+
}
|
|
190
|
+
pub(super) fn merge(&mut self, other: Self) -> Verdict {
|
|
191
|
+
if other.status != Valid {
|
|
192
|
+
return other.status;
|
|
193
|
+
}
|
|
194
|
+
if self.properties.is_empty() {
|
|
195
|
+
self.properties = other.properties;
|
|
196
|
+
} else {
|
|
197
|
+
self.properties.extend(other.properties);
|
|
198
|
+
}
|
|
199
|
+
self.known_properties.merge(other.known_properties);
|
|
200
|
+
merge_bits(&mut self.items, other.items);
|
|
201
|
+
Valid
|
|
202
|
+
}
|
|
203
|
+
pub(super) fn merge_diagnostics(&mut self, other: Self) -> Result<(), Verdict> {
|
|
204
|
+
if other.status == Unsupported {
|
|
205
|
+
return Err(Unsupported);
|
|
206
|
+
}
|
|
207
|
+
if self.status != Valid || other.status != Valid {
|
|
208
|
+
*self = Self::new(Invalid);
|
|
209
|
+
} else {
|
|
210
|
+
self.merge(other);
|
|
211
|
+
}
|
|
212
|
+
Ok(())
|
|
213
|
+
}
|
|
214
|
+
pub(super) fn property(&mut self, engine: &Engine, value: Value) {
|
|
215
|
+
let key = JsonKey::new(value);
|
|
216
|
+
if let Some(&index) = engine.property_ids.get(&key) {
|
|
217
|
+
self.known_property(index);
|
|
218
|
+
} else {
|
|
219
|
+
self.properties.insert(key);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
pub(super) fn contains_property(&self, engine: &Engine, value: Value) -> bool {
|
|
223
|
+
let key = JsonKey::new(value);
|
|
224
|
+
engine
|
|
225
|
+
.property_ids
|
|
226
|
+
.get(&key)
|
|
227
|
+
.is_some_and(|&index| self.contains_known_property(index))
|
|
228
|
+
|| self.properties.contains(&key)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
fn merge_bits(target: &mut Vec<u64>, source: Vec<u64>) {
|
|
232
|
+
// Branch annotations own native buffers of location bits. Transfer an
|
|
233
|
+
// existing buffer into an empty accumulator instead of allocating a copy.
|
|
234
|
+
if target.is_empty() {
|
|
235
|
+
*target = source;
|
|
236
|
+
} else {
|
|
237
|
+
if target.len() < source.len() {
|
|
238
|
+
target.resize(source.len(), 0);
|
|
239
|
+
}
|
|
240
|
+
for (target, source) in target.iter_mut().zip(source) {
|
|
241
|
+
*target |= source;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
impl Execution<'_> {
|
|
246
|
+
pub(super) fn eval_tracked(
|
|
247
|
+
&self,
|
|
248
|
+
node: usize,
|
|
249
|
+
value: Value,
|
|
250
|
+
depth: usize,
|
|
251
|
+
active: scope::Context<'_>,
|
|
252
|
+
) -> Tracked {
|
|
253
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
254
|
+
return Tracked::new(Unsupported);
|
|
255
|
+
}
|
|
256
|
+
if !self.budget.checkpoint() {
|
|
257
|
+
return Tracked::new(Unsupported);
|
|
258
|
+
}
|
|
259
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
260
|
+
TYPE_INTEGRAL_NUMBER
|
|
261
|
+
} else {
|
|
262
|
+
unsafe { sn_kind(value) }
|
|
263
|
+
};
|
|
264
|
+
self.eval_tracked_kind(node, value, depth, active, kind)
|
|
265
|
+
}
|
|
266
|
+
// Applicator branches evaluate the same borrowed instance under the GVL.
|
|
267
|
+
// No Ruby callback can mutate it, so its guarded kind can be reused while
|
|
268
|
+
// each branch still enters its own resource scope and checks depth.
|
|
269
|
+
pub(super) fn eval_tracked_kind(
|
|
270
|
+
&self,
|
|
271
|
+
node: usize,
|
|
272
|
+
value: Value,
|
|
273
|
+
depth: usize,
|
|
274
|
+
active: scope::Context<'_>,
|
|
275
|
+
kind: i32,
|
|
276
|
+
) -> Tracked {
|
|
277
|
+
if !self.budget.checkpoint() {
|
|
278
|
+
return Tracked::new(Unsupported);
|
|
279
|
+
}
|
|
280
|
+
let Some(key) = self
|
|
281
|
+
.memo
|
|
282
|
+
.key(self, node, value, depth, active, &self.budget)
|
|
283
|
+
else {
|
|
284
|
+
if self.budget.hit.get().0 != 0 {
|
|
285
|
+
return Tracked::new(Unsupported);
|
|
286
|
+
}
|
|
287
|
+
return self.eval_tracked_uncached(node, value, depth, active, kind);
|
|
288
|
+
};
|
|
289
|
+
if self.budget.hit.get().0 != 0 || !self.budget.checkpoint() {
|
|
290
|
+
return Tracked::new(Unsupported);
|
|
291
|
+
}
|
|
292
|
+
if let Some(result) = self.memo.tracked(key) {
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
let result = self.eval_tracked_uncached(node, value, depth, active, kind);
|
|
296
|
+
self.memo.record_tracked(key, &result);
|
|
297
|
+
result
|
|
298
|
+
}
|
|
299
|
+
fn eval_tracked_uncached(
|
|
300
|
+
&self,
|
|
301
|
+
node: usize,
|
|
302
|
+
value: Value,
|
|
303
|
+
depth: usize,
|
|
304
|
+
active: scope::Context<'_>,
|
|
305
|
+
kind: i32,
|
|
306
|
+
) -> Tracked {
|
|
307
|
+
let frame = scope::ScopeFrame {
|
|
308
|
+
resource: self.metadata[node].resource,
|
|
309
|
+
parent: active.scope,
|
|
310
|
+
primary_binding: active
|
|
311
|
+
.scope
|
|
312
|
+
.and_then(|frame| frame.primary_binding)
|
|
313
|
+
.or(self.metadata[node].primary_binding),
|
|
314
|
+
};
|
|
315
|
+
let active = self.enter_scope(node, active, &frame);
|
|
316
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
317
|
+
return Tracked::new(Unsupported);
|
|
318
|
+
}
|
|
319
|
+
if kind == TYPE_UNSUPPORTED {
|
|
320
|
+
return Tracked::new(Unsupported);
|
|
321
|
+
}
|
|
322
|
+
let mut evaluation = Tracked::new(Valid);
|
|
323
|
+
for op in &self.nodes[node] {
|
|
324
|
+
if !self.budget.checkpoint() {
|
|
325
|
+
return Tracked::new(Unsupported);
|
|
326
|
+
}
|
|
327
|
+
let result = match op {
|
|
328
|
+
Op::Ref(target) => {
|
|
329
|
+
if RefFrame::contains(active.active, node, value) {
|
|
330
|
+
Valid
|
|
331
|
+
} else {
|
|
332
|
+
let frame = RefFrame {
|
|
333
|
+
source: node,
|
|
334
|
+
value,
|
|
335
|
+
parent: active.active,
|
|
336
|
+
};
|
|
337
|
+
evaluation.merge(self.eval_tracked_kind(
|
|
338
|
+
self.reference_target(*target, active),
|
|
339
|
+
value,
|
|
340
|
+
depth + 1,
|
|
341
|
+
scope::Context {
|
|
342
|
+
active: Some(&frame),
|
|
343
|
+
..active
|
|
344
|
+
},
|
|
345
|
+
kind,
|
|
346
|
+
))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
Op::All(nodes) => {
|
|
350
|
+
let mut result = Valid;
|
|
351
|
+
for &child in nodes {
|
|
352
|
+
if !self.budget.checkpoint() {
|
|
353
|
+
return Tracked::new(Unsupported);
|
|
354
|
+
}
|
|
355
|
+
result = evaluation.merge(self.eval_tracked_kind(
|
|
356
|
+
child,
|
|
357
|
+
value,
|
|
358
|
+
depth + 1,
|
|
359
|
+
active,
|
|
360
|
+
kind,
|
|
361
|
+
));
|
|
362
|
+
if result != Valid {
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
result
|
|
367
|
+
}
|
|
368
|
+
Op::Any(nodes) | Op::One(nodes) => {
|
|
369
|
+
let branch_kind = if matches!(op, Op::Any(_)) {
|
|
370
|
+
evaluation::BranchKind::Any
|
|
371
|
+
} else {
|
|
372
|
+
evaluation::BranchKind::One
|
|
373
|
+
};
|
|
374
|
+
match evaluation::scan_branches(
|
|
375
|
+
nodes,
|
|
376
|
+
evaluation::BranchPolicy::Complete,
|
|
377
|
+
|child| self.eval_tracked_kind(child, value, depth + 1, active, kind),
|
|
378
|
+
) {
|
|
379
|
+
Err(status) => status,
|
|
380
|
+
Ok(result) if !branch_kind.accepts(result.matches) => Invalid,
|
|
381
|
+
Ok(result) => evaluation.merge(result.coverage),
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
Op::Not(child) => match self.eval(*child, value, depth + 1, active) {
|
|
385
|
+
Valid => Invalid,
|
|
386
|
+
Invalid => Valid,
|
|
387
|
+
Unsupported => Unsupported,
|
|
388
|
+
},
|
|
389
|
+
Op::Conditional(condition, yes, no) => {
|
|
390
|
+
let condition =
|
|
391
|
+
self.eval_tracked_kind(*condition, value, depth + 1, active, kind);
|
|
392
|
+
let status = condition.status;
|
|
393
|
+
if status == Unsupported {
|
|
394
|
+
Unsupported
|
|
395
|
+
} else {
|
|
396
|
+
let branch = if status == Valid {
|
|
397
|
+
evaluation.merge(condition);
|
|
398
|
+
yes
|
|
399
|
+
} else {
|
|
400
|
+
no
|
|
401
|
+
};
|
|
402
|
+
if let Some(child) = branch {
|
|
403
|
+
evaluation.merge(self.eval_tracked_kind(
|
|
404
|
+
*child,
|
|
405
|
+
value,
|
|
406
|
+
depth + 1,
|
|
407
|
+
active,
|
|
408
|
+
kind,
|
|
409
|
+
))
|
|
410
|
+
} else {
|
|
411
|
+
Valid
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
Op::Array(required, rules) => {
|
|
416
|
+
if *required != 0 && kind != TYPE_ARRAY {
|
|
417
|
+
Invalid
|
|
418
|
+
} else if kind != TYPE_ARRAY {
|
|
419
|
+
Valid
|
|
420
|
+
} else {
|
|
421
|
+
self.array_tracked(rules, value, depth + 1, active, &mut evaluation)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
Op::Object(required, rules) => {
|
|
425
|
+
if *required != 0 && kind != TYPE_OBJECT {
|
|
426
|
+
Invalid
|
|
427
|
+
} else if kind != TYPE_OBJECT {
|
|
428
|
+
Valid
|
|
429
|
+
} else {
|
|
430
|
+
self.object_tracked(rules, value, depth + 1, active, &mut evaluation)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
_ => self.leaf(op, kind, value, depth),
|
|
434
|
+
};
|
|
435
|
+
if result != Valid {
|
|
436
|
+
return Tracked::new(result);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
evaluation
|
|
440
|
+
}
|
|
441
|
+
fn array_tracked(
|
|
442
|
+
&self,
|
|
443
|
+
r: &Array,
|
|
444
|
+
value: Value,
|
|
445
|
+
depth: usize,
|
|
446
|
+
active: scope::Context<'_>,
|
|
447
|
+
evaluation: &mut Tracked,
|
|
448
|
+
) -> Verdict {
|
|
449
|
+
if !self.budget.checkpoint() {
|
|
450
|
+
return Unsupported;
|
|
451
|
+
}
|
|
452
|
+
let len = unsafe { sn_len(value) };
|
|
453
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
454
|
+
return Invalid;
|
|
455
|
+
}
|
|
456
|
+
if r.unique {
|
|
457
|
+
let result = unique(value, depth, self.fixnum_flag, &self.budget);
|
|
458
|
+
if result != Valid {
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (index, &child) in r.prefix.iter().take(len).enumerate() {
|
|
463
|
+
if !self.budget.checkpoint() {
|
|
464
|
+
return Unsupported;
|
|
465
|
+
}
|
|
466
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
467
|
+
if result != Valid {
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
evaluation.item(index);
|
|
471
|
+
}
|
|
472
|
+
for index in 0..len {
|
|
473
|
+
if !self.budget.checkpoint() {
|
|
474
|
+
return Unsupported;
|
|
475
|
+
}
|
|
476
|
+
let child = if r.list {
|
|
477
|
+
r.items.get(index).copied().or(r.additional)
|
|
478
|
+
} else if index >= r.prefix.len() {
|
|
479
|
+
r.items.first().copied()
|
|
480
|
+
} else {
|
|
481
|
+
None
|
|
482
|
+
};
|
|
483
|
+
if let Some(child) = child {
|
|
484
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
485
|
+
if result != Valid {
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
evaluation.item(index);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if let Some(child) = r.contains {
|
|
492
|
+
let count = match evaluation::scan_contains(
|
|
493
|
+
len,
|
|
494
|
+
|index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
|
|
495
|
+
|index| evaluation.item(index),
|
|
496
|
+
) {
|
|
497
|
+
Ok(count) => count,
|
|
498
|
+
Err(status) => return status,
|
|
499
|
+
};
|
|
500
|
+
if bounds(count, Some(r.min_contains), r.max_contains) == Invalid {
|
|
501
|
+
return Invalid;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if let Some(child) = r.unevaluated.filter(|_| evaluation.item_count() < len) {
|
|
505
|
+
if matches!(self.nodes[child].as_slice(), [Op::Boolean(false)]) {
|
|
506
|
+
return Invalid;
|
|
507
|
+
}
|
|
508
|
+
for index in 0..len {
|
|
509
|
+
if !self.budget.checkpoint() {
|
|
510
|
+
return Unsupported;
|
|
511
|
+
}
|
|
512
|
+
if evaluation.contains_item(index) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
let result = self.eval(child, unsafe { sn_entry(value, index) }, depth, active);
|
|
516
|
+
if result != Valid {
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
evaluation.item(index);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
Valid
|
|
523
|
+
}
|
|
524
|
+
fn object_tracked(
|
|
525
|
+
&self,
|
|
526
|
+
r: &Object,
|
|
527
|
+
value: Value,
|
|
528
|
+
depth: usize,
|
|
529
|
+
active: scope::Context<'_>,
|
|
530
|
+
evaluation: &mut Tracked,
|
|
531
|
+
) -> Verdict {
|
|
532
|
+
if !self.budget.checkpoint() {
|
|
533
|
+
return Unsupported;
|
|
534
|
+
}
|
|
535
|
+
let len = unsafe { sn_len(value) };
|
|
536
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
537
|
+
return Invalid;
|
|
538
|
+
}
|
|
539
|
+
let status = r.check_required(value, self.undefined, &self.budget);
|
|
540
|
+
if status != Valid {
|
|
541
|
+
return status;
|
|
542
|
+
}
|
|
543
|
+
let found = match r.visit_properties(
|
|
544
|
+
value,
|
|
545
|
+
len,
|
|
546
|
+
self.undefined,
|
|
547
|
+
&self.budget,
|
|
548
|
+
|index, child, item| {
|
|
549
|
+
let result = self.record_value(child, item, depth, active);
|
|
550
|
+
if result == Valid {
|
|
551
|
+
evaluation.known_property(r.property_indices[index]);
|
|
552
|
+
}
|
|
553
|
+
result
|
|
554
|
+
},
|
|
555
|
+
) {
|
|
556
|
+
Ok(found) => found,
|
|
557
|
+
Err(status) => return status,
|
|
558
|
+
};
|
|
559
|
+
if r.names.is_some() || !r.patterns.is_empty() || (r.additional.is_some() && found < len) {
|
|
560
|
+
let result = self.tracked_object_walk(r, value, depth, active, evaluation, false);
|
|
561
|
+
if result != Valid {
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
let status = r.check_dependent_required(value, self.undefined, &self.budget);
|
|
566
|
+
if status != Valid {
|
|
567
|
+
return status;
|
|
568
|
+
}
|
|
569
|
+
for &(key, child) in &r.dependent_schemas {
|
|
570
|
+
if !self.budget.checkpoint() {
|
|
571
|
+
return Unsupported;
|
|
572
|
+
}
|
|
573
|
+
if !missing(value, key, self.undefined) {
|
|
574
|
+
let result = evaluation.merge(self.eval_tracked(child, value, depth, active));
|
|
575
|
+
if result != Valid {
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if let Some(child) = r.unevaluated.filter(|_| evaluation.property_count() < len) {
|
|
581
|
+
if matches!(self.nodes[child].as_slice(), [Op::Boolean(false)]) {
|
|
582
|
+
return Invalid;
|
|
583
|
+
}
|
|
584
|
+
let result = self.tracked_object_walk(r, value, depth, active, evaluation, true);
|
|
585
|
+
if result != Valid {
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
Valid
|
|
590
|
+
}
|
|
591
|
+
fn tracked_object_walk(
|
|
592
|
+
&self,
|
|
593
|
+
rules: &Object,
|
|
594
|
+
value: Value,
|
|
595
|
+
depth: usize,
|
|
596
|
+
active: scope::Context<'_>,
|
|
597
|
+
evaluation: &mut Tracked,
|
|
598
|
+
unevaluated: bool,
|
|
599
|
+
) -> Verdict {
|
|
600
|
+
let mut c = TrackingContext {
|
|
601
|
+
engine: self,
|
|
602
|
+
rules,
|
|
603
|
+
depth,
|
|
604
|
+
active,
|
|
605
|
+
evaluation,
|
|
606
|
+
unevaluated,
|
|
607
|
+
};
|
|
608
|
+
decode(unsafe {
|
|
609
|
+
sn_hash_each(
|
|
610
|
+
value,
|
|
611
|
+
(&mut c as *mut TrackingContext).cast(),
|
|
612
|
+
tracking_entry,
|
|
613
|
+
)
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
struct TrackingContext<'a> {
|
|
618
|
+
engine: &'a Execution<'a>,
|
|
619
|
+
rules: &'a Object,
|
|
620
|
+
depth: usize,
|
|
621
|
+
active: scope::Context<'a>,
|
|
622
|
+
evaluation: &'a mut Tracked,
|
|
623
|
+
unevaluated: bool,
|
|
624
|
+
}
|
|
625
|
+
unsafe extern "C" fn tracking_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
626
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
627
|
+
let c = unsafe { &mut *context.cast::<TrackingContext>() };
|
|
628
|
+
if !c.engine.budget.checkpoint() {
|
|
629
|
+
return Unsupported as i32;
|
|
630
|
+
}
|
|
631
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
632
|
+
return Unsupported as i32;
|
|
633
|
+
}
|
|
634
|
+
let key_location = JsonKey::new(key);
|
|
635
|
+
let known = c.engine.property_ids.get(&key_location).copied();
|
|
636
|
+
let child = if c.unevaluated {
|
|
637
|
+
if known.map_or_else(
|
|
638
|
+
|| c.evaluation.properties.contains(&key_location),
|
|
639
|
+
|index| c.evaluation.contains_known_property(index),
|
|
640
|
+
) {
|
|
641
|
+
return Valid as i32;
|
|
642
|
+
}
|
|
643
|
+
c.rules.unevaluated
|
|
644
|
+
} else {
|
|
645
|
+
if let Some(child) = c.rules.names {
|
|
646
|
+
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
647
|
+
if result != Valid {
|
|
648
|
+
return result as i32;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
let result = c.rules.visit_value_rules::<false>(
|
|
652
|
+
c.engine.caches,
|
|
653
|
+
&c.engine.budget,
|
|
654
|
+
key,
|
|
655
|
+
Some(key_location),
|
|
656
|
+
|_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
|
|
657
|
+
Valid => {
|
|
658
|
+
if let Some(index) = known {
|
|
659
|
+
c.evaluation.known_property(index);
|
|
660
|
+
} else {
|
|
661
|
+
c.evaluation.properties.insert(key_location);
|
|
662
|
+
}
|
|
663
|
+
Ok(())
|
|
664
|
+
}
|
|
665
|
+
status => Err(status),
|
|
666
|
+
},
|
|
667
|
+
);
|
|
668
|
+
return match result {
|
|
669
|
+
Ok(_) => Valid as i32,
|
|
670
|
+
Err(status) => status as i32,
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
if let Some(child) = child {
|
|
674
|
+
let result = c.engine.eval(child, value, c.depth, c.active);
|
|
675
|
+
if result != Valid {
|
|
676
|
+
return result as i32;
|
|
677
|
+
}
|
|
678
|
+
if let Some(index) = known {
|
|
679
|
+
c.evaluation.known_property(index);
|
|
680
|
+
} else {
|
|
681
|
+
c.evaluation.properties.insert(key_location);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
Valid as i32
|
|
685
|
+
}))
|
|
686
|
+
.unwrap_or(Unsupported as i32)
|
|
687
|
+
}
|
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
|
|
|
@@ -27,6 +28,7 @@ module Schemurai
|
|
|
27
28
|
|
|
28
29
|
module_function def load_vm!
|
|
29
30
|
require_relative VM_FEATURE
|
|
31
|
+
require_relative "vm/limited_evaluator"
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|