obadh_engine 0.5.1

A linguistically accurate Roman to Bengali transliteration engine
Documentation
use crate::definitions::{consonant_value, vowel_value};

use super::{PhoneticUnit, PhoneticUnitType};

pub(super) fn is_long_iya_marker_at(text: &str, byte_index: usize) -> bool {
    byte_index > 0 && matches!(text.as_bytes().get(byte_index - 1), Some(b'y') | Some(b'Y'))
}

pub(super) fn normalize_iyw_long_iya_signal(units: &mut Vec<PhoneticUnit>) -> bool {
    let Some(first_match) = first_iyw_long_iya_signal(units) else {
        return false;
    };

    let mut read = first_match;
    let mut write = first_match;
    while read < units.len() {
        if is_iyw_long_iya_signal_at(units, read) {
            promote_short_i_to_long_i(&mut units[read]);
            super::move_unit(units, read, write);
            super::move_unit(units, read + 1, write + 1);
            write += 2;
            read += 3;
            continue;
        }

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

    units.truncate(write);
    true
}

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

fn is_iyw_long_iya_signal_at(units: &[PhoneticUnit], index: usize) -> bool {
    index + 2 < units.len()
        && is_ya_consonant_unit(&units[index + 1])
        && is_long_iya_marker(&units[index + 2])
        && is_short_i_vowel_bearing_unit(&units[index])
}

fn is_short_i_vowel_bearing_unit(unit: &PhoneticUnit) -> bool {
    if !matches!(
        unit.unit_type,
        PhoneticUnitType::ConsonantWithVowel
            | PhoneticUnitType::ConjunctWithVowel
            | PhoneticUnitType::RephOverConsonantWithVowel
    ) {
        return false;
    }

    attached_vowel_key(&unit.text) == Some("i")
}

fn attached_vowel_key(text: &str) -> Option<&str> {
    let component = text.rsplit(",,").next()?;
    let component = component
        .strip_prefix("rr")
        .filter(|component| !component.is_empty())
        .unwrap_or(component);

    split_component_vowel_key(component)
}

fn split_component_vowel_key(component: &str) -> Option<&str> {
    for (boundary, _) in component.char_indices().skip(1) {
        let consonant = &component[..boundary];
        let vowel = &component[boundary..];

        if vowel_value(vowel).is_some()
            && (consonant_value(consonant).is_some() || is_phola_component_consonant(consonant))
        {
            return Some(vowel);
        }
    }

    None
}

fn is_phola_component_consonant(component: &str) -> bool {
    component == "w"
}

fn is_ya_consonant_unit(unit: &PhoneticUnit) -> bool {
    unit.unit_type == PhoneticUnitType::Consonant && matches!(unit.text.as_str(), "y" | "Y")
}

fn is_long_iya_marker(unit: &PhoneticUnit) -> bool {
    unit.unit_type == PhoneticUnitType::Unknown && unit.text == "w"
}

fn promote_short_i_to_long_i(unit: &mut PhoneticUnit) {
    debug_assert!(is_short_i_vowel_bearing_unit(unit));
    unit.text.pop();
    unit.text.push('I');
}