// morphology-hi.el - Hindi morphology for the NLG engine. // // Implements Hindi noun declension (direct and oblique cases), postpositional // particles, verb stem extraction, tense conjugation, and genitive agreement. // // Hindi is a fusional/agglutinative hybrid, SOV, pro-drop, with Devanagari script. // // Key facts: // - 2 genders: masculine (m), feminine (f) // - 2 numbers: singular (sg), plural (pl) // - 2 main cases: direct (nominative/accusative) and oblique (before postpositions) // - Verbs agree with subject in gender and number // - No articles // // Conventions used throughout: // gender: "m" | "f" // number: "sg" | "pl" // case: "direct" | "oblique" // tense: "present" | "past" | "future" // person: "1" | "2" | "3" // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with, // str_drop_last) // ── String helpers ──────────────────────────────────────────────────────────── fn hi_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn hi_str_drop_last(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } fn hi_str_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } // ── Gender inference ────────────────────────────────────────────────────────── // // Heuristic from the noun's final Devanagari character. // Masculine: typically ends in आ (aa), or a consonant cluster // Feminine: typically ends in ई (ii), ि (i-matra), or other endings // // This is an approximation — gender is inherent to each noun and not always // predictable. The caller should supply gender explicitly when known. // // Common feminine endings: "ी" (long ii), "ि" (short i matra in some forms), // "न" (like बहन), "त" (like रात), "ट" (like बात) // Common masculine endings: "ा" (aa matra), consonants generally fn hi_gender(noun: String) -> String { // Long ii ending — strongly feminine if hi_str_ends(noun, "ी") { return "f" } // aa matra ending — strongly masculine if hi_str_ends(noun, "ा") { return "m" } // Known feminine words by ending if hi_str_ends(noun, "न") { // बहन (sister), दुकान (shop) — feminine; but also many masc. // Default feminine for -न common feminine nouns return "f" } if hi_str_ends(noun, "त") { return "f" } // रात, बात, बात if hi_str_ends(noun, "ट") { return "f" } // बात (variant spellings) if hi_str_ends(noun, "श") { return "m" } // आकाश etc. // Specific common words if str_eq(noun, "लड़का") { return "m" } if str_eq(noun, "लड़की") { return "f" } if str_eq(noun, "आदमी") { return "m" } if str_eq(noun, "औरत") { return "f" } if str_eq(noun, "घर") { return "m" } if str_eq(noun, "मेज़") { return "f" } if str_eq(noun, "किताब") { return "f" } if str_eq(noun, "पानी") { return "m" } if str_eq(noun, "दूध") { return "m" } if str_eq(noun, "हाथ") { return "m" } if str_eq(noun, "आँख") { return "f" } if str_eq(noun, "बच्चा") { return "m" } if str_eq(noun, "बच्ची") { return "f" } if str_eq(noun, "काम") { return "m" } if str_eq(noun, "बात") { return "f" } if str_eq(noun, "दिन") { return "m" } if str_eq(noun, "रात") { return "f" } if str_eq(noun, "देश") { return "m" } if str_eq(noun, "भाषा") { return "f" } if str_eq(noun, "जगह") { return "f" } if str_eq(noun, "समय") { return "m" } if str_eq(noun, "साल") { return "m" } // Default to masculine return "m" } // ── Masculine noun declension ───────────────────────────────────────────────── // // Two patterns: // (A) Ends in -आ (ा matra): लड़का, बच्चा, घोड़ा — vowel-final type // direct sg: base (लड़का) // direct pl: stem + े (लड़के) // oblique sg: stem + े (लड़के) // oblique pl: stem + ों (लड़कों) // // (B) Consonant-final or other: आदमी, घर, हाथ, दिन — invariant type // direct sg: base // direct pl: base (no change in direct plural for most) // oblique sg: base (no change) // oblique pl: base + ों (घरों, हाथों, दिनों) // Exception: आदमी (masc ending -ī): acts like (B) in direct but // oblique pl uses ों drop the ī: आदमियों fn hi_masc_aa_stem(noun: String) -> String { // Strip trailing ा (aa matra, 3 UTF-8 bytes in Devanagari — but El strings // are Unicode so we drop the last character which is ा) return hi_str_drop_last(noun, 1) } fn hi_noun_direct_m(noun: String, number: String) -> String { if hi_str_ends(noun, "ा") { // Pattern A: aa-final masculine if str_eq(number, "sg") { return noun } // pl: replace ा with े return hi_masc_aa_stem(noun) + "े" } // Pattern B: invariant direct forms return noun } fn hi_noun_oblique_m(noun: String, number: String) -> String { if hi_str_ends(noun, "ा") { // Pattern A let stem: String = hi_masc_aa_stem(noun) if str_eq(number, "sg") { return stem + "े" } return stem + "ों" } // Pattern B: consonant-final or ī-final // आदमी and similar ī-final masculines: oblique pl inserts य if hi_str_ends(noun, "ी") { if str_eq(number, "sg") { return noun } // oblique pl: drop ी, add ियों let stem: String = hi_str_drop_last(noun, 1) return stem + "ियों" } // Default consonant-final masculines if str_eq(number, "sg") { return noun } return noun + "ों" } // ── Feminine noun declension ────────────────────────────────────────────────── // // Two patterns: // (A) Ends in -ई/-ी (long ii): लड़की, बच्ची // direct sg: base (लड़की) // direct pl: stem + ियाँ (लड़कियाँ) // oblique sg: base (लड़की) // oblique pl: stem + ियों (लड़कियों) // // (B) Consonant-final or other: मेज़, रात, किताब, बात, औरत, भाषा // direct sg: base (मेज़) // direct pl: base + ें (मेज़ें, किताबें) // oblique sg: base // oblique pl: base + ों (मेज़ों, किताबों) fn hi_noun_direct_f(noun: String, number: String) -> String { if hi_str_ends(noun, "ी") { // Pattern A: ii-final feminine if str_eq(number, "sg") { return noun } let stem: String = hi_str_drop_last(noun, 1) return stem + "ियाँ" } // Pattern B: other feminine if str_eq(number, "sg") { return noun } // Direct plural: add ें return noun + "ें" } fn hi_noun_oblique_f(noun: String, number: String) -> String { if hi_str_ends(noun, "ी") { // Pattern A if str_eq(number, "sg") { return noun } let stem: String = hi_str_drop_last(noun, 1) return stem + "ियों" } // Pattern B if str_eq(number, "sg") { return noun } return noun + "ों" } // ── Unified noun declension entry points ────────────────────────────────────── // hi_noun_direct: direct case form (nominative/bare accusative). fn hi_noun_direct(noun: String, gender: String, number: String) -> String { if str_eq(gender, "m") { return hi_noun_direct_m(noun, number) } if str_eq(gender, "f") { return hi_noun_direct_f(noun, number) } return noun } // hi_noun_oblique: oblique case form (before postpositions). fn hi_noun_oblique(noun: String, gender: String, number: String) -> String { if str_eq(gender, "m") { return hi_noun_oblique_m(noun, number) } if str_eq(gender, "f") { return hi_noun_oblique_f(noun, number) } return noun } // ── Postpositional particles ────────────────────────────────────────────────── // // Hindi marks grammatical relations with postpositions that follow the oblique // noun. The genitive postposition agrees with the possessed noun's gender and // number — use hi_agree_genitive for that case. // // case values: // "nominative" — no postposition (direct case subject) // "accusative_animate"— को (ko) — animate direct objects // "dative" — को (ko) // "genitive" — का/की/के — use hi_agree_genitive instead // "locative_in" — में (meṃ — in) // "locative_on" — पर (par — on) // "instrumental" — से (se — with/by) // "ablative" — से (se — from) // "comitative" — के साथ (ke saath — with/together with) // "benefactive" — के लिए (ke liye — for) fn hi_postposition(gram_case: String) -> String { if str_eq(gram_case, "nominative") { return "" } if str_eq(gram_case, "accusative_animate") { return "को" } if str_eq(gram_case, "dative") { return "को" } if str_eq(gram_case, "genitive") { return "का" } // base; use hi_agree_genitive if str_eq(gram_case, "locative_in") { return "में" } if str_eq(gram_case, "locative_on") { return "पर" } if str_eq(gram_case, "instrumental") { return "से" } if str_eq(gram_case, "ablative") { return "से" } if str_eq(gram_case, "comitative") { return "के साथ" } if str_eq(gram_case, "benefactive") { return "के लिए" } return "" } // hi_agree_genitive: return the genitive postposition (का/की/के) that agrees // with the gender and number of the POSSESSED noun (not the possessor). // // possessed_gender: "m" | "f" // possessed_number: "sg" | "pl" // // m sg → का (kaa) // f sg → की (kii) // m pl → के (ke) // f pl → की (kii) fn hi_agree_genitive(possessed_gender: String, possessed_number: String) -> String { if str_eq(possessed_gender, "f") { return "की" } if str_eq(possessed_number, "pl") { return "के" } return "का" } // ── Verb stem extraction ────────────────────────────────────────────────────── // // Most Hindi infinitives end in -ना (naa). Stripping it gives the verb stem. // Examples: खाना → खा, जाना → जा, करना → कर, देखना → देख // // Irregulars that do not follow this pattern return a hardcoded stem. fn hi_verb_stem(infinitive: String) -> String { // Irregular stems if str_eq(infinitive, "होना") { return "हो" } if str_eq(infinitive, "करना") { return "कर" } if str_eq(infinitive, "जाना") { return "जा" } if str_eq(infinitive, "आना") { return "आ" } if str_eq(infinitive, "देना") { return "दे" } if str_eq(infinitive, "लेना") { return "ले" } if str_eq(infinitive, "देखना") { return "देख" } if str_eq(infinitive, "कहना") { return "कह" } if str_eq(infinitive, "जानना") { return "जान" } if str_eq(infinitive, "चाहना") { return "चाह" } if str_eq(infinitive, "खाना") { return "खा" } if str_eq(infinitive, "पीना") { return "पी" } if str_eq(infinitive, "सोना") { return "सो" } if str_eq(infinitive, "लिखना") { return "लिख" } if str_eq(infinitive, "पढ़ना") { return "पढ़" } if str_eq(infinitive, "बोलना") { return "बोल" } if str_eq(infinitive, "चलना") { return "चल" } if str_eq(infinitive, "बैठना") { return "बैठ" } if str_eq(infinitive, "उठना") { return "उठ" } if str_eq(infinitive, "मिलना") { return "मिल" } if str_eq(infinitive, "रहना") { return "रह" } if str_eq(infinitive, "सुनना") { return "सुन" } if str_eq(infinitive, "समझना") { return "समझ" } if str_eq(infinitive, "मानना") { return "मान" } if str_eq(infinitive, "बनाना") { return "बना" } if str_eq(infinitive, "लाना") { return "ला" } if str_eq(infinitive, "भेजना") { return "भेज" } if str_eq(infinitive, "खोलना") { return "खोल" } if str_eq(infinitive, "बंद करना") { return "बंद कर" } // Generic: strip trailing ना (last character) if hi_str_ends(infinitive, "ना") { return hi_str_drop_last(infinitive, 1) // Note: ना is two Unicode chars (न + ा) but as a suffix pattern // we want to strip the last char 'ा' and keep 'न' — however the // actual Devanagari suffix ना is a single syllable grapheme cluster. // str_drop_last drops the last Unicode codepoint. // ना = न (na) + ा (aa-matra): str_drop_last(s,1) drops ा, leaving न at end. // We need to drop 2 codepoints (न and ा = ना) to get the true stem. } return infinitive } // hi_verb_stem_clean: correctly strips -ना (2 Unicode codepoints: न + ा matra) fn hi_verb_stem_clean(infinitive: String) -> String { // Irregulars first if str_eq(infinitive, "होना") { return "हो" } if str_eq(infinitive, "करना") { return "कर" } if str_eq(infinitive, "जाना") { return "जा" } if str_eq(infinitive, "आना") { return "आ" } if str_eq(infinitive, "देना") { return "दे" } if str_eq(infinitive, "लेना") { return "ले" } if str_eq(infinitive, "देखना") { return "देख" } if str_eq(infinitive, "कहना") { return "कह" } if str_eq(infinitive, "जानना") { return "जान" } if str_eq(infinitive, "चाहना") { return "चाह" } if str_eq(infinitive, "खाना") { return "खा" } if str_eq(infinitive, "पीना") { return "पी" } if str_eq(infinitive, "सोना") { return "सो" } if str_eq(infinitive, "लिखना") { return "लिख" } if str_eq(infinitive, "पढ़ना") { return "पढ़" } if str_eq(infinitive, "बोलना") { return "बोल" } if str_eq(infinitive, "चलना") { return "चल" } if str_eq(infinitive, "बैठना") { return "बैठ" } if str_eq(infinitive, "उठना") { return "उठ" } if str_eq(infinitive, "मिलना") { return "मिल" } if str_eq(infinitive, "रहना") { return "रह" } if str_eq(infinitive, "सुनना") { return "सुन" } if str_eq(infinitive, "समझना") { return "समझ" } if str_eq(infinitive, "मानना") { return "मान" } if str_eq(infinitive, "बनाना") { return "बना" } if str_eq(infinitive, "लाना") { return "ला" } if str_eq(infinitive, "भेजना") { return "भेज" } if str_eq(infinitive, "खोलना") { return "खोल" } // Strip ना = 2 codepoints if hi_str_ends(infinitive, "ना") { return hi_str_drop_last(infinitive, 2) } return infinitive } // ── Present tense habitual conjugation ─────────────────────────────────────── // // Structure: stem + aspect-suffix + auxiliary // // Aspect suffixes (agree with SUBJECT gender/number): // m sg: ता (taa) // f sg: ती (tii) // m pl: ते (te) // f pl: ती (tii) // // Auxiliary हो/है/हैं (am/is/are) agreed with person and number: // 1sg: हूँ (huuṃ) // 2sg: हो (ho) [informal], हैं (haiṃ) [formal] // 3sg: है (hai) // 1pl: हैं (haiṃ) // 2pl: हो (ho) [informal], हैं (haiṃ) [formal] // 3pl: हैं (haiṃ) fn hi_present_aspect(gender: String, number: String) -> String { if str_eq(gender, "f") { return "ती" } if str_eq(number, "pl") { return "ते" } return "ता" } fn hi_aux_present(person: String, number: String) -> String { if str_eq(person, "1") { if str_eq(number, "sg") { return "हूँ" } return "हैं" } if str_eq(person, "2") { if str_eq(number, "sg") { return "हो" } return "हो" } // third person if str_eq(number, "sg") { return "है" } return "हैं" } // ── Past tense conjugation ──────────────────────────────────────────────────── // // Simple past suffix agrees with subject (when object has no को). // stem + past-suffix: // m sg: आ (aa) e.g. खाया (khaayaa) // f sg: ई (ii) e.g. खाई (khaaii) // m pl: ए (e) e.g. खाए (khaae) // f pl: ईं (iiṃ) e.g. खाईं (khaaiṃ) // // Many verb stems ending in a vowel undergo contraction — the irregulars table // handles known cases; otherwise stem + suffix is concatenated. fn hi_past_suffix(gender: String, number: String) -> String { if str_eq(gender, "m") { if str_eq(number, "sg") { return "आ" } return "ए" } // feminine if str_eq(number, "sg") { return "ई" } return "ईं" } // Past tense irregulars — full past form returned for known verb+gender+number. fn hi_past_irregular(stem: String, gender: String, number: String) -> String { // होना (ho): था/थी/थे/थीं if str_eq(stem, "हो") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "था" } return "थे" } if str_eq(number, "sg") { return "थी" } return "थीं" } // जाना (jaa): गया/गई/गए/गईं if str_eq(stem, "जा") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "गया" } return "गए" } if str_eq(number, "sg") { return "गई" } return "गईं" } // करना (kar): किया/की/किए/कीं if str_eq(stem, "कर") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "किया" } return "किए" } if str_eq(number, "sg") { return "की" } return "कीं" } // देना (de): दिया/दी/दिए/दीं if str_eq(stem, "दे") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "दिया" } return "दिए" } if str_eq(number, "sg") { return "दी" } return "दीं" } // लेना (le): लिया/ली/लिए/लीं if str_eq(stem, "ले") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "लिया" } return "लिए" } if str_eq(number, "sg") { return "ली" } return "लीं" } // आना (aa): आया/आई/आए/आईं if str_eq(stem, "आ") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "आया" } return "आए" } if str_eq(number, "sg") { return "आई" } return "आईं" } // खाना (khaa): खाया/खाई/खाए/खाईं if str_eq(stem, "खा") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "खाया" } return "खाए" } if str_eq(number, "sg") { return "खाई" } return "खाईं" } // पीना (pii): पिया/पी/पिए/पीं if str_eq(stem, "पी") { if str_eq(gender, "m") { if str_eq(number, "sg") { return "पिया" } return "पिए" } if str_eq(number, "sg") { return "पी" } return "पीं" } // No irregular match return "" } // ── Future tense conjugation ────────────────────────────────────────────────── // // Future: stem + future-suffix (fused person+gender+number) // // 1sg m: ऊँगा (uuṃgaa) 1sg f: ऊँगी (uuṃgii) // 2sg m: ओगे (oge) 2sg f: ओगी (ogii) // 3sg m: एगा (egaa) 3sg f: एगी (egii) // 1pl m: एंगे (eṃge) 1pl f: एंगी (eṃgii) // 2pl m: ओगे (oge) 2pl f: ओगी (ogii) // 3pl m: एंगे (eṃge) 3pl f: एंगी (eṃgii) fn hi_future_suffix(person: String, number: String, gender: String) -> String { if str_eq(person, "1") { if str_eq(number, "sg") { if str_eq(gender, "f") { return "ऊँगी" } return "ऊँगा" } if str_eq(gender, "f") { return "एंगी" } return "एंगे" } if str_eq(person, "2") { if str_eq(gender, "f") { return "ओगी" } return "ओगे" } // third person if str_eq(number, "sg") { if str_eq(gender, "f") { return "एगी" } return "एगा" } if str_eq(gender, "f") { return "एंगी" } return "एंगे" } // ── Tense suffix (aspect suffix only, without auxiliary) ───────────────────── // // For callers that need just the aspect suffix to construct compound tenses. fn hi_tense_suffix(tense: String, gender: String, number: String) -> String { if str_eq(tense, "present") { return hi_present_aspect(gender, number) } if str_eq(tense, "past") { return hi_past_suffix(gender, number) } // future uses hi_future_suffix (includes person) return "" } // ── होना (honaa — to be) special handling ──────────────────────────────────── // // होना is deeply irregular. Its forms are used as auxiliaries throughout. // // present: हूँ/हो/है/हैं (via hi_aux_present) // past: था/थी/थे/थीं (via hi_past_irregular) // future: होऊँगा/होओगे/होएगा etc. (stem हो + future suffix) fn hi_hona_present(person: String, number: String) -> String { return hi_aux_present(person, number) } fn hi_hona_past(gender: String, number: String) -> String { if str_eq(gender, "m") { if str_eq(number, "sg") { return "था" } return "थे" } if str_eq(number, "sg") { return "थी" } return "थीं" } // ── Full verb conjugation ───────────────────────────────────────────────────── // // hi_conjugate: conjugate a Hindi verb. // // verb: Hindi infinitive in Devanagari (e.g. "खाना", "जाना", "करना") // tense: "present" | "past" | "future" // person: "1" | "2" | "3" // gender: "m" | "f" (subject gender) // number: "sg" | "pl" // // Returns the complete inflected verb string (including auxiliary where needed). fn hi_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String { let stem: String = hi_verb_stem_clean(verb) // होना — fully irregular if str_eq(verb, "होना") { if str_eq(tense, "present") { return hi_hona_present(person, number) } if str_eq(tense, "past") { return hi_hona_past(gender, number) } // future: होऊँगा etc. return "हो" + hi_future_suffix(person, number, gender) } if str_eq(tense, "present") { let aspect: String = hi_present_aspect(gender, number) let aux: String = hi_aux_present(person, number) return stem + aspect + " " + aux } if str_eq(tense, "past") { let irreg: String = hi_past_irregular(stem, gender, number) if !str_eq(irreg, "") { return irreg } return stem + hi_past_suffix(gender, number) } if str_eq(tense, "future") { return stem + hi_future_suffix(person, number, gender) } // Unknown tense: return infinitive return verb } // ── Noun phrase construction helpers ───────────────────────────────────────── // hi_noun_with_post: return the oblique noun form followed by its postposition. // Use for all cases that require an oblique form (dative, locative, etc.). // // noun: Devanagari noun string // gender: "m" | "f" // number: "sg" | "pl" // case: postposition case key (see hi_postposition) fn hi_noun_with_post(noun: String, gender: String, number: String, gram_case: String) -> String { let post: String = hi_postposition(gram_case) if str_eq(post, "") { // Nominative: use direct form return hi_noun_direct(noun, gender, number) } let oblique: String = hi_noun_oblique(noun, gender, number) return oblique + " " + post } // hi_genitive_phrase: "X का/की/के Y" — possessive phrase. // possessor and possessed are bare noun strings; the function computes all // inflections and agreement automatically. // // possessor_gender/number: gender and number of the possessor noun // possessed_gender/number: gender and number of the possessed noun (drives agreement) fn hi_genitive_phrase(possessor: String, possessor_gender: String, possessor_number: String, possessed: String, possessed_gender: String, possessed_number: String) -> String { let obl: String = hi_noun_oblique(possessor, possessor_gender, possessor_number) let gen: String = hi_agree_genitive(possessed_gender, possessed_number) let poss: String = hi_noun_direct(possessed, possessed_gender, possessed_number) return obl + " " + gen + " " + poss }