88 lines
3.4 KiB
Rust
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})")
|
|
}
|