schemurai 3.0.0 → 3.0.2
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 +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +74 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +25 -2
|
@@ -123,14 +123,16 @@ pub(super) struct Memo {
|
|
|
123
123
|
impl Memo {
|
|
124
124
|
pub(super) fn key(
|
|
125
125
|
&self,
|
|
126
|
-
engine: &
|
|
126
|
+
engine: &Execution<'_>,
|
|
127
127
|
node: usize,
|
|
128
128
|
value: Value,
|
|
129
129
|
depth: usize,
|
|
130
130
|
context: scope::Context<'_>,
|
|
131
131
|
budget: &budget::Budget,
|
|
132
132
|
) -> Option<Key> {
|
|
133
|
-
|
|
133
|
+
// Dynamic Ruby predicates may be stateful. Replayed observations already
|
|
134
|
+
// preserve reached calls; result memoization must not skip later calls.
|
|
135
|
+
if engine.caches.dynamic_calls() || !engine.memoizable[node] {
|
|
134
136
|
return None;
|
|
135
137
|
}
|
|
136
138
|
let mut object = false;
|
|
@@ -968,29 +970,9 @@ impl Engine {
|
|
|
968
970
|
// Cyclic candidates can reuse results only under an exact guard/scope
|
|
969
971
|
// context. Propagate unsupported instructions and nested iteration
|
|
970
972
|
// through reverse edges without a per-candidate graph walk.
|
|
971
|
-
let
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
parents[child].push(parent);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
fn propagate(flags: &mut [bool], parents: &[Vec<usize>]) {
|
|
978
|
-
let mut queue: std::collections::VecDeque<_> = flags
|
|
979
|
-
.iter()
|
|
980
|
-
.enumerate()
|
|
981
|
-
.filter_map(|(node, &set)| set.then_some(node))
|
|
982
|
-
.collect();
|
|
983
|
-
while let Some(node) = queue.pop_front() {
|
|
984
|
-
for &parent in &parents[node] {
|
|
985
|
-
if !flags[parent] {
|
|
986
|
-
flags[parent] = true;
|
|
987
|
-
queue.push_back(parent);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
propagate(&mut unsupported, &parents);
|
|
993
|
-
propagate(&mut repeated_subtree, &parents);
|
|
973
|
+
let parents = graph::parents(&edges);
|
|
974
|
+
graph::propagate(&mut unsupported, &parents);
|
|
975
|
+
graph::propagate(&mut repeated_subtree, &parents);
|
|
994
976
|
// Iterative Kosaraju analysis identifies the only active sources
|
|
995
977
|
// whose guards can affect descendants: sources lying on a schema cycle.
|
|
996
978
|
fn cycle_nodes(edges: &[Vec<usize>], parents: &[Vec<usize>]) -> Vec<bool> {
|
|
@@ -1072,19 +1054,10 @@ impl Engine {
|
|
|
1072
1054
|
}
|
|
1073
1055
|
}
|
|
1074
1056
|
}
|
|
1075
|
-
|
|
1076
|
-
let mut parents = vec![vec![]; edges.len()];
|
|
1077
|
-
for (parent, children) in edges.iter().enumerate() {
|
|
1078
|
-
for &child in children {
|
|
1079
|
-
parents[child].push(parent);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
parents
|
|
1083
|
-
}
|
|
1084
|
-
let scalar_parents = reverse(&same_edges);
|
|
1057
|
+
let scalar_parents = graph::parents(&same_edges);
|
|
1085
1058
|
self.memo_guard_scalar_sources =
|
|
1086
1059
|
cycle_nodes(&same_edges, &scalar_parents)[..count].to_vec();
|
|
1087
|
-
propagate(&mut scalar_refs, &scalar_parents);
|
|
1060
|
+
graph::propagate(&mut scalar_refs, &scalar_parents);
|
|
1088
1061
|
let mut scalar_amplification = vec![false; edges.len()];
|
|
1089
1062
|
for (node, ops) in self.nodes.iter().enumerate() {
|
|
1090
1063
|
for op in ops {
|
|
@@ -1106,7 +1079,7 @@ impl Engine {
|
|
|
1106
1079
|
scalar_amplification[node] |= references > 1;
|
|
1107
1080
|
}
|
|
1108
1081
|
}
|
|
1109
|
-
propagate(&mut scalar_amplification, &scalar_parents);
|
|
1082
|
+
graph::propagate(&mut scalar_amplification, &scalar_parents);
|
|
1110
1083
|
self.memo_guard_scalar_amplification = scalar_amplification[..count].to_vec();
|
|
1111
1084
|
// Object dependencies keep the same value; all other container child
|
|
1112
1085
|
// edges change positions. Scalar/array inputs cannot take dependencies.
|
|
@@ -1119,7 +1092,7 @@ impl Engine {
|
|
|
1119
1092
|
}
|
|
1120
1093
|
}
|
|
1121
1094
|
self.memo_guard_same_sources =
|
|
1122
|
-
cycle_nodes(&same_edges, &
|
|
1095
|
+
cycle_nodes(&same_edges, &graph::parents(&same_edges))[..count].to_vec();
|
|
1123
1096
|
self.memo_guard_sources = cyclic[..count].to_vec();
|
|
1124
1097
|
self.memo_guarded = vec![false; count];
|
|
1125
1098
|
for node in 0..count {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//! Cold arithmetic for magnitudes outside the fixed-width scalar paths.
|
|
2
|
+
//! Only ordinary numeric scalar operands are read; no Ruby allocation or
|
|
3
|
+
//! method dispatch can invalidate the evaluator's borrowed instance storage.
|
|
4
|
+
use super::*;
|
|
5
|
+
use num_bigint::{BigInt, Sign};
|
|
6
|
+
use std::borrow::Cow;
|
|
7
|
+
|
|
8
|
+
fn integer(value: Value) -> BigInt {
|
|
9
|
+
if unsafe { sn_fixnum(value) } != 0 {
|
|
10
|
+
return BigInt::from(unsafe { sn_integer(value) });
|
|
11
|
+
}
|
|
12
|
+
let (words, sign) = unbounded_integer_words(value);
|
|
13
|
+
let digits = words
|
|
14
|
+
.into_iter()
|
|
15
|
+
.flat_map(|word| [word as u32, (word >> 32) as u32])
|
|
16
|
+
.collect();
|
|
17
|
+
BigInt::new(if sign < 0 { Sign::Minus } else { Sign::Plus }, digits)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub(super) fn fraction(numerator: Value, denominator: Option<Value>) -> BigRational {
|
|
21
|
+
BigRational::new(
|
|
22
|
+
integer(numerator),
|
|
23
|
+
denominator.map_or_else(|| BigInt::from(1), integer),
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn decimal(value: f64) -> Option<BigRational> {
|
|
28
|
+
let Decimal { coefficient, power } = decimal_parts(value)?;
|
|
29
|
+
let scale = BigInt::from(10).pow(power.unsigned_abs());
|
|
30
|
+
Some(if power >= 0 {
|
|
31
|
+
BigRational::from_integer(BigInt::from(coefficient) * scale)
|
|
32
|
+
} else {
|
|
33
|
+
BigRational::new(BigInt::from(coefficient), scale)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl Limit {
|
|
38
|
+
fn fraction(&self) -> Cow<'_, BigRational> {
|
|
39
|
+
if let Some(fraction) = &self.arbitrary {
|
|
40
|
+
Cow::Borrowed(fraction)
|
|
41
|
+
} else {
|
|
42
|
+
let fraction = self.exact.expect("compiled numeric fraction");
|
|
43
|
+
Cow::Owned(BigRational::new_raw(
|
|
44
|
+
BigInt::from(fraction.numerator),
|
|
45
|
+
BigInt::from(fraction.denominator),
|
|
46
|
+
))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub(super) fn arbitrary_memory_usage(&self) -> usize {
|
|
51
|
+
self.arbitrary.as_ref().map_or(0, |fraction| {
|
|
52
|
+
// num-bigint exposes used digits rather than allocation capacity.
|
|
53
|
+
std::mem::size_of::<BigRational>()
|
|
54
|
+
+ (fraction.numer().bits().div_ceil(64) + fraction.denom().bits().div_ceil(64))
|
|
55
|
+
as usize
|
|
56
|
+
* 8
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl Rules {
|
|
62
|
+
pub(super) fn arbitrary_check(
|
|
63
|
+
&self,
|
|
64
|
+
caches: &runtime::Caches,
|
|
65
|
+
value: Value,
|
|
66
|
+
budget: &budget::Budget,
|
|
67
|
+
mut failure: impl FnMut(i32) -> bool,
|
|
68
|
+
) -> Result<i32, Verdict> {
|
|
69
|
+
let float = unsafe { sn_is_float(value) } != 0;
|
|
70
|
+
let n = if float {
|
|
71
|
+
unsafe { sn_float(value) }
|
|
72
|
+
} else {
|
|
73
|
+
0.0
|
|
74
|
+
};
|
|
75
|
+
let actual = if float {
|
|
76
|
+
if self.multiple.is_some() {
|
|
77
|
+
if !n.is_finite() {
|
|
78
|
+
budget.failure.set(if n.is_nan() {
|
|
79
|
+
STATUS_DECIMAL_NAN
|
|
80
|
+
} else if n.is_sign_negative() {
|
|
81
|
+
STATUS_DECIMAL_NEGATIVE_INFINITY
|
|
82
|
+
} else {
|
|
83
|
+
STATUS_DECIMAL_POSITIVE_INFINITY
|
|
84
|
+
});
|
|
85
|
+
return Err(Unsupported);
|
|
86
|
+
}
|
|
87
|
+
Some(decimal(n).ok_or(Unsupported)?)
|
|
88
|
+
} else {
|
|
89
|
+
None
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
if unsafe { sn_kind(value) } & TYPE_INTEGER == 0 {
|
|
93
|
+
return Err(Unsupported);
|
|
94
|
+
}
|
|
95
|
+
Some(BigRational::from_integer(integer(value)))
|
|
96
|
+
};
|
|
97
|
+
// Without multipleOf, Ruby compares a Float to Integer bounds exactly
|
|
98
|
+
// in binary, but rounds Rational bounds to Float. With multipleOf,
|
|
99
|
+
// Ruby first converts the instance's decimal spelling to a Rational.
|
|
100
|
+
let binary = if actual.is_none() && self.limits.iter().any(|(_, limit)| limit.integer) {
|
|
101
|
+
BigRational::from_float(n)
|
|
102
|
+
} else {
|
|
103
|
+
None
|
|
104
|
+
};
|
|
105
|
+
let mut bits = 0;
|
|
106
|
+
for (bit, limit) in &self.limits {
|
|
107
|
+
if !budget.checkpoint() {
|
|
108
|
+
return Err(Unsupported);
|
|
109
|
+
}
|
|
110
|
+
let violates = limit.violation(caches, value, *bit, self.multiple.is_some(), || {
|
|
111
|
+
let order = if let Some(actual) = &actual {
|
|
112
|
+
Some(actual.cmp(limit.fraction().as_ref()))
|
|
113
|
+
} else if !limit.integer {
|
|
114
|
+
n.partial_cmp(&limit.rounded)
|
|
115
|
+
} else if let Some(binary) = &binary {
|
|
116
|
+
Some(binary.cmp(limit.fraction().as_ref()))
|
|
117
|
+
} else if n.is_nan() {
|
|
118
|
+
None
|
|
119
|
+
} else {
|
|
120
|
+
Some(if n.is_sign_negative() {
|
|
121
|
+
Ordering::Less
|
|
122
|
+
} else {
|
|
123
|
+
Ordering::Greater
|
|
124
|
+
})
|
|
125
|
+
};
|
|
126
|
+
violates(*bit, order)
|
|
127
|
+
})?;
|
|
128
|
+
if violates {
|
|
129
|
+
bits |= bit;
|
|
130
|
+
if failure(*bit) {
|
|
131
|
+
return Ok(bits);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if let Some(limit) = &self.multiple {
|
|
136
|
+
if !budget.checkpoint() {
|
|
137
|
+
return Err(Unsupported);
|
|
138
|
+
}
|
|
139
|
+
let violates = limit.violation(caches, value, MULTIPLE_OF, true, || {
|
|
140
|
+
let divisor = limit.fraction();
|
|
141
|
+
let actual = actual.as_ref().expect("multipleOf decimal operand");
|
|
142
|
+
let valid = divisor.numer().sign() == Sign::Plus
|
|
143
|
+
&& (actual.numer() % divisor.numer()).sign() == Sign::NoSign
|
|
144
|
+
&& (divisor.denom() % actual.denom()).sign() == Sign::NoSign;
|
|
145
|
+
!valid
|
|
146
|
+
})?;
|
|
147
|
+
if violates {
|
|
148
|
+
bits |= MULTIPLE_OF;
|
|
149
|
+
failure(MULTIPLE_OF);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
Ok(bits)
|
|
153
|
+
}
|
|
154
|
+
}
|