///|
/// Replace a label while preserving identity, control role and source binding.
pub fn Node::with_label(self : Node, label : String) -> Node {
{ ..self, label, }
}
///|
/// Replace the ordered nodes, copying the caller's collection.
pub fn Flowchart::with_nodes(
self : Flowchart,
nodes : ArrayView[Node],
) -> Flowchart {
{ ..self, nodes: ReadOnlyArray::from_array(nodes), }
}
///|
/// Allocate a deterministic identity without colliding with authored identities.
pub fn fresh_id(
stem : String,
used : ArrayView[String],
work_limit~ : Int32 = 1000000,
) -> Result[String, @diagrams.Diagnostic] {
if work_limit <= 0 || stem.is_empty() {
return Err(
@diagrams.Diagnostic::error(
"flowchart/invalid-limits", "An ID stem and positive work limit are required",
),
)
}
let taken : Map[String, Bool] = Map([])
let mut work = 0
for id in used {
if id.length() >= work_limit - work {
return Err(
@diagrams.Diagnostic::error(
"flowchart/id-limit", "Identity allocation work limit exceeded",
),
)
}
work += id.length() + 1
taken[id] = true
}
let mut candidate = stem
let mut suffix = 0
while true {
if candidate.length() >= work_limit - work {
return Err(
@diagrams.Diagnostic::error(
"flowchart/id-limit", "Identity allocation work limit exceeded",
),
)
}
work += candidate.length() + 1
if !taken.contains(candidate) {
return Ok(candidate)
}
suffix += 1
candidate = stem + ":" + suffix.to_string()
} nobreak {
Err(
@diagrams.Diagnostic::error(
"flowchart/id-limit", "Identity allocation work limit exceeded",
),
)
}
}