89 lines
3.2 KiB
Rust
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))
|
|
}
|