use ifc_model::EntityId;
use crate::error::{AlignmentError, AlignmentResult};
pub(crate) const MISPLACED: &str =
"a zero-length alignment segment is allowed only as the last segment of its layout";
const ONLY_CLOSING: &str =
"an alignment layout needs a segment of positive length before its closing zero-length segment";
pub(crate) fn split_closing<T>(
segments: &[T],
length: impl Fn(&T) -> f64,
entity: impl Fn(&T) -> EntityId,
) -> AlignmentResult<(&[T], Option<&T>)> {
let Some((last, body)) = segments.split_last() else {
return Ok((segments, None));
};
if let Some(misplaced) = body.iter().find(|segment| length(segment) == 0.0) {
return Err(AlignmentError::SemanticViolation {
entity: Some(entity(misplaced)),
rule: MISPLACED,
});
}
if length(last) != 0.0 {
return Ok((segments, None));
}
if body.is_empty() {
return Err(AlignmentError::SemanticViolation {
entity: Some(entity(last)),
rule: ONLY_CLOSING,
});
}
Ok((body, Some(last)))
}
#[cfg(test)]
mod tests {
use super::*;
fn split(lengths: &[f64]) -> AlignmentResult<(usize, Option<u64>)> {
let segments: Vec<(u64, f64)> = lengths
.iter()
.enumerate()
.map(|(i, l)| (i as u64 + 1, *l))
.collect();
split_closing(&segments, |s| s.1, |s| EntityId(s.0))
.map(|(body, closing)| (body.len(), closing.map(|s| s.0)))
}
#[test]
fn a_closing_zero_length_segment_is_split_off() {
assert_eq!(split(&[10.0, 5.0, 0.0]), Ok((2, Some(3))));
assert_eq!(split(&[10.0, 5.0]), Ok((2, None)));
assert_eq!(split(&[]), Ok((0, None)));
}
#[test]
fn a_zero_length_segment_before_the_end_is_refused() {
assert_eq!(
split(&[10.0, 0.0, 5.0, 0.0]),
Err(AlignmentError::SemanticViolation {
entity: Some(EntityId(2)),
rule: MISPLACED,
})
);
}
#[test]
fn a_layout_of_only_its_closing_segment_is_refused() {
assert!(matches!(
split(&[0.0]),
Err(AlignmentError::SemanticViolation {
entity: Some(EntityId(1)),
..
})
));
}
}