adam_rs/template.rs
1//! Owning, source-free recipes for composition in one native [`Sheet`].
2//!
3//! Parameters alias caller-owned cells; private declarations allocate fresh cells
4//! in declaration order. Factories run at instantiation, never propagation.
5//! Each factory must create fresh execution state and satisfy [`Method`]'s contract.
6//!
7//! ```
8//! use adam_rs::{Method, MethodRecipe, Sheet, TemplateBuilder};
9//!
10//! let mut builder = TemplateBuilder::new();
11//! let input = builder.parameter::<i32>("input")?;
12//! let output = builder.parameter::<i32>("output")?;
13//! let method = MethodRecipe::new(&[input], &[output], |ins, outs| {
14//! Ok(Method::from_fn_1_1(ins[0], outs[0], |x: &i32| Ok(*x)))
15//! })?;
16//! builder.relationship(vec![method])?;
17//! let template = builder.finish()?;
18//! let mut sheet = Sheet::new();
19//! let input = sheet.add_source(7i32);
20//! let output = sheet.add_cell(0i32);
21//! let instance = template.instantiate(&mut sheet, &[("output", output), ("input", input)])?;
22//! sheet.propagate()?;
23//! assert_eq!(*sheet.read::<i32>(output)?, 7);
24//! assert_eq!(instance.cells(), &[input, output]);
25//! # Ok::<(), Box<dyn std::error::Error>>(())
26//! ```
27//!
28//! This path constructs native methods directly: it needs no CEL text, lexer,
29//! parser or operation lookup. For checked multiplication with a source-bound
30//! factor, run `cargo run -p adam-lang --example templates` in the workspace.
31//! That example's native path uses only this crate's recipe and sheet APIs.
32//!
33//! Named actuals must cover each parameter once, use distinct live cells from
34//! the destination sheet, and match the declared concrete types. Fixed sources
35//! specialize away every method that would write them. An empty specialized
36//! relationship fails before factories run. `out` cells cannot be actuals.
37//! Parameters keep caller-owned filters, requirements, strength and aspirations;
38//! component-owned policies may govern only private cells.
39//!
40//! Finished templates own their factories; each factory creates fresh execution
41//! state. Immutable captures may be shared. Live methods survive dropping both
42//! the template and its returned instance metadata. Instantiation runs factories
43//! but never propagates. On error, the destination remains safely discardable;
44//! instantiation does not promise rollback. [`TemplateError::Construction`]
45//! retains the actual native site and original cause without invented source spans.
46//! Conditional inclusion requires a transitively relationship-only component.
47//! Private state remains available to unconditional inclusions.
48
49mod builder;
50mod error;
51mod instantiate;
52mod provenance;
53
54use std::any::TypeId;
55use std::collections::{HashMap, HashSet};
56use std::rc::Rc;
57
58use crate::{CellId, CellKind, Method, RelationshipId, Sheet};
59
60pub use builder::TemplateBuilder;
61pub use error::{ConstructionSite, TemplateError};
62pub use provenance::{InstanceOccurrence, MethodOrigin, OccurrenceId, TemplateId};
63
64/// Identifies a cell declaration within exactly one owning builder/template.
65///
66/// Clones retain identity, not ownership of a live cell.
67///
68/// # Examples
69/// See the [module example](self).
70#[derive(Clone, Debug)]
71pub struct CellSlot {
72 owner: Rc<()>,
73 index: usize,
74}
75
76impl PartialEq for CellSlot {
77 /// Compares the owning recipe and local declaration index.
78 fn eq(&self, other: &Self) -> bool {
79 Rc::ptr_eq(&self.owner, &other.owner) && self.index == other.index
80 }
81}
82
83impl Eq for CellSlot {}
84
85/// Identifies a relationship declaration or inclusion group within one owning recipe.
86///
87/// A group expands to zero or more distinct live relationships, never a merged
88/// runtime relationship. Inclusion returns one group in the including namespace.
89///
90/// # Examples
91/// See [`TemplateBuilder::include`].
92#[derive(Clone, Debug)]
93pub struct RelationshipSlot {
94 owner: Rc<()>,
95 index: usize,
96}
97
98impl PartialEq for RelationshipSlot {
99 /// Compares the owning recipe and local relationship index.
100 fn eq(&self, other: &Self) -> bool {
101 Rc::ptr_eq(&self.owner, &other.owner) && self.index == other.index
102 }
103}
104
105impl Eq for RelationshipSlot {}
106
107/// Creates a fresh native method for the supplied positional edge handles.
108type MethodFactory = dyn Fn(&[CellId], &[CellId]) -> anyhow::Result<Method>;
109
110/// Stores declared local edges and a repeatable, owning native method factory.
111///
112/// Factories must create fresh execution state satisfying [`Method`]'s contract.
113/// Their returned method's positional edges and types must match the declarations.
114/// Immutable captured configuration may be shared.
115///
116/// # Examples
117/// See the [module example](self).
118pub struct MethodRecipe {
119 inputs: Vec<CellSlot>,
120 outputs: Vec<CellSlot>,
121 make: Box<MethodFactory>,
122}
123
124impl MethodRecipe {
125 /// Creates a recipe whose local edges all belong to one owner.
126 ///
127 /// # Errors
128 ///
129 /// Returns [`TemplateError::ForeignHandle`] for mixed owners, or
130 /// [`TemplateError::InvalidDefinition`] for empty or repeated outputs.
131 ///
132 /// - Complexity: O(i + o²) time and O(i + o) space for edge counts i and o.
133 ///
134 /// # Examples
135 ///
136 /// See the [module example](self).
137 pub fn new<F>(inputs: &[CellSlot], outputs: &[CellSlot], make: F) -> Result<Self, TemplateError>
138 where
139 F: Fn(&[CellId], &[CellId]) -> anyhow::Result<Method> + 'static,
140 {
141 let Some(first) = outputs.first() else {
142 return Err(TemplateError::InvalidDefinition {
143 detail: "a method requires an output",
144 slot: None,
145 });
146 };
147 for slot in inputs.iter().chain(outputs) {
148 if !Rc::ptr_eq(&first.owner, &slot.owner) {
149 return Err(TemplateError::ForeignHandle { slot: slot.clone() });
150 }
151 }
152 if outputs
153 .iter()
154 .enumerate()
155 .any(|(i, slot)| outputs[..i].contains(slot))
156 {
157 return Err(TemplateError::InvalidDefinition {
158 detail: "a method repeats an output",
159 slot: Some(first.clone()),
160 });
161 }
162 Ok(Self {
163 inputs: inputs.to_vec(),
164 outputs: outputs.to_vec(),
165 make: Box::new(make),
166 })
167 }
168}
169
170/// Adds a freshly initialized private cell through the ordinary sheet mutators.
171type CellFactory = dyn Fn(&mut Sheet) -> anyhow::Result<CellId>;
172
173/// Describes one typed local cell; parameters have no allocating factory.
174struct CellRecipe {
175 name: String,
176 type_id: TypeId,
177 kind: CellKind,
178 make: Option<Box<CellFactory>>,
179}
180
181/// Preserves inline declaration order independently of local handle numbering.
182enum Item {
183 Cell(usize),
184 Relationship(usize),
185 Policy(PolicyRecipe),
186 Conditional(ConditionalRecipe),
187}
188
189/// Registers one checked private-cell policy with fresh execution state.
190type PolicyFactory = dyn Fn(&mut Sheet, CellId, &[CellId]) -> Result<(), TemplateError>;
191
192/// Retains the private target and declared inputs of an ordered filter or requirement.
193struct PolicyRecipe {
194 cell: CellSlot,
195 inputs: Vec<CellSlot>,
196 make: Box<PolicyFactory>,
197}
198
199/// Registers a conditional using the expanded live members of its local groups.
200type ConditionalFactory = dyn Fn(
201 &mut Sheet,
202 &[CellId],
203 &[std::ops::Range<usize>],
204 &[RelationshipId],
205) -> Result<crate::ConditionalId, TemplateError>;
206
207/// Retains declared match inputs and a type-erased native conditional constructor.
208struct ConditionalRecipe {
209 inputs: Vec<CellSlot>,
210 make: Box<ConditionalFactory>,
211}
212
213/// Owns one relationship or a compact child-DAG edge with local formal bindings.
214enum RelationshipRecipe {
215 Methods(Vec<MethodRecipe>),
216 Out(MethodRecipe),
217 Include {
218 child: Rc<SheetTemplate>,
219 bindings: HashMap<usize, CellSlot>,
220 },
221}
222
223/// Owns an immutable ordered graph recipe, independent of any live sheet.
224///
225/// Parameters alias bindings; private cells and methods are fresh per instance.
226/// Destruction releases last-owned inclusion edges iteratively; shared children
227/// and their factories remain alive until their last strong owner releases them.
228///
229/// # Examples
230/// See the [module example](self).
231pub struct SheetTemplate {
232 owner: Rc<()>,
233 cells: Vec<CellRecipe>,
234 relationships: Vec<RelationshipRecipe>,
235 items: Vec<Item>,
236 /// Summarizes local outputs without enumerating transitive descendants.
237 written_cells: HashSet<usize>,
238 /// Caches the transitive relationship-only classification.
239 relationship_only: bool,
240 /// Memoizes checked expanded storage/work, with `None` denoting overflow.
241 expanded_size: Option<usize>,
242 /// Bounds the explicit traversal stack without traversing descendants.
243 depth: Option<usize>,
244}
245
246impl Drop for SheetTemplate {
247 /// Releases uniquely owned inclusion descendants without recursive destruction.
248 ///
249 /// Shared children retain their recipes unchanged. Detaching a uniquely owned
250 /// child's edges before dropping it bounds destructor call depth independently
251 /// of inclusion nesting.
252 ///
253 /// - Complexity: O(n) time for released recipe storage n, excluding captured-value
254 /// destructors; O(w) additional space for the maximum pending relationship edges w.
255 fn drop(&mut self) {
256 let mut pending = std::mem::take(&mut self.relationships);
257 while let Some(relationship) = pending.pop() {
258 if let RelationshipRecipe::Include { child, .. } = relationship
259 && let Ok(mut child) = Rc::try_unwrap(child)
260 {
261 pending.append(&mut child.relationships);
262 }
263 }
264 }
265}
266
267impl SheetTemplate {
268 /// Borrows formal names in declaration order, excluding private cells.
269 ///
270 /// - Complexity: O(c) traversal for c local declarations.
271 ///
272 /// # Examples
273 /// ```
274 /// let mut builder = adam_rs::TemplateBuilder::new();
275 /// builder.parameter::<i32>("value")?;
276 /// assert_eq!(builder.finish()?.parameter_names().collect::<Vec<_>>(), ["value"]);
277 /// # Ok::<(), adam_rs::TemplateError>(())
278 /// ```
279 pub fn parameter_names(&self) -> impl Iterator<Item = &str> {
280 self.cells
281 .iter()
282 .filter(|cell| cell.make.is_none())
283 .map(|cell| cell.name.as_str())
284 }
285
286 /// Returns this recipe's immutable identity without retaining its factories or children.
287 ///
288 /// # Examples
289 ///
290 /// ```
291 /// let template = adam_rs::TemplateBuilder::new().finish()?;
292 /// assert_eq!(template.id(), template.id());
293 /// # Ok::<(), adam_rs::TemplateError>(())
294 /// ```
295 pub fn id(&self) -> TemplateId {
296 TemplateId(self.owner.clone())
297 }
298}
299
300/// Checks ordinary relationship edge structure without constructing methods or a live sheet.
301///
302/// Every method must have distinct, nonempty outputs and reference the same union of
303/// input/output cells. Output sets of different methods cannot be equal or nested.
304/// Inputs may overlap outputs. This check neither specializes sources nor plans a graph.
305///
306/// # Errors
307/// Returns the violated structural rule.
308/// - Complexity: O(m²e) time and O(me) space for m methods of at most e edges.
309///
310/// # Examples
311/// ```
312/// use adam_rs::template::check_relationship_edges;
313/// assert!(check_relationship_edges(&[(&[0usize][..], &[1usize][..]),
314/// (&[1usize][..], &[0usize][..])]).is_ok());
315/// ```
316pub fn check_relationship_edges<C: Eq + std::hash::Hash>(
317 methods: &[(&[C], &[C])],
318) -> Result<(), &'static str> {
319 if methods.is_empty() {
320 return Err("empty relationship");
321 }
322 let mut cells = Vec::with_capacity(methods.len());
323 let mut outputs = Vec::with_capacity(methods.len());
324 for (input, output) in methods {
325 let output_set: HashSet<_> = output.iter().collect();
326 if output.is_empty() || output_set.len() != output.len() {
327 return Err("a method requires distinct nonempty outputs");
328 }
329 let cell_set: HashSet<_> = input.iter().chain(output.iter()).collect();
330 if cells.first().is_some_and(|first| first != &cell_set) {
331 return Err("relationship methods reference different cell sets");
332 }
333 if outputs.iter().any(|previous: &HashSet<&C>| {
334 previous.is_subset(&output_set) || output_set.is_subset(previous)
335 }) {
336 return Err("relationship output sets are equal or nested");
337 }
338 cells.push(cell_set);
339 outputs.push(output_set);
340 }
341 Ok(())
342}
343
344impl SheetTemplate {
345 /// Returns whether expansion contains only relationships and parameter declarations.
346 ///
347 /// Private cells, policies, outputs and conditionals disqualify a recipe transitively.
348 /// An empty recipe qualifies, independently of source specialization.
349 ///
350 /// # Examples
351 ///
352 /// ```
353 /// # use adam_rs::TemplateBuilder;
354 /// assert!(TemplateBuilder::new().finish()?.is_relationship_only());
355 /// # Ok::<(), adam_rs::TemplateError>(())
356 /// ```
357 pub fn is_relationship_only(&self) -> bool {
358 self.relationship_only
359 }
360}
361
362/// Returns one owner-checked relationship-group handle in the including namespace.
363///
364/// The group represents the entire compact include, including transitive children.
365/// It contains no live handles and does not enumerate its members before expansion.
366///
367/// # Examples
368/// See [`TemplateBuilder::include`].
369pub struct TemplateInclusion {
370 relationships: Vec<RelationshipSlot>,
371}
372
373impl TemplateInclusion {
374 /// Borrows the single inclusion-group handle without enumerating descendants.
375 ///
376 /// # Examples
377 ///
378 /// See [`TemplateBuilder::include`].
379 pub fn relationships(&self) -> &[RelationshipSlot] {
380 &self.relationships
381 }
382}
383
384/// Owns an opaque instance identity and its mapped live handles.
385///
386/// Dropping this object does not remove graph structure from the destination.
387/// Cells begin with root parameters/private cells in local declaration order, then
388/// included private cells in inline expansion order. Bound child formals introduce
389/// no duplicate cell entries. Relationships follow inline expansion order and
390/// remain distinct even when their recipe belongs to a group.
391/// Occurrence records retain local mappings and immutable identity tokens, not
392/// template factories or recursively owned inclusion edges.
393///
394/// # Examples
395/// See the [module example](self).
396#[derive(Debug)]
397pub struct Instance {
398 _identity: Rc<()>,
399 cells: Vec<CellId>,
400 relationships: Vec<RelationshipId>,
401 occurrences: Vec<InstanceOccurrence>,
402 local_cells: Vec<CellId>,
403 local_groups: Vec<std::ops::Range<usize>>,
404 relationship_origins: Vec<provenance::RelationshipOrigin>,
405 authored_methods: Vec<usize>,
406}
407
408impl Instance {
409 /// Borrows live relationship handles in inline declaration order.
410 ///
411 /// # Examples
412 ///
413 /// See the [module example](self).
414 pub fn relationships(&self) -> &[RelationshipId] {
415 &self.relationships
416 }
417
418 /// Borrows all mapped cells, including unchanged parameter identities.
419 ///
420 /// # Examples
421 ///
422 /// See the [module example](self).
423 pub fn cells(&self) -> &[CellId] {
424 &self.cells
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 /// Preserves slot identity through cloning and distinguishes owners and indices.
433 #[test]
434 fn local_handle_identity_laws() {
435 let mut a = TemplateBuilder::new();
436 let x = a.parameter::<i32>("x").unwrap();
437 let y = a.parameter::<i32>("y").unwrap();
438 let foreign = TemplateBuilder::new().parameter::<i32>("x").unwrap();
439 assert_eq!(x, x.clone());
440 assert_eq!(x.clone(), x);
441 assert_ne!(x, y);
442 assert_ne!(x, foreign);
443 let group = RelationshipSlot {
444 owner: x.owner.clone(),
445 index: 0,
446 };
447 assert_eq!(group, group.clone());
448 assert_ne!(
449 group,
450 RelationshipSlot {
451 owner: y.owner.clone(),
452 index: 1
453 }
454 );
455 assert_ne!(
456 group,
457 RelationshipSlot {
458 owner: foreign.owner.clone(),
459 index: 0
460 }
461 );
462 }
463
464 /// Rejects each malformed relationship boundary and accepts overlapping non-nested outputs.
465 #[test]
466 fn relationship_edges_obey_structural_contract() {
467 assert!(check_relationship_edges::<usize>(&[]).is_err());
468 assert!(check_relationship_edges(&[(&[0][..], &[][..])]).is_err());
469 assert!(check_relationship_edges(&[(&[0][..], &[1, 1][..])]).is_err());
470 assert!(check_relationship_edges(&[(&[0][..], &[1][..]), (&[1][..], &[2][..])]).is_err());
471 assert!(check_relationship_edges(&[(&[0][..], &[1][..]), (&[0][..], &[1][..])]).is_err());
472 assert!(
473 check_relationship_edges(&[(&[0, 2][..], &[1][..]), (&[0][..], &[1, 2][..])]).is_err()
474 );
475 assert!(
476 check_relationship_edges(&[(&[2][..], &[0, 1][..]), (&[0][..], &[1, 2][..])]).is_ok()
477 );
478 }
479
480 /// Retains empty-template summaries and the inclusion's single borrowed group.
481 #[test]
482 fn empty_template_and_inclusion_accessors() {
483 let child = Rc::new(TemplateBuilder::new().finish().unwrap());
484 assert_eq!(child.parameter_names().count(), 0);
485 assert!(child.is_relationship_only());
486 let mut builder = TemplateBuilder::new();
487 let inclusion = builder.include(child, &[]).unwrap();
488 assert_eq!(inclusion.relationships().len(), 1);
489 let instance = builder
490 .finish()
491 .unwrap()
492 .instantiate(&mut Sheet::new(), &[])
493 .unwrap();
494 assert!(instance.cells().is_empty());
495 assert!(instance.relationships().is_empty());
496 assert_eq!(
497 instance.relationship_group(
498 instance.occurrences()[0].id(),
499 &inclusion.relationships()[0]
500 ),
501 Some(&[][..])
502 );
503 }
504}