normalizer-tr 0.4.0

Bounded Turkish text-to-speech normalization with original-source diagnostics
Documentation
use super::super::scan::whitespace_between;
use super::{Attempt, Context};
use crate::{
    FallbackClass, IssueCategory,
    domain::{
        lexicon,
        numeric::{self, Numeric},
    },
    model::Value,
};
pub(super) fn read(ctx: &Context<'_>, index: usize) -> Option<(Attempt, FallbackClass)> {
    let text = ctx.text;
    let tokens = ctx.tokens;
    let token = tokens[index];
    let source = token.text;
    if let Some(percent) = numeric::percent(source) {
        return Some((
            (
                percent.map(|(number, case)| Value::Percent(number, case)),
                index,
            ),
            FallbackClass::Percent,
        ));
    }
    {
        let base = source.split(['\'', '’']).next().unwrap_or(source);
        if (base.ends_with('.') || source.contains(['\'', '’']))
            && let Some(number) = Numeric::parse(source, false)
        {
            return Some(((Ok(Value::Numeric(number)), index), FallbackClass::Number));
        }
    }
    let roman_base = source
        .split(['\'', '’'])
        .next()
        .unwrap_or(source)
        .trim_end_matches('.');
    if !roman_base.is_empty() && roman_base.bytes().all(|b| b"IVXLCDM".contains(&b)) {
        let contextual = source.ends_with('.')
            && tokens.get(index + 1).is_some_and(|next| {
                whitespace_between(text, token.range.end, next.range.start)
                    && (lexicon::lookup_key(next.text) == "yüzyıl"
                        || (lexicon::lookup_key(next.text) == "dünya"
                            && tokens.get(index + 2).is_some_and(|last| {
                                lexicon::lookup_key(last.text) == "savaşı"
                                    && whitespace_between(text, next.range.end, last.range.start)
                            })))
            });
        return Some((
            (
                if contextual {
                    Numeric::roman(source)
                        .map(Value::Roman)
                        .ok_or(IssueCategory::InvalidExpression)
                } else {
                    Err(IssueCategory::Ambiguous)
                },
                index,
            ),
            FallbackClass::Roman,
        ));
    }

    None
}