use crate::generated::enums::WrittenUnit;
use WrittenUnit::{
Aa, Ch, Cr, Dd, Gx, Hx, Mvs, Nirugu, Rh, Sh, Zr, Zwj, B, C, D, F, G, H, I, K, K2, L, M, N, O,
P, R, S, T, U, W, Y, Z,
};
const INITIAL_BEFORE_LETTER: &[WrittenUnit] = &[
B, C, Ch, Cr, D, Dd, F, G, Gx, I, K, K2, O, P, R, Rh, S, Sh, T, W, Y, Z, Zr,
];
const INITIAL_ALONE: &[WrittenUnit] = &[Aa, Cr, Dd, I, U, Zr];
const FINAL_AFTER_LETTER: &[WrittenUnit] = &[Cr, O, Zr];
const FINAL_ALONE: &[WrittenUnit] = &[
B, C, Ch, Cr, D, F, G, Gx, H, Hx, K, K2, L, M, N, P, R, Rh, S, Sh, T, W, Y, Z, Zr,
];
pub(crate) fn drop_redundant_zwj(shape: &[WrittenUnit]) -> Vec<WrittenUnit> {
let mut units: Vec<WrittenUnit> = Vec::with_capacity(shape.len());
for &unit in shape {
if unit == Zwj && units.last() == Some(&Zwj) {
continue;
}
units.push(unit);
}
loop {
let before = units.len();
let mut index = 0;
while index < units.len() {
if units[index] == Zwj && is_redundant(&units, index) {
units.remove(index);
} else {
index += 1;
}
}
if units.len() == before {
return units;
}
}
}
fn is_letter(unit: WrittenUnit) -> bool {
!unit.is_structural()
}
fn is_redundant(units: &[WrittenUnit], index: usize) -> bool {
let prev = index.checked_sub(1).map(|i| units[i]);
let next = units.get(index + 1).copied();
match (prev, next) {
(Some(Nirugu), _) | (_, Some(Nirugu)) => true,
(Some(p), Some(n)) if is_letter(p) && is_letter(n) => true,
(None | Some(Mvs), Some(n)) if is_letter(n) => {
let alone = matches!(units.get(index + 2), None | Some(Mvs));
let same_shape = if alone {
INITIAL_ALONE
} else {
INITIAL_BEFORE_LETTER
};
same_shape.contains(&n)
}
(Some(p), None | Some(Mvs)) if is_letter(p) => {
let alone = index < 2 || units[index - 2] == Mvs;
let same_shape = if alone {
FINAL_ALONE
} else {
FINAL_AFTER_LETTER
};
same_shape.contains(&p)
}
(None, None) => false,
_ => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
use WrittenUnit::A;
fn drop(shape: &[WrittenUnit]) -> Vec<WrittenUnit> {
drop_redundant_zwj(shape)
}
#[test]
fn interior_zwj_between_letters_is_dropped() {
assert_eq!(drop(&[L, I, Zwj, Aa]), [L, I, Aa]);
assert_eq!(drop(&[A, A, R, A, Zwj, O, A]), [A, A, R, A, O, A]);
assert_eq!(
drop(&[L, A, L, A, Zwj, P, O, O, A]),
[L, A, L, A, P, O, O, A]
);
}
#[test]
fn zwj_next_to_a_nirugu_is_dropped_anywhere() {
assert_eq!(drop(&[A, L, Zwj, Nirugu]), [A, L, Nirugu]);
assert_eq!(drop(&[Nirugu, Zwj, L, A]), [Nirugu, L, A]);
assert_eq!(drop(&[A, L, Nirugu, Zwj]), [A, L, Nirugu]);
assert_eq!(drop(&[Zwj, Nirugu, L, A]), [Nirugu, L, A]);
assert_eq!(drop(&[A, L, Nirugu, Zwj, Mvs, Aa]), [A, L, Nirugu, Mvs, Aa]);
}
#[test]
fn repeated_zwj_keeps_one() {
assert_eq!(drop(&[Zwj, Zwj, N, A]), [Zwj, N, A]);
assert_eq!(drop(&[A, A, Zwj, Zwj]), [A, A, Zwj]);
assert_eq!(drop(&[Zwj, Zwj, Zwj]), [Zwj]);
assert_eq!(drop(&[L, I, Zwj, Zwj, Aa]), [L, I, Aa]);
}
#[test]
fn zwj_after_mvs_is_segment_initial() {
assert_eq!(drop(&[A, Mvs, Zwj, B, Aa]), [A, Mvs, B, Aa]); assert_eq!(drop(&[A, Mvs, Zwj, Y, I, A]), [A, Mvs, Y, I, A]); assert_eq!(drop(&[A, Mvs, Zwj, N, A]), [A, Mvs, Zwj, N, A]); assert_eq!(drop(&[A, Mvs, Zwj, Aa]), [A, Mvs, Aa]); assert_eq!(drop(&[A, Mvs, Zwj, A]), [A, Mvs, Zwj, A]); assert_eq!(drop(&[A, Mvs, Zwj, B, Zwj, Mvs, Aa]), [A, Mvs, B, Mvs, Aa]);
}
#[test]
fn zwj_with_no_letter_on_either_side_is_dropped() {
assert_eq!(drop(&[Zwj, Mvs, Aa]), [Mvs, Aa]);
assert_eq!(drop(&[A, Mvs, Zwj]), [A, Mvs]);
assert_eq!(drop(&[A, Mvs, Zwj, Mvs, Aa]), [A, Mvs, Mvs, Aa]);
assert_eq!(drop(&[Mvs, Zwj]), [Mvs]);
assert_eq!(drop(&[Zwj, Mvs]), [Mvs]);
}
#[test]
fn zwj_before_mvs_is_segment_final() {
assert_eq!(drop(&[A, L, Zwj, Mvs, Aa]), [A, L, Zwj, Mvs, Aa]); assert_eq!(drop(&[A, O, Zwj, Mvs, Aa]), [A, O, Mvs, Aa]); assert_eq!(drop(&[B, Zwj, Mvs, Aa]), [B, Mvs, Aa]); assert_eq!(drop(&[A, Zwj, Mvs, Aa]), [A, Zwj, Mvs, Aa]); assert_eq!(drop(&[Mvs, B, Zwj, Mvs, Aa]), [Mvs, B, Mvs, Aa]); assert_eq!(drop(&[Zwj, B, Zwj, Mvs, Aa]), [B, Mvs, Aa]); }
#[test]
fn lone_zwj_is_the_whole_shape() {
assert_eq!(drop(&[Zwj]), [Zwj]);
assert_eq!(drop(&[]), []);
}
#[test]
fn word_initial_zwj_follows_the_reviewed_table() {
assert_eq!(drop(&[Zwj, B, Aa]), [B, Aa]); assert_eq!(drop(&[Zwj, S, A]), [S, A]); assert_eq!(drop(&[Zwj, N, A]), [Zwj, N, A]); assert_eq!(drop(&[Zwj, A, A]), [Zwj, A, A]);
assert_eq!(drop(&[Zwj, I]), [I]); assert_eq!(drop(&[Zwj, B]), [Zwj, B]); assert_eq!(drop(&[Zwj, B, Mvs, Aa]), [Zwj, B, Mvs, Aa]); assert_eq!(drop(&[Zwj, U, Mvs, Aa]), [U, Mvs, Aa]);
assert_eq!(drop(&[Zwj, B, Nirugu]), [B, Nirugu]); }
#[test]
fn word_final_zwj_follows_the_reviewed_table() {
assert_eq!(drop(&[A, A, Zwj]), [A, A, Zwj]); assert_eq!(drop(&[A, O, Zwj]), [A, O]); assert_eq!(drop(&[B, Zwj]), [B]); assert_eq!(drop(&[A, Zwj]), [A, Zwj]); assert_eq!(drop(&[Mvs, B, Zwj]), [Mvs, B]); assert_eq!(drop(&[Nirugu, B, Zwj]), [Nirugu, B, Zwj]); }
#[test]
fn both_edges_resolve_to_a_fixed_point() {
assert_eq!(drop(&[Zwj, B, Zwj]), [B]);
assert_eq!(drop(&[Zwj, A, Zwj]), [Zwj, A, Zwj]);
assert_eq!(drop(&[Zwj, O, Zwj]), [O, Zwj]);
}
#[test]
fn idempotent() {
for shape in [
vec![Zwj, B, Zwj],
vec![Zwj, N, A, Zwj],
vec![L, I, Zwj, Aa],
vec![A, L, Zwj, Mvs, Aa],
vec![Zwj, Zwj, Nirugu, Zwj],
] {
let once = drop(&shape);
assert_eq!(drop(&once), once, "{shape:?}");
}
}
}