adam_rs/template/builder.rs
1//! Ordered recipe construction and local inclusion remapping.
2
3use std::any::{Any, TypeId};
4use std::collections::{HashMap, HashSet};
5use std::rc::Rc;
6
7use super::{
8 CellFactory, CellRecipe, CellSlot, ConditionalRecipe, Item, MethodRecipe, PolicyRecipe,
9 RelationshipRecipe, RelationshipSlot, SheetTemplate, TemplateError, TemplateInclusion,
10};
11use crate::conditional::MatchSource;
12use crate::{CellId, CellKind, Filter, MatchExpr, Requirement, Sheet};
13
14/// Owns declarations and validates all local references before accepting them.
15///
16/// Private included names do not enter this builder's declaration namespace.
17///
18/// # Examples
19/// See the [module example](super).
20pub struct TemplateBuilder {
21 recipe: SheetTemplate,
22 names: HashSet<String>,
23 /// Tracks local groups already owned by a conditional.
24 conditional_groups: HashSet<usize>,
25 /// Tracks private cells already assigned a filter.
26 filtered: HashSet<usize>,
27}
28
29impl Default for TemplateBuilder {
30 /// Creates an empty builder with a fresh local owner.
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl TemplateBuilder {
37 /// Creates an empty recipe with its own local handle namespace.
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// # use adam_rs::TemplateBuilder;
43 /// let template = TemplateBuilder::new().finish()?;
44 /// assert!(template.is_relationship_only());
45 /// # Ok::<(), adam_rs::TemplateError>(())
46 /// ```
47 pub fn new() -> Self {
48 Self {
49 recipe: SheetTemplate {
50 owner: Rc::new(()),
51 cells: Vec::new(),
52 relationships: Vec::new(),
53 items: Vec::new(),
54 written_cells: HashSet::new(),
55 relationship_only: true,
56 expanded_size: Some(0),
57 depth: Some(1),
58 },
59 names: HashSet::new(),
60 conditional_groups: HashSet::new(),
61 filtered: HashSet::new(),
62 }
63 }
64
65 /// Declares a typed formal that allocates and writes no destination cell.
66 ///
67 /// # Errors
68 ///
69 /// Returns [`TemplateError::DuplicateParameter`] for a repeated declaration
70 /// name, or [`TemplateError::Capacity`] when storage cannot be reserved.
71 ///
72 /// - Complexity: Amortized O(n) time and space for a name of length n.
73 ///
74 /// # Examples
75 ///
76 /// See the [module example](super).
77 pub fn parameter<T: 'static>(&mut self, name: &str) -> Result<CellSlot, TemplateError> {
78 self.add_cell_recipe(name, TypeId::of::<T>(), CellKind::Cell, None)
79 }
80
81 /// Declares a private cell initialized afresh for every live instance.
82 ///
83 /// The initializer owns its captures and runs only during instantiation.
84 ///
85 /// # Errors
86 ///
87 /// Returns [`TemplateError::DuplicateParameter`] for a repeated declaration
88 /// name, or [`TemplateError::Capacity`] when storage cannot be reserved.
89 ///
90 /// - Complexity: Amortized O(n) time and space for a name of length n.
91 ///
92 /// # Examples
93 ///
94 /// ```
95 /// # use adam_rs::{Sheet, TemplateBuilder};
96 /// let mut builder = TemplateBuilder::new();
97 /// builder.cell("count", || Ok(3i32))?;
98 /// let mut sheet = Sheet::new();
99 /// let instance = builder.finish()?.instantiate(&mut sheet, &[])?;
100 /// assert_eq!(*sheet.read::<i32>(instance.cells()[0])?, 3);
101 /// # Ok::<(), Box<dyn std::error::Error>>(())
102 /// ```
103 pub fn cell<T, F>(&mut self, name: &str, initial: F) -> Result<CellSlot, TemplateError>
104 where
105 T: Any + PartialEq + 'static,
106 F: Fn() -> anyhow::Result<T> + 'static,
107 {
108 self.add_cell_recipe(
109 name,
110 TypeId::of::<T>(),
111 CellKind::Cell,
112 Some(Box::new(move |sheet: &mut Sheet| {
113 Ok(sheet.add_cell(initial()?))
114 })),
115 )
116 }
117
118 /// Declares a private source initialized afresh for every live instance.
119 ///
120 /// Specialization removes every whole method that writes a source.
121 ///
122 /// # Errors
123 ///
124 /// Returns [`TemplateError::DuplicateParameter`] for a repeated declaration
125 /// name, or [`TemplateError::Capacity`] when storage cannot be reserved.
126 ///
127 /// - Complexity: Amortized O(n) time and space for a name of length n.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// # use adam_rs::{CellKind, Sheet, TemplateBuilder};
133 /// let mut builder = TemplateBuilder::new();
134 /// builder.source("constant", || Ok(7i32))?;
135 /// let mut sheet = Sheet::new();
136 /// let instance = builder.finish()?.instantiate(&mut sheet, &[])?;
137 /// assert_eq!(sheet.cell_kind(instance.cells()[0]), Some(CellKind::Source));
138 /// # Ok::<(), adam_rs::TemplateError>(())
139 /// ```
140 pub fn source<T, F>(&mut self, name: &str, initial: F) -> Result<CellSlot, TemplateError>
141 where
142 T: Any + PartialEq + 'static,
143 F: Fn() -> anyhow::Result<T> + 'static,
144 {
145 self.add_cell_recipe(
146 name,
147 TypeId::of::<T>(),
148 CellKind::Source,
149 Some(Box::new(move |sheet: &mut Sheet| {
150 Ok(sheet.add_source(initial()?))
151 })),
152 )
153 }
154
155 /// Appends a checked local cell declaration and its ordered body item.
156 ///
157 /// - Complexity: Amortized O(n) time and space for a name of length n.
158 fn add_cell_recipe(
159 &mut self,
160 name: &str,
161 type_id: TypeId,
162 kind: CellKind,
163 make: Option<Box<CellFactory>>,
164 ) -> Result<CellSlot, TemplateError> {
165 if self.names.contains(name) {
166 return Err(TemplateError::DuplicateParameter { name: name.into() });
167 }
168 self.recipe
169 .cells
170 .try_reserve(1)
171 .map_err(|_| TemplateError::Capacity)?;
172 self.recipe
173 .items
174 .try_reserve(1)
175 .map_err(|_| TemplateError::Capacity)?;
176 let index = self.recipe.cells.len();
177 self.names.insert(name.into());
178 self.recipe.relationship_only &= make.is_none();
179 self.recipe.cells.push(CellRecipe {
180 name: name.into(),
181 type_id,
182 kind,
183 make,
184 });
185 self.recipe.items.push(Item::Cell(index));
186 Ok(CellSlot {
187 owner: self.recipe.owner.clone(),
188 index,
189 })
190 }
191
192 /// Appends a relationship after validating owners and native edge-set rules.
193 ///
194 /// Methods must reference the same cell set and have distinct, non-nested
195 /// output sets. Source-writing methods remain authored until specialization.
196 ///
197 /// # Errors
198 ///
199 /// Returns [`TemplateError::EmptyRelationship`], [`TemplateError::ForeignHandle`],
200 /// [`TemplateError::InvalidDefinition`], or [`TemplateError::Capacity`].
201 ///
202 /// - Complexity: O(m² × e) time and O(m × e) space for m methods of at most e edges.
203 ///
204 /// # Examples
205 ///
206 /// See the [module example](super).
207 pub fn relationship(
208 &mut self,
209 methods: Vec<MethodRecipe>,
210 ) -> Result<RelationshipSlot, TemplateError> {
211 self.validate_relationship(&methods)?;
212 self.recipe
213 .relationships
214 .try_reserve(1)
215 .map_err(|_| TemplateError::Capacity)?;
216 self.recipe
217 .items
218 .try_reserve(1)
219 .map_err(|_| TemplateError::Capacity)?;
220 let index = self.recipe.relationships.len();
221 self.recipe
222 .written_cells
223 .extend(methods.iter().flat_map(|m| &m.outputs).map(|s| s.index));
224 self.recipe
225 .relationships
226 .push(RelationshipRecipe::Methods(methods));
227 self.recipe.items.push(Item::Relationship(index));
228 Ok(RelationshipSlot {
229 owner: self.recipe.owner.clone(),
230 index,
231 })
232 }
233
234 /// Checks ownership and native relationship invariants without running factories.
235 ///
236 /// - Complexity: O(m² × e) time and O(m × e) space for m methods of at most e edges.
237 fn validate_relationship(&self, methods: &[MethodRecipe]) -> Result<(), TemplateError> {
238 if methods.is_empty() {
239 return Err(TemplateError::EmptyRelationship);
240 }
241 for slot in methods
242 .iter()
243 .flat_map(|m| m.inputs.iter().chain(&m.outputs))
244 {
245 self.check_slot(slot)?;
246 }
247 if let Some(slot) = methods
248 .iter()
249 .flat_map(|m| &m.outputs)
250 .find(|s| self.recipe.cells[s.index].kind == CellKind::Out)
251 {
252 return Err(TemplateError::InvalidDefinition {
253 detail: "an out cell already has its sole writer",
254 slot: Some(slot.clone()),
255 });
256 }
257 let edges: Vec<_> = methods
258 .iter()
259 .map(|method| (method.inputs.as_slice(), method.outputs.as_slice()))
260 .collect();
261 super::check_relationship_edges(&edges).map_err(|detail| TemplateError::InvalidDefinition {
262 detail,
263 slot: Some(methods[0].outputs[0].clone()),
264 })
265 }
266
267 /// Rejects a local reference not owned by this recipe.
268 fn check_slot(&self, slot: &CellSlot) -> Result<(), TemplateError> {
269 if !Rc::ptr_eq(&self.recipe.owner, &slot.owner) || slot.index >= self.recipe.cells.len() {
270 return Err(TemplateError::ForeignHandle { slot: slot.clone() });
271 }
272 Ok(())
273 }
274
275 /// Promotes a private ordinary cell through its sole, single-output writer.
276 ///
277 /// Formal parameters cannot become outputs. Requirements attach separately,
278 /// in declaration order, through [`Self::require`].
279 ///
280 /// # Errors
281 ///
282 /// Returns [`TemplateError::InvalidDefinition`] for a non-private, non-ordinary,
283 /// multiply-written, or non-single output; also returns relationship validation errors.
284 ///
285 /// - Complexity: O(i + r) time and O(i) space for inputs i and prior output summaries r.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # use adam_rs::{TemplateBuilder, MethodRecipe, Method};
291 /// let mut b = TemplateBuilder::new();
292 /// let input = b.parameter::<i32>("input")?;
293 /// let output = b.cell("output", || Ok(0i32))?;
294 /// b.out(MethodRecipe::new(&[input], &[output], |i, o| {
295 /// Ok(Method::from_fn_1_1(i[0], o[0], |x: &i32| Ok(*x)))
296 /// })?)?;
297 /// # Ok::<(), adam_rs::TemplateError>(())
298 /// ```
299 pub fn out(&mut self, writer: MethodRecipe) -> Result<RelationshipSlot, TemplateError> {
300 self.validate_relationship(std::slice::from_ref(&writer))?;
301 let output = &writer.outputs[0];
302 self.check_private(output)?;
303 if writer.outputs.len() != 1
304 || self.recipe.cells[output.index].kind != CellKind::Cell
305 || self.recipe.written_cells.contains(&output.index)
306 {
307 return Err(TemplateError::InvalidDefinition {
308 detail: "out requires one private ordinary cell with no prior writer",
309 slot: Some(output.clone()),
310 });
311 }
312 self.recipe
313 .relationships
314 .try_reserve(1)
315 .map_err(|_| TemplateError::Capacity)?;
316 self.recipe
317 .items
318 .try_reserve(1)
319 .map_err(|_| TemplateError::Capacity)?;
320 let index = self.recipe.relationships.len();
321 self.recipe.cells[output.index].kind = CellKind::Out;
322 self.recipe.written_cells.insert(output.index);
323 self.recipe
324 .relationships
325 .push(RelationshipRecipe::Out(writer));
326 self.recipe.items.push(Item::Relationship(index));
327 self.recipe.relationship_only = false;
328 Ok(RelationshipSlot {
329 owner: self.recipe.owner.clone(),
330 index,
331 })
332 }
333
334 /// Rejects policy targets that are not private declarations of this owner.
335 fn check_private(&self, cell: &CellSlot) -> Result<(), TemplateError> {
336 self.check_slot(cell)?;
337 if self.recipe.cells[cell.index].make.is_none() {
338 return Err(TemplateError::InvalidDefinition {
339 detail: "component policies and out writers require private targets",
340 slot: Some(cell.clone()),
341 });
342 }
343 Ok(())
344 }
345
346 /// Attaches one freshly constructed filter to a private cell.
347 ///
348 /// The factory receives the live target and positional dynamic arguments.
349 /// Its filter must declare those exact arguments and their concrete types.
350 ///
351 /// # Errors
352 ///
353 /// Returns ownership, capacity, or [`TemplateError::InvalidDefinition`] errors
354 /// for a formal target, duplicate filter, or self-dependent filter.
355 ///
356 /// - Complexity: O(i) time and space for i inputs.
357 ///
358 /// # Examples
359 ///
360 /// ```
361 /// # use adam_rs::{TemplateBuilder, Filter};
362 /// let mut b = TemplateBuilder::new();
363 /// let x = b.cell("x", || Ok(0i32))?;
364 /// b.filter(&x, &[], |_, _| Ok(Filter::from_fn_0(|x: &i32| Ok((*x).max(0)))))?;
365 /// # Ok::<(), adam_rs::TemplateError>(())
366 /// ```
367 pub fn filter<F>(
368 &mut self,
369 cell: &CellSlot,
370 inputs: &[CellSlot],
371 make: F,
372 ) -> Result<(), TemplateError>
373 where
374 F: Fn(CellId, &[CellId]) -> anyhow::Result<Filter> + 'static,
375 {
376 self.check_private(cell)?;
377 if self.filtered.contains(&cell.index) || inputs.contains(cell) {
378 return Err(TemplateError::InvalidDefinition {
379 detail: "duplicate or self-dependent filter",
380 slot: Some(cell.clone()),
381 });
382 }
383 let slot = cell.clone();
384 self.add_policy(
385 cell,
386 inputs,
387 Box::new(move |sheet, target, ids| {
388 let filter = make(target, ids).map_err(|source| TemplateError::Factory {
389 source,
390 slot: slot.clone(),
391 })?;
392 if filter.0.args != ids
393 || filter.0.value_type != sheet.cell_type_id(target).unwrap()
394 || !super::instantiate::signature_matches(sheet, ids, &filter.0.arg_types)
395 {
396 return Err(TemplateError::InvalidDefinition {
397 detail: "filter factory signature differs from its recipe",
398 slot: Some(slot.clone()),
399 });
400 }
401 sheet
402 .add_filter(target, filter)
403 .map_err(TemplateError::Runtime)
404 }),
405 )?;
406 self.filtered.insert(cell.index);
407 Ok(())
408 }
409
410 /// Attaches a freshly constructed requirement to a private cell.
411 ///
412 /// The factory must declare exactly the supplied positional inputs and types.
413 /// Native attachment evaluates ordinary/source requirements immediately;
414 /// output requirements wait until propagation.
415 ///
416 /// # Errors
417 ///
418 /// Returns ownership, capacity, or [`TemplateError::InvalidDefinition`] errors
419 /// for a formal target. Factory and native errors occur during instantiation.
420 ///
421 /// - Complexity: O(i) time and space for i inputs.
422 ///
423 /// # Examples
424 ///
425 /// ```
426 /// # use adam_rs::{TemplateBuilder, Requirement};
427 /// let mut b = TemplateBuilder::new();
428 /// let x = b.cell("x", || Ok(1i32))?;
429 /// b.require(&x, &[x.clone()], |i| Ok(Requirement::from_fn_1(i[0], |x: &i32| Ok(*x > 0))))?;
430 /// # Ok::<(), adam_rs::TemplateError>(())
431 /// ```
432 pub fn require<F>(
433 &mut self,
434 cell: &CellSlot,
435 inputs: &[CellSlot],
436 make: F,
437 ) -> Result<(), TemplateError>
438 where
439 F: Fn(&[CellId]) -> anyhow::Result<Requirement> + 'static,
440 {
441 let slot = cell.clone();
442 self.add_policy(
443 cell,
444 inputs,
445 Box::new(move |sheet, target, ids| {
446 let requirement = make(ids).map_err(|source| TemplateError::Factory {
447 source,
448 slot: slot.clone(),
449 })?;
450 if requirement.inputs != ids
451 || !super::instantiate::signature_matches(sheet, ids, &requirement.input_types)
452 {
453 return Err(TemplateError::InvalidDefinition {
454 detail: "requirement factory signature differs from its recipe",
455 slot: Some(slot.clone()),
456 });
457 }
458 sheet
459 .add_requirement(target, requirement)
460 .map(|_| ())
461 .map_err(TemplateError::Runtime)
462 }),
463 )
464 }
465
466 /// Appends a private policy after validating all local input owners.
467 ///
468 /// - Complexity: O(i) time and space for i inputs.
469 fn add_policy(
470 &mut self,
471 cell: &CellSlot,
472 inputs: &[CellSlot],
473 make: Box<super::PolicyFactory>,
474 ) -> Result<(), TemplateError> {
475 self.check_private(cell)?;
476 for input in inputs {
477 self.check_slot(input)?;
478 }
479 self.recipe
480 .items
481 .try_reserve(1)
482 .map_err(|_| TemplateError::Capacity)?;
483 self.recipe.items.push(Item::Policy(PolicyRecipe {
484 cell: cell.clone(),
485 inputs: inputs.to_vec(),
486 make,
487 }));
488 self.recipe.relationship_only = false;
489 Ok(())
490 }
491
492 /// Registers native conditional branches over direct relationships or included groups.
493 ///
494 /// Included groups must be transitively relationship-only before specialization.
495 /// Each constituent remains a distinct native relationship; no extra activation
496 /// layer is introduced. Keys follow native first-match semantics.
497 ///
498 /// # Errors
499 ///
500 /// Returns [`TemplateError::ConditionalComponent`] for stateful groups, or
501 /// [`TemplateError::InvalidDefinition`] for foreign, reused, or duplicate groups
502 /// or empty branch keys. Input ownership and capacity errors are also reported.
503 ///
504 /// - Complexity: O(i + g + k) time and space for inputs i, groups g, and keys k.
505 ///
506 /// # Examples
507 ///
508 /// ```
509 /// # use adam_rs::{TemplateBuilder, MatchExpr};
510 /// let mut b = TemplateBuilder::new();
511 /// let guard = b.parameter::<bool>("guard")?;
512 /// b.conditional(&[guard], |i| Ok(MatchExpr::cell(i[0])), vec![(vec![true], vec![])], vec![])?;
513 /// # Ok::<(), adam_rs::TemplateError>(())
514 /// ```
515 pub fn conditional<T, F>(
516 &mut self,
517 inputs: &[CellSlot],
518 make: F,
519 branches: Vec<(Vec<T>, Vec<RelationshipSlot>)>,
520 default: Vec<RelationshipSlot>,
521 ) -> Result<(), TemplateError>
522 where
523 T: Clone + Any + PartialEq + 'static,
524 F: Fn(&[CellId]) -> anyhow::Result<MatchExpr> + 'static,
525 {
526 let (keys, groups): (Vec<_>, Vec<_>) = branches.into_iter().unzip();
527 if keys.iter().any(Vec::is_empty) {
528 return Err(TemplateError::InvalidDefinition {
529 detail: "conditional branch requires keys",
530 slot: None,
531 });
532 }
533 self.conditional_factory(inputs, make, move || Ok(keys.clone()), groups, default)
534 }
535
536 /// Registers conditional groups with fresh keys constructed once per instance.
537 ///
538 /// Neither factory executes during preparation. Group ownership, uniqueness and
539 /// relationship-only eligibility are checked before mutation. Keys follow native
540 /// first-match semantics and must have one nonempty vector per declared branch.
541 ///
542 /// # Errors
543 /// Reports invalid inputs/groups before mutation; reports failed factories, invalid
544 /// key counts, empty keys or native conditional errors during construction.
545 /// - Complexity: O(i + b + g) preparation for inputs i, branches b and groups g;
546 /// construction adds factory costs and O(i + b + k + g + r) for keys k and
547 /// expanded relationship members r.
548 ///
549 /// # Examples
550 /// ```
551 /// let mut b = adam_rs::TemplateBuilder::new();
552 /// let flag = b.parameter::<bool>("flag")?;
553 /// b.conditional_factory::<bool, _, _>(&[flag],
554 /// |ids| Ok(adam_rs::MatchExpr::cell(ids[0])),
555 /// || Ok(vec![vec![true]]), vec![vec![]], vec![])?;
556 /// # Ok::<(), adam_rs::TemplateError>(())
557 /// ```
558 pub fn conditional_factory<T, F, K>(
559 &mut self,
560 inputs: &[CellSlot],
561 make: F,
562 keys: K,
563 branches: Vec<Vec<RelationshipSlot>>,
564 default: Vec<RelationshipSlot>,
565 ) -> Result<(), TemplateError>
566 where
567 T: Any + PartialEq + 'static,
568 F: Fn(&[CellId]) -> anyhow::Result<MatchExpr> + 'static,
569 K: Fn() -> anyhow::Result<Vec<Vec<T>>> + 'static,
570 {
571 for input in inputs {
572 self.check_slot(input)?;
573 }
574 let mut seen = HashSet::new();
575 for group in branches.iter().flatten().chain(&default) {
576 if !Rc::ptr_eq(&self.recipe.owner, &group.owner)
577 || group.index >= self.recipe.relationships.len()
578 || self.conditional_groups.contains(&group.index)
579 || !seen.insert(group.index)
580 {
581 return Err(TemplateError::InvalidDefinition {
582 detail: "foreign or already conditional relationship group",
583 slot: None,
584 });
585 }
586 let eligible = match &self.recipe.relationships[group.index] {
587 RelationshipRecipe::Methods(_) => true,
588 RelationshipRecipe::Out(_) => false,
589 RelationshipRecipe::Include { child, .. } => child.relationship_only,
590 };
591 if !eligible {
592 return Err(TemplateError::ConditionalComponent);
593 }
594 }
595 self.recipe
596 .items
597 .try_reserve(1)
598 .map_err(|_| TemplateError::Capacity)?;
599 self.recipe.items.push(Item::Conditional(ConditionalRecipe {
600 inputs: inputs.to_vec(),
601 make: Box::new(move |sheet, ids, groups, relationships| {
602 let source =
603 make(ids).map_err(|source| TemplateError::ConditionalFactory { source })?;
604 let valid = match &source.0 {
605 MatchSource::Cell(id) => {
606 ids == [*id] && sheet.cell_type_id(*id) == Some(TypeId::of::<T>())
607 }
608 MatchSource::Expr(expr) => {
609 expr.inputs == ids
610 && expr.output_type == TypeId::of::<T>()
611 && super::instantiate::signature_matches(sheet, ids, &expr.input_types)
612 }
613 };
614 if !valid {
615 return Err(TemplateError::InvalidDefinition {
616 detail: "conditional factory signature differs from its recipe",
617 slot: None,
618 });
619 }
620 let mut live_branches = Vec::new();
621 live_branches
622 .try_reserve(branches.len())
623 .map_err(|_| TemplateError::Capacity)?;
624 let keys = keys().map_err(|source| TemplateError::ConditionalFactory { source })?;
625 if keys.len() != branches.len() || keys.iter().any(Vec::is_empty) {
626 return Err(TemplateError::InvalidDefinition {
627 detail: "conditional key factory differs from its branches",
628 slot: None,
629 });
630 }
631 for (live_keys, slots) in keys.into_iter().zip(&branches) {
632 live_branches.push((
633 live_keys,
634 super::instantiate::group_members(slots, groups, relationships)?,
635 ));
636 }
637 let live_default =
638 super::instantiate::group_members(&default, groups, relationships)?;
639 sheet
640 .add_conditional(source, live_branches, live_default)
641 .map_err(TemplateError::Runtime)
642 }),
643 }));
644 self.conditional_groups.extend(seen);
645 self.recipe.relationship_only = false;
646 Ok(())
647 }
648
649 /// Appends a compact child-DAG edge with checked named bindings.
650 ///
651 /// Binding and structural failures leave the builder unchanged. Immutable
652 /// factories are shared, but every live instantiation creates fresh native methods.
653 /// The returned single group slot represents all transitive relationships;
654 /// only instantiation enumerates its distinct live members in inline order.
655 ///
656 /// # Errors
657 ///
658 /// Returns binding errors for unknown, missing, duplicate, aliased, foreign,
659 /// or wrongly typed arguments; returns [`TemplateError::InvalidDefinition`]
660 /// for a private out argument, or [`TemplateError::Capacity`] on reservation failure.
661 ///
662 /// - Complexity: Expected O(c + b) time and space for the child's own local
663 /// cells c and bindings b, independent of transitive expansion size.
664 ///
665 /// # Examples
666 ///
667 /// ```
668 /// # use std::rc::Rc;
669 /// # use adam_rs::TemplateBuilder;
670 /// let child = Rc::new(TemplateBuilder::new().finish()?);
671 /// let mut parent = TemplateBuilder::new();
672 /// let included = parent.include(child, &[])?;
673 /// assert_eq!(included.relationships().len(), 1);
674 /// # Ok::<(), adam_rs::TemplateError>(())
675 /// ```
676 pub fn include(
677 &mut self,
678 child: Rc<SheetTemplate>,
679 bindings: &[(&str, CellSlot)],
680 ) -> Result<TemplateInclusion, TemplateError> {
681 let bound = child.bind(bindings, |_, slot| {
682 self.check_slot(slot)?;
683 Ok(self.recipe.cells[slot.index].type_id)
684 })?;
685 if let Some(slot) = bound
686 .values()
687 .find(|s| self.recipe.cells[s.index].kind == CellKind::Out)
688 {
689 return Err(TemplateError::InvalidDefinition {
690 detail: "an included parameter cannot bind an out cell",
691 slot: Some(slot.clone()),
692 });
693 }
694 self.recipe
695 .relationships
696 .try_reserve(1)
697 .map_err(|_| TemplateError::Capacity)?;
698 self.recipe
699 .items
700 .try_reserve(1)
701 .map_err(|_| TemplateError::Capacity)?;
702 let index = self.recipe.relationships.len();
703 let handles = vec![RelationshipSlot {
704 owner: self.recipe.owner.clone(),
705 index,
706 }];
707 self.recipe.written_cells.extend(
708 child
709 .written_cells
710 .iter()
711 .filter_map(|i| bound.get(i))
712 .map(|s| s.index),
713 );
714 self.recipe.relationship_only &= child.relationship_only;
715 self.recipe.relationships.push(RelationshipRecipe::Include {
716 child,
717 bindings: bound,
718 });
719 self.recipe.items.push(Item::Relationship(index));
720 Ok(TemplateInclusion {
721 relationships: handles,
722 })
723 }
724
725 /// Finalizes the owning immutable recipe without invoking any factory.
726 ///
727 /// # Errors
728 ///
729 /// Rejects included parameters that became private outputs after inclusion.
730 /// Expanded-size overflow is retained for diagnosis before live expansion.
731 ///
732 /// - Complexity: O(n + e) time for local declarations n and edges e; no DAG expansion.
733 ///
734 /// # Examples
735 ///
736 /// See the [module example](super).
737 pub fn finish(mut self) -> Result<SheetTemplate, TemplateError> {
738 let mut size = self
739 .recipe
740 .cells
741 .len()
742 .checked_add(self.recipe.items.len())
743 .and_then(|n| n.checked_add(self.recipe.relationships.len()))
744 // Every occurrence needs a record, even when its recipe is empty.
745 .and_then(|n| n.checked_add(1));
746 let mut depth = Some(1usize);
747 for relationship in &self.recipe.relationships {
748 match relationship {
749 RelationshipRecipe::Include { child, bindings } => {
750 if let Some(slot) = bindings
751 .values()
752 .find(|s| self.recipe.cells[s.index].kind == CellKind::Out)
753 {
754 return Err(TemplateError::InvalidDefinition {
755 detail: "an included parameter cannot bind an out cell",
756 slot: Some(slot.clone()),
757 });
758 }
759 size = size.and_then(|n| child.expanded_size.and_then(|c| n.checked_add(c)));
760 depth = depth
761 .and_then(|d| child.depth.and_then(|c| c.checked_add(1)).map(|c| d.max(c)));
762 }
763 RelationshipRecipe::Methods(methods) => {
764 for method in methods {
765 size = size
766 .and_then(|n| n.checked_add(method.inputs.len()))
767 .and_then(|n| n.checked_add(method.outputs.len()))
768 .and_then(|n| n.checked_add(1));
769 }
770 }
771 RelationshipRecipe::Out(method) => {
772 size = size
773 .and_then(|n| n.checked_add(method.inputs.len()))
774 .and_then(|n| n.checked_add(method.outputs.len()));
775 }
776 }
777 }
778 self.recipe.expanded_size = size;
779 self.recipe.depth = depth;
780 Ok(self.recipe)
781 }
782}
783
784impl SheetTemplate {
785 /// Builds a local-index binding map after checking names, aliases, and concrete types.
786 ///
787 /// - Complexity: Expected O(c + b) time and O(c + b) space for cells c and bindings b.
788 pub(super) fn bind<A, F>(
789 &self,
790 bindings: &[(&str, A)],
791 type_of: F,
792 ) -> Result<HashMap<usize, A>, TemplateError>
793 where
794 A: Clone + Eq + std::hash::Hash,
795 F: Fn(&str, &A) -> Result<TypeId, TemplateError>,
796 {
797 let parameters: HashMap<&str, usize> = self
798 .cells
799 .iter()
800 .enumerate()
801 .filter(|(_, c)| c.make.is_none())
802 .map(|(i, c)| (c.name.as_str(), i))
803 .collect();
804 let mut result = HashMap::with_capacity(bindings.len());
805 let mut actuals = HashMap::with_capacity(bindings.len());
806 for (name, actual) in bindings {
807 let &index = parameters
808 .get(name)
809 .ok_or_else(|| TemplateError::UnknownParameter {
810 name: (*name).into(),
811 })?;
812 if result.contains_key(&index) {
813 return Err(TemplateError::DuplicateParameter {
814 name: (*name).into(),
815 });
816 }
817 if let Some(first) = actuals.insert(actual.clone(), *name) {
818 return Err(TemplateError::AliasedArgument {
819 first: first.into(),
820 second: (*name).into(),
821 });
822 }
823 let found = type_of(name, actual)?;
824 let expected = self.cells[index].type_id;
825 if expected != found {
826 return Err(TemplateError::TypeMismatch {
827 name: (*name).into(),
828 expected,
829 found,
830 });
831 }
832 result.insert(index, actual.clone());
833 }
834 for (index, cell) in self
835 .cells
836 .iter()
837 .enumerate()
838 .filter(|(_, c)| c.make.is_none())
839 {
840 if !result.contains_key(&index) {
841 return Err(TemplateError::MissingParameter {
842 name: cell.name.clone(),
843 });
844 }
845 }
846 Ok(result)
847 }
848}
849
850impl std::hash::Hash for CellSlot {
851 /// Hashes the immutable owner identity and local index consistently with equality.
852 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
853 std::hash::Hash::hash(&Rc::as_ptr(&self.owner), state);
854 std::hash::Hash::hash(&self.index, state);
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 /// Starts fresh namespaces and keeps the builder usable after a rejected declaration.
863 #[test]
864 fn defaults_and_rejected_declarations_preserve_builder_contract() {
865 let mut builder = TemplateBuilder::default();
866 let slot = builder.parameter::<i32>("input").unwrap();
867 assert!(matches!(
868 builder.parameter::<bool>("input"),
869 Err(TemplateError::DuplicateParameter { .. })
870 ));
871 let foreign = TemplateBuilder::new().parameter::<i32>("input").unwrap();
872 assert_ne!(slot, foreign);
873 assert_eq!(HashSet::from([slot.clone(), slot, foreign]).len(), 2);
874 builder.cell("private", || Ok(9_i32)).unwrap();
875 let template = builder.finish().unwrap();
876 assert_eq!(template.parameter_names().collect::<Vec<_>>(), ["input"]);
877 assert!(!template.is_relationship_only());
878 let mut sheet = Sheet::new();
879 let actual = sheet.add_cell(3_i32);
880 let instance = template
881 .instantiate(&mut sheet, &[("input", actual)])
882 .unwrap();
883 assert_eq!(instance.cells().len(), 2);
884 assert_eq!(*sheet.read::<i32>(instance.cells()[1]).unwrap(), 9);
885 assert!(
886 TemplateBuilder::default()
887 .finish()
888 .unwrap()
889 .is_relationship_only()
890 );
891 }
892}