#[must_use]
pub const fn names_are_separating(names: &[&str]) -> bool {
match names.split_first() {
None => true,
Some((first, rest)) => {
name_is_grammatical(first) && !names_contain(rest, first) && names_are_separating(rest)
}
}
}
#[derive(Clone, Copy)]
enum Segment {
Opening,
Inside,
}
pub(crate) const fn name_is_grammatical(name: &str) -> bool {
grammatical(name.as_bytes(), Segment::Opening)
}
const fn grammatical(bytes: &[u8], at: Segment) -> bool {
match bytes.split_first() {
None => matches!(at, Segment::Inside),
Some((byte, rest)) => {
if *byte == b'-' {
matches!(at, Segment::Inside) && grammatical(rest, Segment::Opening)
} else if byte.is_ascii_lowercase() || byte.is_ascii_digit() {
grammatical(rest, Segment::Inside)
} else {
false
}
}
}
}
const fn names_contain(names: &[&str], name: &str) -> bool {
match names.split_first() {
None => false,
Some((first, rest)) => {
same_bytes(first.as_bytes(), name.as_bytes()) || names_contain(rest, name)
}
}
}
const fn same_bytes(left: &[u8], right: &[u8]) -> bool {
match (left.split_first(), right.split_first()) {
(None, None) => true,
(None, Some(_)) | (Some(_), None) => false,
(Some((here, left_rest)), Some((there, right_rest))) => {
*here == *there && same_bytes(left_rest, right_rest)
}
}
}