obadh_engine 0.5.1

A linguistically accurate Roman to Bengali transliteration engine
Documentation
use super::{move_unit, PhoneticUnit, PhoneticUnitType};

pub(super) fn normalize_reph_and_vocalic_r(units: &mut Vec<PhoneticUnit>) {
    let mut read = 0;
    let mut write = 0;

    while read < units.len() {
        if read + 1 < units.len()
            && units[read].text == "rr"
            && units[read].unit_type == PhoneticUnitType::SpecialForm
            && units[read + 1].text == "i"
            && units[read + 1].unit_type == PhoneticUnitType::Vowel
        {
            let position = units[read].position;
            units[write] = PhoneticUnit {
                text: String::from("rri"),
                unit_type: PhoneticUnitType::Vowel,
                position,
            };
            read += 2;
            write += 1;
            continue;
        }

        if read + 1 < units.len()
            && units[read].text == "rr"
            && units[read].unit_type == PhoneticUnitType::SpecialForm
            && units[read + 1].unit_type == PhoneticUnitType::Consonant
        {
            let position = units[read].position;
            let next_text = units[read + 1].text.as_str();
            let mut reph_text = String::with_capacity(2 + next_text.len());
            reph_text.push_str("rr");
            reph_text.push_str(next_text);

            units[write] = PhoneticUnit {
                text: reph_text,
                unit_type: PhoneticUnitType::RephOverConsonant,
                position,
            };
            read += 2;
            write += 1;
            continue;
        }

        move_unit(units, read, write);

        read += 1;
        write += 1;
    }

    units.truncate(write);
}

pub(super) fn normalize_redundant_reph_hasant(units: &mut Vec<PhoneticUnit>) {
    let Some(first_match) = first_redundant_reph_hasant(units) else {
        return;
    };

    let mut read = first_match;
    let mut write = first_match;

    while read < units.len() {
        if is_redundant_reph_hasant_at(units, read) {
            move_unit(units, read, write);
            read += 2;
            write += 1;
            continue;
        }

        move_unit(units, read, write);
        read += 1;
        write += 1;
    }

    units.truncate(write);
}

pub(super) fn normalize_redundant_khanda_ta_hasant(units: &mut Vec<PhoneticUnit>) {
    let Some(first_match) = first_redundant_khanda_ta_hasant(units) else {
        return;
    };

    let mut read = first_match;
    let mut write = first_match;

    while read < units.len() {
        if is_redundant_khanda_ta_hasant_at(units, read) {
            move_unit(units, read, write);
            read += 2;
            write += 1;
            continue;
        }

        move_unit(units, read, write);
        read += 1;
        write += 1;
    }

    units.truncate(write);
}

pub(super) fn normalize_velar_nasal_conjunct_aliases(units: &mut Vec<PhoneticUnit>) {
    let Some(first_match) = first_velar_nasal_conjunct_alias(units) else {
        return;
    };

    let mut read = first_match;
    let mut write = first_match;

    while read < units.len() {
        if is_velar_nasal_conjunct_alias_at(units, read) {
            if let Some(canonical_tail) = velar_nasal_conjunct_tail(&units[read + 1].text) {
                let position = units[read].position;
                let mut text = String::with_capacity(4 + canonical_tail.len());
                text.push_str("Ng,,");
                text.push_str(canonical_tail);

                units[write] = PhoneticUnit {
                    text,
                    unit_type: PhoneticUnitType::Conjunct,
                    position,
                };
                read += 2;
                write += 1;
                continue;
            }
        }

        move_unit(units, read, write);
        read += 1;
        write += 1;
    }

    units.truncate(write);
}

pub(super) fn normalize_non_conjunct_ra_ya_zwnj(units: &mut Vec<PhoneticUnit>) {
    let Some(first_match) = first_non_conjunct_ra_ya_zwnj(units) else {
        return;
    };

    let mut read = first_match;
    let mut write = first_match;

    while read < units.len() {
        if is_non_conjunct_ra_ya_zwnj_at(units, read) {
            units[write] = PhoneticUnit {
                text: String::from("rZ,,y"),
                unit_type: PhoneticUnitType::Conjunct,
                position: units[read].position,
            };
            read += 3;
            write += 1;
            continue;
        }

        move_unit(units, read, write);
        read += 1;
        write += 1;
    }

    units.truncate(write);
}

fn first_redundant_reph_hasant(units: &[PhoneticUnit]) -> Option<usize> {
    (0..units.len().saturating_sub(2)).find(|&index| is_redundant_reph_hasant_at(units, index))
}

fn is_redundant_reph_hasant_at(units: &[PhoneticUnit], index: usize) -> bool {
    index + 2 < units.len()
        && units[index].unit_type == PhoneticUnitType::SpecialForm
        && units[index].text == "rr"
        && units[index + 1].unit_type == PhoneticUnitType::ConsonantWithHasant
        && is_reph_target_after_redundant_hasant(&units[index + 2])
}

fn is_reph_target_after_redundant_hasant(unit: &PhoneticUnit) -> bool {
    unit.unit_type == PhoneticUnitType::Consonant || is_khanda_ta_unit(unit)
}

fn first_redundant_khanda_ta_hasant(units: &[PhoneticUnit]) -> Option<usize> {
    (0..units.len().saturating_sub(2)).find(|&index| is_redundant_khanda_ta_hasant_at(units, index))
}

fn is_redundant_khanda_ta_hasant_at(units: &[PhoneticUnit], index: usize) -> bool {
    index + 2 < units.len()
        && is_khanda_ta_unit(&units[index])
        && units[index + 1].unit_type == PhoneticUnitType::ConsonantWithHasant
        && units[index + 2].unit_type == PhoneticUnitType::Consonant
}

fn is_khanda_ta_unit(unit: &PhoneticUnit) -> bool {
    unit.unit_type == PhoneticUnitType::SpecialForm && matches!(unit.text.as_str(), "t``" | "T``")
}

fn first_velar_nasal_conjunct_alias(units: &[PhoneticUnit]) -> Option<usize> {
    (0..units.len().saturating_sub(1)).find(|&index| {
        is_velar_nasal_conjunct_alias_at(units, index)
            && velar_nasal_conjunct_tail(&units[index + 1].text).is_some()
    })
}

fn is_velar_nasal_conjunct_alias_at(units: &[PhoneticUnit], index: usize) -> bool {
    index + 1 < units.len()
        && units[index].unit_type == PhoneticUnitType::SpecialForm
        && units[index].text == "ng"
        && units[index + 1].unit_type == PhoneticUnitType::Consonant
}

fn velar_nasal_conjunct_tail(text: &str) -> Option<&'static str> {
    match text {
        "g" => Some("g"),
        "gh" | "Gh" | "GH" => Some("gh"),
        _ => None,
    }
}

fn first_non_conjunct_ra_ya_zwnj(units: &[PhoneticUnit]) -> Option<usize> {
    (0..units.len().saturating_sub(2)).find(|&index| is_non_conjunct_ra_ya_zwnj_at(units, index))
}

fn is_non_conjunct_ra_ya_zwnj_at(units: &[PhoneticUnit], index: usize) -> bool {
    index + 2 < units.len()
        && units[index].unit_type == PhoneticUnitType::Consonant
        && units[index].text == "r"
        && units[index + 1].unit_type == PhoneticUnitType::Unknown
        && units[index + 1].text == "Z"
        && units[index + 2].unit_type == PhoneticUnitType::Consonant
        && matches!(units[index + 2].text.as_str(), "y" | "Y")
}