// morphology-sa.el - Sanskrit morphology for the NLG engine. // // Implements Sanskrit verb conjugation and noun declension using IAST // transliteration as the primary form. Designed as a companion to // morphology.el and called by the engine when the language profile code // is "sa". // // Language profile: code=sa, name=Sanskrit, morph_type=fusional, // word_order=SOV, question_strategy=intonation, script=devanagari, // family=indo-aryan. // // Verb conjugation covered: // Tenses: present (laṭ), past (imperfect laṅ), future (lṛṭ) // Persons: first/second/third × singular/plural // (dual is treated as plural throughout — see sa_slot) // Classes: Class 1 (bhū-adi, stem + a + endings) as the regular path // Irregulars: as, bhū, gam, dṛś, vad, kṛ (the core NLG vocabulary) // Canonical map: "be" -> "as" // // Noun declension covered: // Cases: nominative, accusative, instrumental, dative, ablative, // genitive, locative, vocative (all 8 Sanskrit cases) // Stem types: a-stem masculine (paradigm: deva), // ā-stem feminine (paradigm: devī) // Numbers: singular, plural (dual collapsed to plural) // // Sanskrit has no articles. sa_noun_phrase returns the declined noun // directly. // // Notes on IAST diacritics used throughout this file: // ā ī ū — long vowels // ṛ — vocalic r // ṃ — anusvāra (nasalisation / homorganic nasal) // ḥ — visarga (final breath) // ś ṣ — palatal / retroflex sibilants // ṭ ḍ ṇ — retroflex stops and nasal // ñ — palatal nasal // Sandhi is intentionally suppressed — forms are returned in their // isolated (pausa) shapes so the NLG realizer can apply sandhi later. // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with) // ── String helpers ───────────────────────────────────────────────────────────── fn sa_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn sa_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) } // ── Person/number slot ───────────────────────────────────────────────────────── // // Maps person × number to a 0-based index into paradigm arrays. // // 0 = 1st singular (uttama eka) // 1 = 2nd singular (madhyama eka) // 2 = 3rd singular (prathama eka) // 3 = 1st plural (uttama bahu) // 4 = 2nd plural (madhyama bahu) // 5 = 3rd plural (prathama bahu) // // Sanskrit has a dual number but for NLG simplicity the dual is collapsed: // "dual" inputs return the same slot as "plural". Forms in this file // therefore carry plural endings even when the dual was requested. fn sa_slot(person: String, number: String) -> Int { if str_eq(person, "first") { if str_eq(number, "singular") { return 0 } return 3 } if str_eq(person, "second") { if str_eq(number, "singular") { return 1 } return 4 } // third person if str_eq(number, "singular") { return 2 } return 5 } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // Semantic-layer canonical English labels are mapped to IAST dictionary // entries before conjugation. The dictionary entry is then looked up in // the irregular table; unknown entries fall through to the Class-1 path. fn sa_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "as" } if str_eq(verb, "become") { return "bhu" } if str_eq(verb, "go") { return "gam" } if str_eq(verb, "see") { return "drs" } if str_eq(verb, "speak") { return "vad" } if str_eq(verb, "say") { return "vad" } if str_eq(verb, "do") { return "kr" } if str_eq(verb, "make") { return "kr" } return verb } // ── Irregular verb: as (to be) ──────────────────────────────────────────────── // // Present (laṭ) parasmaipada: // 1sg asmi 2sg asi 3sg asti // 1pl smaḥ 2pl stha 3pl santi // // Imperfect (laṅ) parasmaipada: // 1sg āsam 2sg āsīḥ 3sg āsīt // 1pl āsma 2pl āsta 3pl āsan fn sa_as_present(slot: Int) -> String { if slot == 0 { return "asmi" } if slot == 1 { return "asi" } if slot == 2 { return "asti" } if slot == 3 { return "smaḥ" } if slot == 4 { return "stha" } return "santi" } fn sa_as_past(slot: Int) -> String { if slot == 0 { return "āsam" } if slot == 1 { return "āsīḥ" } if slot == 2 { return "āsīt" } if slot == 3 { return "āsma" } if slot == 4 { return "āsta" } return "āsan" } // Future (lṛṭ) of as: bhaviṣyāmi series (uses bhū as suppletive stem) fn sa_as_future(slot: Int) -> String { if slot == 0 { return "bhaviṣyāmi" } if slot == 1 { return "bhaviṣyasi" } if slot == 2 { return "bhaviṣyati" } if slot == 3 { return "bhaviṣyāmaḥ" } if slot == 4 { return "bhaviṣyatha" } return "bhaviṣyanti" } // ── Irregular verb: bhū (to be, to become) ──────────────────────────────────── // // Class 1; present stem bho → bhava (guṇa of u before -a-). // // Present (laṭ): // 1sg bhavāmi 2sg bhavasi 3sg bhavati // 1pl bhavāmaḥ 2pl bhavatha 3pl bhavanti // // Imperfect (laṅ): // 1sg abhavam 2sg abhavaḥ 3sg abhavat // 1pl abhavāma 2pl abhavata 3pl abhavan // // Future (lṛṭ): regular from bhaviṣya- fn sa_bhu_present(slot: Int) -> String { if slot == 0 { return "bhavāmi" } if slot == 1 { return "bhavasi" } if slot == 2 { return "bhavati" } if slot == 3 { return "bhavāmaḥ" } if slot == 4 { return "bhavatha" } return "bhavanti" } fn sa_bhu_past(slot: Int) -> String { if slot == 0 { return "abhavam" } if slot == 1 { return "abhavaḥ" } if slot == 2 { return "abhavat" } if slot == 3 { return "abhavāma" } if slot == 4 { return "abhavata" } return "abhavan" } fn sa_bhu_future(slot: Int) -> String { if slot == 0 { return "bhaviṣyāmi" } if slot == 1 { return "bhaviṣyasi" } if slot == 2 { return "bhaviṣyati" } if slot == 3 { return "bhaviṣyāmaḥ" } if slot == 4 { return "bhaviṣyatha" } return "bhaviṣyanti" } // ── Irregular verb: gam (to go) ─────────────────────────────────────────────── // // Historically Class 1 with the present stem gaccha- (inserted -ccha-). // // Present (laṭ): // gacchāmi gacchasi gacchati // gacchāmaḥ gacchatha gacchanti // // Imperfect (laṅ): augmented agaccha- // // Future: gamiṣyati series fn sa_gam_present(slot: Int) -> String { if slot == 0 { return "gacchāmi" } if slot == 1 { return "gacchasi" } if slot == 2 { return "gacchati" } if slot == 3 { return "gacchāmaḥ" } if slot == 4 { return "gacchatha" } return "gacchanti" } fn sa_gam_past(slot: Int) -> String { if slot == 0 { return "agaccham" } if slot == 1 { return "agacchaḥ" } if slot == 2 { return "agacchat" } if slot == 3 { return "agacchāma" } if slot == 4 { return "agacchata" } return "agacchan" } fn sa_gam_future(slot: Int) -> String { if slot == 0 { return "gamiṣyāmi" } if slot == 1 { return "gamiṣyasi" } if slot == 2 { return "gamiṣyati" } if slot == 3 { return "gamiṣyāmaḥ" } if slot == 4 { return "gamiṣyatha" } return "gamiṣyanti" } // ── Irregular verb: dṛś (to see) ───────────────────────────────────────────── // // Suppletive present stem paśya- (Class 4 / ātmanepada suppletive). // Used in the active (parasmaipada) sense throughout for NLG simplicity. // // Present: paśyāmi paśyasi paśyati paśyāmaḥ paśyatha paśyanti // Imperfect: apaśyam series // Future: drakṣyati series fn sa_drs_present(slot: Int) -> String { if slot == 0 { return "paśyāmi" } if slot == 1 { return "paśyasi" } if slot == 2 { return "paśyati" } if slot == 3 { return "paśyāmaḥ" } if slot == 4 { return "paśyatha" } return "paśyanti" } fn sa_drs_past(slot: Int) -> String { if slot == 0 { return "apaśyam" } if slot == 1 { return "apaśyaḥ" } if slot == 2 { return "apaśyat" } if slot == 3 { return "apaśyāma" } if slot == 4 { return "apaśyata" } return "apaśyan" } fn sa_drs_future(slot: Int) -> String { if slot == 0 { return "drakṣyāmi" } if slot == 1 { return "drakṣyasi" } if slot == 2 { return "drakṣyati" } if slot == 3 { return "drakṣyāmaḥ" } if slot == 4 { return "drakṣyatha" } return "drakṣyanti" } // ── Irregular verb: vad (to speak, to say) ──────────────────────────────────── // // Class 1; present stem vada-. // // Present: vadāmi vadasi vadati vadāmaḥ vadatha vadanti // Imperfect: avadam series // Future: vadiṣyati series fn sa_vad_present(slot: Int) -> String { if slot == 0 { return "vadāmi" } if slot == 1 { return "vadasi" } if slot == 2 { return "vadati" } if slot == 3 { return "vadāmaḥ" } if slot == 4 { return "vadatha" } return "vadanti" } fn sa_vad_past(slot: Int) -> String { if slot == 0 { return "avadam" } if slot == 1 { return "avadaḥ" } if slot == 2 { return "avadat" } if slot == 3 { return "avadāma" } if slot == 4 { return "avadata" } return "avadan" } fn sa_vad_future(slot: Int) -> String { if slot == 0 { return "vadiṣyāmi" } if slot == 1 { return "vadiṣyasi" } if slot == 2 { return "vadiṣyati" } if slot == 3 { return "vadiṣyāmaḥ" } if slot == 4 { return "vadiṣyatha" } return "vadiṣyanti" } // ── Irregular verb: kṛ (to do, to make) ────────────────────────────────────── // // Class 8 (tanādi); highly irregular. Present stem karo- (sg) / kuru- (pl). // // Present: // 1sg karomi 2sg karoṣi 3sg karoti // 1pl kurmaḥ 2pl kurutha 3pl kurvanti // // Imperfect: akaro- / akuru- // Future: kariṣyati series fn sa_kr_present(slot: Int) -> String { if slot == 0 { return "karomi" } if slot == 1 { return "karoṣi" } if slot == 2 { return "karoti" } if slot == 3 { return "kurmaḥ" } if slot == 4 { return "kurutha" } return "kurvanti" } fn sa_kr_past(slot: Int) -> String { if slot == 0 { return "akaravam" } if slot == 1 { return "akarodaḥ" } if slot == 2 { return "akarot" } if slot == 3 { return "akurma" } if slot == 4 { return "akuruta" } return "akurvan" } fn sa_kr_future(slot: Int) -> String { if slot == 0 { return "kariṣyāmi" } if slot == 1 { return "kariṣyasi" } if slot == 2 { return "kariṣyati" } if slot == 3 { return "kariṣyāmaḥ" } if slot == 4 { return "kariṣyatha" } return "kariṣyanti" } // ── Class-1 regular conjugation (bhū-adi) ───────────────────────────────────── // // The thematic class: root → guṇa-strengthened root → + a + personal ending. // Present endings (parasmaipada): // 1sg -āmi 2sg -asi 3sg -ati // 1pl -āmaḥ 2pl -atha 3pl -anti // // Imperfect (laṅ) = augment a- + stem + imperfect endings: // 1sg -am 2sg -aḥ 3sg -at // 1pl -āma 2pl -ata 3pl -an // // Future (lṛṭ) = stem + iṣya + present personal endings // // The caller supplies the present-tense verbal stem (e.g. "bodha" for // "to know/wake"). We do not derive stems automatically from roots for // arbitrary input — only the known irregular verbs above have root→stem // derivation. Unknown verbs are conjugated as if their input IS the stem. fn sa_class1_present_ending(slot: Int) -> String { if slot == 0 { return "āmi" } if slot == 1 { return "asi" } if slot == 2 { return "ati" } if slot == 3 { return "āmaḥ" } if slot == 4 { return "atha" } return "anti" } fn sa_class1_past_ending(slot: Int) -> String { if slot == 0 { return "am" } if slot == 1 { return "aḥ" } if slot == 2 { return "at" } if slot == 3 { return "āma" } if slot == 4 { return "ata" } return "an" } fn sa_class1_future_ending(slot: Int) -> String { if slot == 0 { return "iṣyāmi" } if slot == 1 { return "iṣyasi" } if slot == 2 { return "iṣyati" } if slot == 3 { return "iṣyāmaḥ" } if slot == 4 { return "iṣyatha" } return "iṣyanti" } fn sa_class1_conjugate(stem: String, tense: String, slot: Int) -> String { if str_eq(tense, "present") { return stem + sa_class1_present_ending(slot) } if str_eq(tense, "past") { return "a" + stem + sa_class1_past_ending(slot) } if str_eq(tense, "future") { return stem + sa_class1_future_ending(slot) } return stem } // ── sa_conjugate: main conjugation entry point ──────────────────────────────── // // verb: IAST form (e.g. "gam", "as") or English canonical ("be", "go") // tense: "present" | "past" | "future" // person: "first" | "second" | "third" // number: "singular" | "plural" (or "dual" — treated as plural) // // Returns the inflected form in IAST. Falls back to the verb stem for any // unknown input rather than crashing. fn sa_conjugate(verb: String, tense: String, person: String, number: String) -> String { let v: String = sa_map_canonical(verb) let slot: Int = sa_slot(person, number) // ── Irregular: as (to be) ───────────────────────────────────────────────── if str_eq(v, "as") { if str_eq(tense, "present") { return sa_as_present(slot) } if str_eq(tense, "past") { return sa_as_past(slot) } if str_eq(tense, "future") { return sa_as_future(slot) } return v } // ── Irregular: bhū (to be/become) ──────────────────────────────────────── if str_eq(v, "bhu") { if str_eq(tense, "present") { return sa_bhu_present(slot) } if str_eq(tense, "past") { return sa_bhu_past(slot) } if str_eq(tense, "future") { return sa_bhu_future(slot) } return v } // ── Irregular: gam (to go) ──────────────────────────────────────────────── if str_eq(v, "gam") { if str_eq(tense, "present") { return sa_gam_present(slot) } if str_eq(tense, "past") { return sa_gam_past(slot) } if str_eq(tense, "future") { return sa_gam_future(slot) } return v } // ── Irregular: dṛś / drs (to see) ──────────────────────────────────────── if str_eq(v, "drs") { if str_eq(tense, "present") { return sa_drs_present(slot) } if str_eq(tense, "past") { return sa_drs_past(slot) } if str_eq(tense, "future") { return sa_drs_future(slot) } return v } // ── Irregular: vad (to speak/say) ──────────────────────────────────────── if str_eq(v, "vad") { if str_eq(tense, "present") { return sa_vad_present(slot) } if str_eq(tense, "past") { return sa_vad_past(slot) } if str_eq(tense, "future") { return sa_vad_future(slot) } return v } // ── Irregular: kṛ / kr (to do/make) ────────────────────────────────────── if str_eq(v, "kr") { if str_eq(tense, "present") { return sa_kr_present(slot) } if str_eq(tense, "past") { return sa_kr_past(slot) } if str_eq(tense, "future") { return sa_kr_future(slot) } return v } // ── Regular Class-1 fallback ────────────────────────────────────────────── // Treat the supplied string as a present-tense verbal stem and apply the // standard thematic endings. This handles any verb the caller passes in // the form "" without a recognised root tag. return sa_class1_conjugate(v, tense, slot) } // ── a-stem masculine paradigm (deva) ────────────────────────────────────────── // // Stems of the deva- type are the most numerous Sanskrit noun class. // All eight cases × singular and plural are encoded below. // // Singular: // nom deva-ḥ → "devaḥ" (visarga in citation; use bare form here) // acc deva-m → "devam" // ins deva-na → "devena" (guṇa: a+n→ena) // dat deva-āya → "devāya" // abl deva-āt → "devāt" // gen deva-sya → "devasya" // loc deva-e → "deve" // voc deva → "deva" (bare stem) // // Plural: // nom deva-āḥ → "devāḥ" // acc deva-ān → "devān" // ins deva-aiḥ → "devaiḥ" // dat deva-bhyaḥ → "devebhyaḥ" (with connecting -e-) // abl deva-bhyaḥ → "devebhyaḥ" (dat=abl in plural for a-stems) // gen deva-ānām → "devānām" // loc deva-eṣu → "deveṣu" // voc deva-āḥ → "devāḥ" fn sa_decline_a_stem_sg(stem: String, gram_case: String) -> String { if str_eq(gram_case, "nominative") { return stem + "ḥ" } if str_eq(gram_case, "accusative") { return stem + "m" } if str_eq(gram_case, "instrumental") { return stem + "ena" } if str_eq(gram_case, "dative") { return stem + "āya" } if str_eq(gram_case, "ablative") { return stem + "āt" } if str_eq(gram_case, "genitive") { return stem + "sya" } if str_eq(gram_case, "locative") { return stem + "e" } if str_eq(gram_case, "vocative") { return stem } return stem } fn sa_decline_a_stem_pl(stem: String, gram_case: String) -> String { if str_eq(gram_case, "nominative") { return stem + "āḥ" } if str_eq(gram_case, "accusative") { return stem + "ān" } if str_eq(gram_case, "instrumental") { return stem + "aiḥ" } if str_eq(gram_case, "dative") { return stem + "ebhyaḥ" } if str_eq(gram_case, "ablative") { return stem + "ebhyaḥ" } if str_eq(gram_case, "genitive") { return stem + "ānām" } if str_eq(gram_case, "locative") { return stem + "eṣu" } if str_eq(gram_case, "vocative") { return stem + "āḥ" } return stem + "āḥ" } // ── ā-stem feminine paradigm (devī / nārī type) ─────────────────────────────── // // ā-stems are the primary feminine class. Paradigm for nārī (woman): // // Singular: // nom nārī acc nārīm ins nāryā // dat nāryai abl nāryāḥ gen nāryāḥ // loc nāryām voc nāri // // Plural: // nom nāryaḥ acc nārīḥ ins nārībhiḥ // dat nārībhyaḥ abl nārībhyaḥ gen nārīṇām // loc nārīṣu voc nāryaḥ // // For input the caller passes the nominative singular form (e.g. "nārī"). // We strip the final ī to obtain the stem for oblique formation. fn sa_decline_aa_stem_sg(stem: String, gram_case: String) -> String { if str_eq(gram_case, "nominative") { return stem + "ī" } if str_eq(gram_case, "accusative") { return stem + "īm" } if str_eq(gram_case, "instrumental") { return stem + "yā" } if str_eq(gram_case, "dative") { return stem + "yai" } if str_eq(gram_case, "ablative") { return stem + "yāḥ" } if str_eq(gram_case, "genitive") { return stem + "yāḥ" } if str_eq(gram_case, "locative") { return stem + "yām" } if str_eq(gram_case, "vocative") { return stem + "i" } return stem + "ī" } fn sa_decline_aa_stem_pl(stem: String, gram_case: String) -> String { if str_eq(gram_case, "nominative") { return stem + "yaḥ" } if str_eq(gram_case, "accusative") { return stem + "īḥ" } if str_eq(gram_case, "instrumental") { return stem + "ībhiḥ" } if str_eq(gram_case, "dative") { return stem + "ībhyaḥ" } if str_eq(gram_case, "ablative") { return stem + "ībhyaḥ" } if str_eq(gram_case, "genitive") { return stem + "īṇām" } if str_eq(gram_case, "locative") { return stem + "īṣu" } if str_eq(gram_case, "vocative") { return stem + "yaḥ" } return stem + "yaḥ" } // ── Stem-type detection ──────────────────────────────────────────────────────── // // Infers the stem class from the nominative singular form supplied by the // caller. Sanskrit stems are conventionally cited in their nom-sg form. // // Heuristics (sufficient for the NLG working vocabulary): // ends in ā -> ā-stem feminine // ends in ī -> ā-stem feminine (long ī subtype) // ends in aḥ -> a-stem masculine (visarga ending from -as) // ends in a -> treat as a-stem masculine (bare stem supplied) // otherwise -> return base form as-is (unknown/consonant stem fallback) fn sa_stem_type(noun: String) -> String { if sa_str_ends(noun, "ā") { return "aa" } if sa_str_ends(noun, "ī") { return "aa" } if sa_str_ends(noun, "aḥ") { return "a" } if sa_str_ends(noun, "a") { return "a" } return "unknown" } // sa_extract_stem: strip the nominative-singular suffix to get the bare stem. // // a-stem "deva" -> "dev" (if ends in bare -a; strip final char) // "devaḥ" -> "dev" (strip -aḥ = 2 Unicode code-points … but // since ḥ is multi-byte we treat "aḥ" as suffix) // ā-stem "nārī" -> "nār" (strip -ī) // "nārā" -> "nār" (strip -ā) // // Because IAST diacritics are multi-byte UTF-8 the raw byte lengths do not // equal character counts. The engine's str_len / str_slice operate on bytes. // Rather than counting UTF-8 bytes for each diacritic here we take a simpler // path: we look for a known suffix and drop a fixed number of characters. // For the characters used: // ā = 2 bytes (U+0101) // ī = 2 bytes (U+012B) // ḥ = 3 bytes (U+1E25) // So "aḥ" = 1 + 3 = 4 bytes; bare "a" = 1 byte; "ā" = 2 bytes; "ī" = 2 bytes. // // The function uses str_ends_with for detection, then str_slice to strip. fn sa_extract_stem(noun: String, stype: String) -> String { let n: Int = str_len(noun) if str_eq(stype, "a") { // Check whether it ends in "aḥ" (visarga form): 4 bytes to strip if sa_str_ends(noun, "aḥ") { return str_slice(noun, 0, n - 4) } // Otherwise bare -a: 1 byte return str_slice(noun, 0, n - 1) } if str_eq(stype, "aa") { // ī or ā: both 2 bytes return str_slice(noun, 0, n - 2) } return noun } // ── sa_decline: main declension entry point ─────────────────────────────────── // // noun: nominative singular IAST form (e.g. "deva", "devaḥ", "nārī") // gram_case: "nominative" | "accusative" | "instrumental" | "dative" | // "ablative" | "genitive" | "locative" | "vocative" // number: "singular" | "plural" (dual → plural) // // Returns the inflected form. Unknown stems return the noun unchanged. fn sa_decline(noun: String, gram_case: String, number: String) -> String { let stype: String = sa_stem_type(noun) if str_eq(stype, "a") { let stem: String = sa_extract_stem(noun, "a") if str_eq(number, "singular") { return sa_decline_a_stem_sg(stem, gram_case) } return sa_decline_a_stem_pl(stem, gram_case) } if str_eq(stype, "aa") { let stem: String = sa_extract_stem(noun, "aa") if str_eq(number, "singular") { return sa_decline_aa_stem_sg(stem, gram_case) } return sa_decline_aa_stem_pl(stem, gram_case) } // Unknown stem class: return noun unchanged rather than producing garbage return noun } // ── sa_noun_phrase: noun phrase builder ─────────────────────────────────────── // // Sanskrit has no articles — neither definite nor indefinite. The definite // parameter is accepted for interface compatibility with other language modules // but has no effect on the output. // // Sanskrit expresses definiteness and referential status through word order, // demonstratives (etad / tad), and discourse context — none of which is the // responsibility of the morphology module. // // noun: nominative singular IAST form // gram_case: "nominative" | "accusative" | "instrumental" | "dative" | // "ablative" | "genitive" | "locative" | "vocative" // number: "singular" | "plural" // definite: ignored fn sa_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { return sa_decline(noun, gram_case, number) }