bimr-engine/eval/src/nodes/operator/explode.rs
2026-08-31 11:16:41 +02:00

88 lines
3.4 KiB
Rust

// 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<Entity> = 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 `<binding>_<i>`, 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})")
}