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

89 lines
3.2 KiB
Rust

// SPDX-FileCopyrightText: 2026 Milovann Yanatchkov
// SPDX-License-Identifier: MIT
//! Cut — the whole engine story of `Cut`.
//!
//! Nature lives in the kernel (`bimr::operator::Cut`): the source by value,
//! the piece count, the cutting methods (circle first). Same record shape
//! as `Divide`: the statement is a factory with a persistent root — the
//! record stays (parametric), the pieces are created as named nodes edged
//! to it, and resolving `d1` yields the created collection.
use bimr::Entity;
use bimr::operator::Cut as KernelCut;
use parser::ast::Argument;
use crate::error::Error;
use crate::nodes::{fmt_num, lookup, name_of, number, positional_args, ref_name};
use crate::store::Store;
use crate::value::Node;
/// `ct = Cut(source, n)` — positional only. The source must be a Circle
/// (the only cutting supported so far); `n` an integer ≥ 1.
pub(crate) fn eval(
store: &mut Store,
file: &str,
name: &str,
args: &[Argument],
) -> Result<(), Error> {
let positional = positional_args(args)?;
if positional.len() != 2 {
return Err(Error::Unsupported("arity"));
}
let source = name_of(positional[0])?;
let n = count(positional[1])?;
let source_node = lookup(store, &source)?;
let source_entity = match &source_node.entity {
Entity::Circle(c) => Entity::Circle(*c),
Entity::Curve(c) => Entity::Curve(c.clone()),
other => {
return Err(Error::WrongNature {
name: source,
expected: "Circle or Curve",
got: other.type_name(),
});
}
};
// The record first — items and consumers hang off it.
let cut = KernelCut::new(source_entity.clone(), n);
let record = store.insert(file, name, Entity::Cut(cut), vec![source_node.guid]);
// The created pieces: named `<binding>_<i>`, edges to the record. All
// names checked before any node is created.
let lines = KernelCut::new(source_entity, n)
.lines()
.map_err(|_| Error::Unsupported("cut"))?;
let mut names = Vec::with_capacity(n);
for i in 0..n {
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(n);
for (i, item_name) in names.iter().enumerate() {
let guid = store.insert(file, item_name, Entity::Line(lines[i]), vec![record]);
items.push(guid);
}
store.outputs.insert(record, items);
Ok(())
}
/// The piece count: an integral number ≥ 1.
fn count(expr: &parser::ast::Expression) -> Result<usize, Error> {
let x = number(expr)?;
if x < 1.0 || x.fract() != 0.0 || x > usize::MAX as f64 {
return Err(Error::Unsupported("arity"));
}
Ok(x as usize)
}
/// The compact BIMR text: `ct1 = Cut(c1, 8)` — the record only; its created
/// pieces are engine-owned and never reach the artifact.
pub(crate) fn to_bimr(store: &Store, node: &Node, c: &KernelCut) -> String {
let name = ref_name(store, node.guid);
let source_var = ref_name(store, node.refs[0]);
format!("{name} = Cut({source_var}, {})", fmt_num(c.n() as f64))
}