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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -7
  3. data/ext/schemurai_native/Cargo.lock +99 -0
  4. data/ext/schemurai_native/Cargo.toml +21 -0
  5. data/ext/schemurai_native/build.rs +35 -0
  6. data/ext/schemurai_native/extconf.rb +63 -0
  7. data/ext/schemurai_native/glue.c +780 -0
  8. data/ext/schemurai_native/protocol.def +63 -0
  9. data/ext/schemurai_native/protocol.h +10 -0
  10. data/ext/schemurai_native/src/budget.rs +101 -0
  11. data/ext/schemurai_native/src/content.rs +574 -0
  12. data/ext/schemurai_native/src/diagnostics.rs +1135 -0
  13. data/ext/schemurai_native/src/enumeration.rs +154 -0
  14. data/ext/schemurai_native/src/evaluation.rs +219 -0
  15. data/ext/schemurai_native/src/format.rs +357 -0
  16. data/ext/schemurai_native/src/graph.rs +50 -0
  17. data/ext/schemurai_native/src/instance_graph.rs +84 -0
  18. data/ext/schemurai_native/src/lib.rs +3139 -0
  19. data/ext/schemurai_native/src/memo.rs +1149 -0
  20. data/ext/schemurai_native/src/memo_scope.rs +240 -0
  21. data/ext/schemurai_native/src/number.rs +1054 -0
  22. data/ext/schemurai_native/src/pattern.rs +431 -0
  23. data/ext/schemurai_native/src/scope.rs +420 -0
  24. data/ext/schemurai_native/src/structural.rs +86 -0
  25. data/ext/schemurai_native/src/tracking.rs +687 -0
  26. data/lib/schemurai/backend.rb +3 -1
  27. data/lib/schemurai/evaluator.rb +45 -3
  28. data/lib/schemurai/limit_budget.rb +78 -0
  29. data/lib/schemurai/limit_exceeded.rb +18 -0
  30. data/lib/schemurai/limited_evaluator.rb +100 -0
  31. data/lib/schemurai/native/diagnostics.rb +52 -0
  32. data/lib/schemurai/native/evaluator.rb +59 -0
  33. data/lib/schemurai/schema_domain.rb +12 -7
  34. data/lib/schemurai/schema_graph.rb +10 -1
  35. data/lib/schemurai/unique_items.rb +2 -0
  36. data/lib/schemurai/version.rb +1 -1
  37. data/lib/schemurai/vm/compiler.rb +7 -3
  38. data/lib/schemurai/vm/evaluator.rb +50 -3
  39. data/lib/schemurai/vm/limited_evaluator.rb +97 -0
  40. data/lib/schemurai.rb +81 -33
  41. data/sig/schemurai.rbs +21 -13
  42. 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
+ }