// 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 `_`, 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 { 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)) }