obadh_engine 0.9.1

A linguistically accurate Roman to Bengali transliteration engine
Documentation
use crate::definitions::conjuncts::ConjunctDefinitions;

use super::parts::BorrowedParts;
use super::{reph_base_part, PhoneticUnit, PhoneticUnitType};

pub(super) fn form_conjuncts_in_range(
    units: &mut [PhoneticUnit],
    start: usize,
    end: usize,
    conjunct_defs: &ConjunctDefinitions,
) {
    if end.saturating_sub(start) <= 1 {
        return;
    }

    let mut i = start;

    while i < end {
        if units[i].text.is_empty() {
            i += 1;
            continue;
        }

        if let Some(length) = longest_conjunct_prefix_in_range(units, i, end, conjunct_defs) {
            let conjunct_text = conjunct_text_for_range(units, i, length);

            let position = units[i].position;
            units[i] = PhoneticUnit {
                text: conjunct_text,
                unit_type: PhoneticUnitType::Conjunct,
                position,
            };

            for unit in units.iter_mut().take(i + length).skip(i + 1) {
                unit.text.clear();
            }
        }

        i += 1;
    }
}

pub(super) fn is_conjunct_run_component(unit: &PhoneticUnit) -> bool {
    matches!(
        unit.unit_type,
        PhoneticUnitType::Consonant
            | PhoneticUnitType::Conjunct
            | PhoneticUnitType::RephOverConsonant
    ) || (unit.unit_type == PhoneticUnitType::SpecialForm && unit.text == "rr")
        || (unit.unit_type == PhoneticUnitType::Unknown && unit.text == "w")
}

fn longest_conjunct_prefix_in_range(
    units: &[PhoneticUnit],
    start: usize,
    end: usize,
    conjunct_defs: &ConjunctDefinitions,
) -> Option<usize> {
    let mut node = conjunct_defs.conjunct_match_root();
    let mut best_length = None;
    let mut parts = BorrowedParts::new();
    let mut trie_active = true;

    for (current, unit) in units.iter().enumerate().take(end).skip(start) {
        if unit.text.is_empty() {
            break;
        }

        for part in unit.text.split(",,") {
            parts.push(part);

            if trie_active {
                if let Some(next_node) = conjunct_defs.advance_conjunct_match(node, part) {
                    node = next_node;
                } else {
                    trie_active = false;
                }
            }
        }

        let length = current - start + 1;
        if length >= 2
            && ((trie_active && conjunct_defs.conjunct_match_value(node).is_some())
                || conjunct_defs.can_form_derived_conjunct_from_parts(parts.as_slice()))
        {
            best_length = Some(length);
        }

        if !trie_active && !conjunct_defs.can_match_derived_conjunct_prefix(parts.as_slice()) {
            break;
        }
    }

    best_length.or_else(|| reph_tail_conjunct_prefix_in_range(units, start, end, conjunct_defs))
}

fn reph_tail_conjunct_prefix_in_range(
    units: &[PhoneticUnit],
    start: usize,
    end: usize,
    conjunct_defs: &ConjunctDefinitions,
) -> Option<usize> {
    let first = units.get(start)?;
    let reph_base = reph_base_part(first)?;
    let mut tail_parts = BorrowedParts::from_one(reph_base);
    let mut best_length = None;

    for (current, unit) in units.iter().enumerate().take(end).skip(start + 1) {
        if unit.text.is_empty() {
            break;
        }

        for part in unit.text.split(",,") {
            tail_parts.push(part);
        }

        let length = current - start + 1;
        if tail_parts.len() >= 2
            && conjunct_defs.can_form_conjunct_from_parts(tail_parts.as_slice())
            && !is_ambiguous_reph_r_phola_before_vowel(units, start, length, tail_parts.as_slice())
        {
            best_length = Some(length);
        }
    }

    best_length
}

fn is_ambiguous_reph_r_phola_before_vowel(
    units: &[PhoneticUnit],
    start: usize,
    length: usize,
    tail_parts: &[&str],
) -> bool {
    tail_parts.last() == Some(&"r")
        && units.get(start + length).is_some_and(|unit| {
            matches!(
                unit.unit_type,
                PhoneticUnitType::Vowel | PhoneticUnitType::TerminatingVowel
            )
        })
}

fn conjunct_text_for_range(units: &[PhoneticUnit], start: usize, length: usize) -> String {
    let mut conjunct_text = String::new();

    for unit in &units[start..start + length] {
        push_conjunct_text_parts(&mut conjunct_text, unit);
    }

    conjunct_text
}

fn push_conjunct_text_parts(conjunct_text: &mut String, unit: &PhoneticUnit) {
    if let Some(reph_base) = reph_base_part(unit) {
        push_conjunct_text_part(conjunct_text, "rr");
        push_conjunct_text_part(conjunct_text, reph_base);
        return;
    }

    for part in unit.text.split(",,") {
        push_conjunct_text_part(conjunct_text, part);
    }
}

fn push_conjunct_text_part(conjunct_text: &mut String, part: &str) {
    if !conjunct_text.is_empty() {
        conjunct_text.push_str(",,");
    }
    conjunct_text.push_str(part);
}