// SPDX-FileCopyrightText: 2026 Milovann Yanatchkov // SPDX-License-Identifier: MIT //! Explode — the whole engine story of `Explode`. //! //! Nature lives in the kernel (`bimr::operator::Explode`): a Line into its //! two endpoints, a List of Lines into pairs (each pair a List of two //! Points). Same record shape as `Divide`/`Cut`: the statement is a factory //! with a persistent root — the record stays, the parts are created as //! named nodes edged to it, and resolving `ex1` yields the created //! collection. `Frame(ex1, …)` lifts one Frame per pair. use bimr::Entity; use bimr::operator::Explode as KernelExplode; use parser::ast::Argument; use crate::error::Error; use crate::nodes::{lookup, name_of, positional_args, ref_name}; use crate::store::Store; use crate::value::Node; /// `ex = Explode(source)` — positional only. The source is a Line (two /// endpoints) or a List of Lines / a `Cut` record (one pair per line). pub(crate) fn eval( store: &mut Store, file: &str, name: &str, args: &[Argument], ) -> Result<(), Error> { let positional = positional_args(args)?; if positional.len() != 1 { return Err(Error::Unsupported("arity")); } let source = name_of(positional[0])?; let source_node = lookup(store, &source)?; // Normalize the source into a kernel Entity for the explosion. let source_entity = match &source_node.entity { Entity::Cut(_) => { let lines: Vec = store .outputs_of(source_node.guid) .iter() .map(|&guid| { let node = store.resolve(guid).expect("indexed"); match node.entity { Entity::Line(l) => Entity::Line(l), ref other => unreachable!("cut pieces are Lines: {}", other.type_name()), } }) .collect(); Entity::List(bimr::set::List::try_new(lines).expect("cut pieces — all Lines")) } entity => entity.clone(), }; // The record first — items and consumers hang off it. let explode = KernelExplode::new(source_entity.clone()); let record = store.insert(file, name, Entity::Explode(explode), vec![source_node.guid]); // The created parts: named `_`, edges to the record. All // names checked before any node is created. let parts = KernelExplode::new(source_entity) .parts() .map_err(|_| Error::Unsupported("explode"))?; let mut names = Vec::with_capacity(parts.len()); for i in 0..parts.len() { let item_name = format!("{name}_{i}"); if store.named.contains_key(&item_name) { return Err(Error::NameCollision(item_name)); } names.push(item_name); } let mut items = Vec::with_capacity(parts.len()); for (i, item_name) in names.iter().enumerate() { let guid = store.insert(file, item_name, parts[i].clone(), vec![record]); items.push(guid); } store.outputs.insert(record, items); Ok(()) } /// The compact BIMR text: `ex1 = Explode(ct1)` — the record only; its /// created parts are engine-owned and never reach the artifact. pub(crate) fn to_bimr(store: &Store, node: &Node, _e: &KernelExplode) -> String { let name = ref_name(store, node.guid); let source_var = ref_name(store, node.refs[0]); format!("{name} = Explode({source_var})") }