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,240 @@
|
|
|
1
|
+
//! Exact reachable scope dependencies, shared across schema components.
|
|
2
|
+
use super::*;
|
|
3
|
+
use std::borrow::Cow;
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
const MAX_NAMES: usize = 65536;
|
|
8
|
+
|
|
9
|
+
#[derive(Default)]
|
|
10
|
+
pub(super) struct Projection {
|
|
11
|
+
names: Vec<Option<Arc<[usize]>>>,
|
|
12
|
+
// If materialized closures exceed the budget, retain the linear dependency
|
|
13
|
+
// graph and compute those exact projections on demand, never all names.
|
|
14
|
+
graph: Option<Vec<Vec<usize>>>,
|
|
15
|
+
}
|
|
16
|
+
impl Projection {
|
|
17
|
+
pub(super) fn compile(edges: &[Vec<usize>], parents: &[Vec<usize>], count: usize) -> Self {
|
|
18
|
+
Self::bounded(edges, parents, count, MAX_NAMES)
|
|
19
|
+
}
|
|
20
|
+
fn bounded(edges: &[Vec<usize>], parents: &[Vec<usize>], count: usize, limit: usize) -> Self {
|
|
21
|
+
let mut seen = vec![false; edges.len()];
|
|
22
|
+
let order = graph::postorder(edges, &mut seen);
|
|
23
|
+
let mut component = vec![usize::MAX; edges.len()];
|
|
24
|
+
let mut members = vec![];
|
|
25
|
+
for root in order.into_iter().rev() {
|
|
26
|
+
if component[root] != usize::MAX {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let id = members.len();
|
|
30
|
+
let mut nodes = vec![];
|
|
31
|
+
let mut stack = vec![root];
|
|
32
|
+
component[root] = id;
|
|
33
|
+
while let Some(node) = stack.pop() {
|
|
34
|
+
nodes.push(node);
|
|
35
|
+
for &parent in &parents[node] {
|
|
36
|
+
if component[parent] == usize::MAX {
|
|
37
|
+
component[parent] = id;
|
|
38
|
+
stack.push(parent);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
members.push(nodes);
|
|
43
|
+
}
|
|
44
|
+
// Kosaraju components are ordered from sources to sinks. Every member
|
|
45
|
+
// of a cycle has the same reachable reference names; solve it once.
|
|
46
|
+
let mut closures: Vec<Option<Arc<[usize]>>> = vec![None; members.len()];
|
|
47
|
+
let mut interned: HashSet<Arc<[usize]>> = HashSet::new();
|
|
48
|
+
let mut slots = 0;
|
|
49
|
+
for (id, nodes) in members.iter().enumerate().rev() {
|
|
50
|
+
let mut names = vec![];
|
|
51
|
+
let mut children = HashSet::new();
|
|
52
|
+
let mut dependencies = HashSet::new();
|
|
53
|
+
let mut complete = true;
|
|
54
|
+
for &node in nodes {
|
|
55
|
+
if node >= count {
|
|
56
|
+
names.push(node - count);
|
|
57
|
+
}
|
|
58
|
+
for &child in &edges[node] {
|
|
59
|
+
let child = component[child];
|
|
60
|
+
if child != id && children.insert(child) {
|
|
61
|
+
match &closures[child] {
|
|
62
|
+
Some(dependency) => {
|
|
63
|
+
if dependencies.insert(dependency.as_ptr()) {
|
|
64
|
+
names.extend(dependency.iter().copied());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
None => complete = false,
|
|
68
|
+
}
|
|
69
|
+
if names.len() > limit {
|
|
70
|
+
names.sort_unstable();
|
|
71
|
+
names.dedup();
|
|
72
|
+
if names.len() > limit {
|
|
73
|
+
complete = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Bound temporary concatenation too. Deduplicate overlapping
|
|
80
|
+
// dependencies before choosing the on-demand representation.
|
|
81
|
+
if names.len() > limit {
|
|
82
|
+
names.sort_unstable();
|
|
83
|
+
names.dedup();
|
|
84
|
+
if names.len() > limit {
|
|
85
|
+
complete = false;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if !complete {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
names.sort_unstable();
|
|
94
|
+
names.dedup();
|
|
95
|
+
if let Some(existing) = interned.get(names.as_slice()) {
|
|
96
|
+
closures[id] = Some(existing.clone());
|
|
97
|
+
} else if names.len() <= limit - slots {
|
|
98
|
+
slots += names.len();
|
|
99
|
+
let names: Arc<[usize]> = names.into();
|
|
100
|
+
interned.insert(names.clone());
|
|
101
|
+
closures[id] = Some(names);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let names: Vec<_> = component[..count]
|
|
105
|
+
.iter()
|
|
106
|
+
.map(|&id| closures[id].clone())
|
|
107
|
+
.collect();
|
|
108
|
+
let graph = names.iter().any(Option::is_none).then(|| edges.to_vec());
|
|
109
|
+
Self { names, graph }
|
|
110
|
+
}
|
|
111
|
+
pub(super) fn names(
|
|
112
|
+
&self,
|
|
113
|
+
node: usize,
|
|
114
|
+
count: usize,
|
|
115
|
+
anchors: usize,
|
|
116
|
+
guarded: bool,
|
|
117
|
+
budget: &budget::Budget,
|
|
118
|
+
) -> Option<Cow<'_, [usize]>> {
|
|
119
|
+
if self.names.is_empty() {
|
|
120
|
+
// Hand-built engines in key tests have no compiled dependency graph.
|
|
121
|
+
return Some(Cow::Owned((0..anchors + usize::from(guarded)).collect()));
|
|
122
|
+
}
|
|
123
|
+
if let Some(names) = &self.names[node] {
|
|
124
|
+
return Some(Cow::Borrowed(names));
|
|
125
|
+
}
|
|
126
|
+
let graph = self.graph.as_ref()?;
|
|
127
|
+
let mut seen = vec![false; graph.len()];
|
|
128
|
+
let mut stack = vec![node];
|
|
129
|
+
let mut names = vec![];
|
|
130
|
+
seen[node] = true;
|
|
131
|
+
while let Some(node) = stack.pop() {
|
|
132
|
+
if !budget.checkpoint() {
|
|
133
|
+
return None;
|
|
134
|
+
}
|
|
135
|
+
if node >= count {
|
|
136
|
+
names.push(node - count);
|
|
137
|
+
}
|
|
138
|
+
for &child in &graph[node] {
|
|
139
|
+
if !seen[child] {
|
|
140
|
+
seen[child] = true;
|
|
141
|
+
stack.push(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
names.sort_unstable();
|
|
146
|
+
Some(Cow::Owned(names))
|
|
147
|
+
}
|
|
148
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
149
|
+
let mut seen = HashSet::new();
|
|
150
|
+
self.names.capacity() * std::mem::size_of::<Option<Arc<[usize]>>>()
|
|
151
|
+
+ self
|
|
152
|
+
.names
|
|
153
|
+
.iter()
|
|
154
|
+
.flatten()
|
|
155
|
+
.filter(|names| seen.insert(names.as_ptr()))
|
|
156
|
+
.map(|names| (names.len() + 2) * std::mem::size_of::<usize>())
|
|
157
|
+
.sum::<usize>()
|
|
158
|
+
+ self.graph.as_ref().map_or(0, |graph| {
|
|
159
|
+
graph.capacity() * std::mem::size_of::<Vec<usize>>()
|
|
160
|
+
+ graph
|
|
161
|
+
.iter()
|
|
162
|
+
.map(|row| row.capacity() * std::mem::size_of::<usize>())
|
|
163
|
+
.sum::<usize>()
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
mod tests {
|
|
170
|
+
use super::*;
|
|
171
|
+
|
|
172
|
+
fn parents(edges: &[Vec<usize>]) -> Vec<Vec<usize>> {
|
|
173
|
+
let mut parents = vec![vec![]; edges.len()];
|
|
174
|
+
for (node, children) in edges.iter().enumerate() {
|
|
175
|
+
for &child in children {
|
|
176
|
+
parents[child].push(node);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
parents
|
|
180
|
+
}
|
|
181
|
+
#[test]
|
|
182
|
+
fn projects_cycles_and_transitive_dynamic_dispatch_targets() {
|
|
183
|
+
// Four nodes, two dynamic names and the recursive dispatch vertex.
|
|
184
|
+
let edges = vec![
|
|
185
|
+
vec![1],
|
|
186
|
+
vec![0, 4],
|
|
187
|
+
vec![5],
|
|
188
|
+
vec![],
|
|
189
|
+
vec![2],
|
|
190
|
+
vec![3],
|
|
191
|
+
vec![0],
|
|
192
|
+
];
|
|
193
|
+
for limit in [0, 1, MAX_NAMES] {
|
|
194
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, limit);
|
|
195
|
+
let names = |node| {
|
|
196
|
+
projection
|
|
197
|
+
.names(node, 4, 2, true, &budget::Budget::default())
|
|
198
|
+
.unwrap()
|
|
199
|
+
.into_owned()
|
|
200
|
+
};
|
|
201
|
+
assert_eq!(names(0), [0, 1]);
|
|
202
|
+
assert_eq!(names(1), [0, 1]);
|
|
203
|
+
assert_eq!(names(2), [1]);
|
|
204
|
+
assert!(names(3).is_empty());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
#[test]
|
|
208
|
+
fn retains_recursive_dispatch_only_where_reachable() {
|
|
209
|
+
let edges = vec![vec![1], vec![4], vec![3], vec![], vec![0]];
|
|
210
|
+
let projection = Projection::compile(&edges, &parents(&edges), 3);
|
|
211
|
+
let names = |node| {
|
|
212
|
+
projection
|
|
213
|
+
.names(node, 3, 1, true, &budget::Budget::default())
|
|
214
|
+
.unwrap()
|
|
215
|
+
.into_owned()
|
|
216
|
+
};
|
|
217
|
+
assert_eq!(names(0), [1]);
|
|
218
|
+
assert_eq!(names(1), [1]);
|
|
219
|
+
assert_eq!(names(2), [0]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn shares_equal_closures_and_retains_exact_projection_after_budget_exhaustion() {
|
|
224
|
+
let edges = vec![vec![1, 2], vec![3], vec![3], vec![4], vec![], vec![]];
|
|
225
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, MAX_NAMES);
|
|
226
|
+
assert!(projection.graph.is_none());
|
|
227
|
+
assert!(Arc::ptr_eq(
|
|
228
|
+
projection.names[0].as_ref().unwrap(),
|
|
229
|
+
projection.names[3].as_ref().unwrap()
|
|
230
|
+
));
|
|
231
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, 0);
|
|
232
|
+
assert!(projection.graph.is_some());
|
|
233
|
+
assert_eq!(
|
|
234
|
+
&*projection
|
|
235
|
+
.names(0, 4, 1, false, &budget::Budget::default())
|
|
236
|
+
.unwrap(),
|
|
237
|
+
&[0]
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|