652 lines
26 KiB
EmacsLisp
652 lines
26 KiB
EmacsLisp
// 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 "<stem>" 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)
|
||
}
|