Skip to main content

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}