rishe 0.4.0

Rishe (ریشه): High-performance Persian NLP engine in Rust — tokenization, SBD, lemmatization, ezafe detection, informal→formal normalization, stemming, spell checking, embeddings
Documentation
const ZWNJ: &str = "\u{200C}";

pub struct Inflector;

impl Inflector {
    pub fn comparative(adj: &str) -> String {
        match irregular(adj) {
            Some((comparative, _)) => comparative.to_string(),
            None => join(adj, "تر"),
        }
    }

    pub fn superlative(adj: &str) -> String {
        match irregular(adj) {
            Some((_, superlative)) => superlative.to_string(),
            None => join(adj, "ترین"),
        }
    }

    pub fn plural(noun: &str) -> String {
        join(noun, "ها")
    }

    pub fn possessive(noun: &str, person: usize) -> Option<String> {
        let suffixes = ["م", "ت", "ش", "مان", "تان", "شان"];
        suffixes.get(person)?;

        if noun.ends_with('ه') {
            if person < 3 {
                let suffix = ["ام", "ات", "اش"][person];
                return Some(format!("{noun}{ZWNJ}{suffix}"));
            }
            return Some(format!("{noun}{ZWNJ}{}", suffixes[person]));
        }

        Some(format!("{noun}{}", suffixes[person]))
    }
}

fn irregular(adj: &str) -> Option<(&'static str, &'static str)> {
    match adj {
        "خوب" => Some(("بهتر", "بهترین")),
        "کم" => Some(("کمتر", "کمترین")),
        "بیش" => Some(("بیشتر", "بیشترین")),
        "مه" => Some(("مهتر", "مهترین")),
        "به" => Some(("بهتر", "بهترین")),
        _ => None,
    }
}

fn join(word: &str, suffix: &str) -> String {
    if word.chars().last().is_some_and(|c| "اأآودذرزژ".contains(c)) {
        format!("{word}{suffix}")
    } else {
        format!("{word}{ZWNJ}{suffix}")
    }
}