Files
el/elp/src/morphology-pi.el
T
will.anderson c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
fix: elb macOS OpenSSL + C master declarations header; add ELP missing imports
elb.el:
- Auto-detect Homebrew OpenSSL (-L$(brew --prefix openssl)/lib) so -lssl
  resolves on macOS without manual flags; no-op on Linux
- Add -include elp-c-decls.h when present in out_dir: resolves undeclared
  cross-module calls in packages like ELP that lack explicit imports

ELP source:
- Add import "morphology.el" to all 29 language morphology modules
- Add language module imports to morphology.el (all langs it dispatches to)
  These were missing since ELP was originally built as a monolithic unit
2026-05-08 19:44:31 -05:00

547 lines
21 KiB
EmacsLisp

// morphology-pi.el - Pali morphology for the NLG engine.
//
// Implements Pali verb conjugation and noun declension using standard IAST-subset
// Latin transliteration. Pali is the liturgical language of Theravada Buddhism
// (canonical form of Middle Indo-Aryan, ca. 3rd century BCE).
//
// Language profile: code=pi, name=Pali, morph_type=fusional, word_order=SOV,
// question_strategy=particle, script=latin-iast, family=indo-aryan.
//
// Verb conjugation covered:
// Tenses: present (bhū-ādi class), aorist (past), future
// Persons: first/second/third x singular/plural (slots 0-5)
// Endings: present -āmi/-asi/-ati/-āma/-atha/-anti
// aorist -iṃ/-i/-i/-imhā/-ittha/-iṃsu
// future -issāmi/-issasi/-issati/-issāma/-issatha/-issanti
// Irregulars: atthi/hoti (be), gacchati (go), passati (see),
// vadati (say), karoti (do)
// Canonical map: "be" -> "hoti"
//
// Noun declension covered:
// a-stem masculine (paradigm: "purisa" man): 8 cases, sg + pl
// ā-stem feminine (paradigm: "kaññā" girl): 8 cases, sg (pl approx.)
// Cases: nominative, accusative, instrumental, dative, ablative,
// genitive, locative, vocative
//
// Articles:
// Pali has no articles. pi_noun_phrase returns the declined noun directly.
//
// Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq)
// String helpers
import "morphology.el"
fn pi_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn pi_drop(s: String, n: Int) -> String {
let len: Int = str_len(s)
if n >= len { return "" }
return str_slice(s, 0, len - n)
}
fn pi_last_char(s: String) -> String {
let n: Int = str_len(s)
if n == 0 { return "" }
return str_slice(s, n - 1, n)
}
// Person/number slot
//
// Maps person x number to a 0-based paradigm slot.
// 0 = 1st singular (ahaṃ)
// 1 = 2nd singular (tvaṃ)
// 2 = 3rd singular (so/sā/taṃ)
// 3 = 1st plural (mayaṃ)
// 4 = 2nd plural (tumhe)
// 5 = 3rd plural (te/tā/tāni)
fn pi_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
if str_eq(number, "singular") { return 2 }
return 5
}
// Present tense endings (bhū-ādi class)
//
// The standard present active endings for the most common Pali verb class:
// 1sg -āmi 2sg -asi 3sg -ati
// 1pl -āma 2pl -atha 3pl -anti
fn pi_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"
}
// Aorist (past) tense endings
//
// The Pali aorist is the standard simple past. The most common set:
// 1sg -iṃ 2sg -i 3sg -i
// 1pl -imhā 2pl -ittha 3pl -iṃsu
//
// These are appended to the verb root (or a modified stem). For regular
// weak aorists, the root is taken from the infinitive by stripping -ati.
fn pi_aorist_ending(slot: Int) -> String {
if slot == 0 { return "iṃ" }
if slot == 1 { return "i" }
if slot == 2 { return "i" }
if slot == 3 { return "imhā" }
if slot == 4 { return "ittha" }
return "iṃsu"
}
// Future tense endings
//
// Future is formed with the suffix -issa- inserted between root and personal ending:
// 1sg -issāmi 2sg -issasi 3sg -issati
// 1pl -issāma 2pl -issatha 3pl -issanti
fn pi_future_ending(slot: Int) -> String {
if slot == 0 { return "issāmi" }
if slot == 1 { return "issasi" }
if slot == 2 { return "issati" }
if slot == 3 { return "issāma" }
if slot == 4 { return "issatha" }
return "issanti"
}
// Irregular verb tables
//
// atthi/hoti (to be): two parallel paradigms.
// The "hoti" paradigm is used by default (canonical "be" maps here).
// "atthi" paradigm is included for the commonly seen alternative.
fn pi_hoti_present(slot: Int) -> String {
if slot == 0 { return "homi" }
if slot == 1 { return "hosi" }
if slot == 2 { return "hoti" }
if slot == 3 { return "homa" }
if slot == 4 { return "hotha" }
return "honti"
}
fn pi_atthi_present(slot: Int) -> String {
if slot == 0 { return "amhi" }
if slot == 1 { return "asi" }
if slot == 2 { return "atthi" }
if slot == 3 { return "amha" }
if slot == 4 { return "attha" }
return "santi"
}
// Past (aorist) of hoti/atthi: āsi paradigm (a-aorist)
fn pi_hoti_aorist(slot: Int) -> String {
if slot == 0 { return "āsiṃ" }
if slot == 1 { return "āsi" }
if slot == 2 { return "āsi" }
if slot == 3 { return "āsimhā" }
if slot == 4 { return "āsittha" }
return "āsiṃsu"
}
// Future of hoti: regular -issa- on root "ho-"
fn pi_hoti_future(slot: Int) -> String {
if slot == 0 { return "hossāmi" }
if slot == 1 { return "hossasi" }
if slot == 2 { return "hossati" }
if slot == 3 { return "hossāma" }
if slot == 4 { return "hossatha" }
return "hossanti"
}
// gacchati (to go): present is suppletive; aorist uses root "gam-"/"agamā-"
fn pi_gacchati_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 pi_gacchati_aorist(slot: Int) -> String {
if slot == 0 { return "agamāsiṃ" }
if slot == 1 { return "agamāsi" }
if slot == 2 { return "agamāsi" }
if slot == 3 { return "agamāsimhā" }
if slot == 4 { return "agamāsittha" }
return "agamaṃsu"
}
fn pi_gacchati_future(slot: Int) -> String {
if slot == 0 { return "gamissāmi" }
if slot == 1 { return "gamissasi" }
if slot == 2 { return "gamissati" }
if slot == 3 { return "gamissāma" }
if slot == 4 { return "gamissatha" }
return "gamissanti"
}
// passati (to see): regular present; aorist root "dis-"/"addasā-"
fn pi_passati_present(slot: Int) -> String {
if slot == 0 { return "passāmi" }
if slot == 1 { return "passasi" }
if slot == 2 { return "passati" }
if slot == 3 { return "passāma" }
if slot == 4 { return "passatha" }
return "passanti"
}
fn pi_passati_aorist(slot: Int) -> String {
if slot == 0 { return "addasāsiṃ" }
if slot == 1 { return "addasāsi" }
if slot == 2 { return "addasāsi" }
if slot == 3 { return "addasāsimhā" }
if slot == 4 { return "addasāsittha" }
return "addasāsiṃsu"
}
fn pi_passati_future(slot: Int) -> String {
if slot == 0 { return "dakkhissāmi" }
if slot == 1 { return "dakkhissasi" }
if slot == 2 { return "dakkhissati" }
if slot == 3 { return "dakkhissāma" }
if slot == 4 { return "dakkhissatha" }
return "dakkhissanti"
}
// vadati (to say): regular throughout; aorist uses avadi-
fn pi_vadati_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 pi_vadati_aorist(slot: Int) -> String {
if slot == 0 { return "avadāsiṃ" }
if slot == 1 { return "avadāsi" }
if slot == 2 { return "avadāsi" }
if slot == 3 { return "avadāsimhā" }
if slot == 4 { return "avadāsittha" }
return "avadāsiṃsu"
}
fn pi_vadati_future(slot: Int) -> String {
if slot == 0 { return "vadissāmi" }
if slot == 1 { return "vadissasi" }
if slot == 2 { return "vadissati" }
if slot == 3 { return "vadissāma" }
if slot == 4 { return "vadissatha" }
return "vadissanti"
}
// karoti (to do/make): karo- present stem; kāri-/akāsi aorist
fn pi_karoti_present(slot: Int) -> String {
if slot == 0 { return "karomi" }
if slot == 1 { return "karosi" }
if slot == 2 { return "karoti" }
if slot == 3 { return "karoma" }
if slot == 4 { return "karotha" }
return "karonti"
}
fn pi_karoti_aorist(slot: Int) -> String {
if slot == 0 { return "akāsiṃ" }
if slot == 1 { return "akāsi" }
if slot == 2 { return "akāsi" }
if slot == 3 { return "akāsimhā" }
if slot == 4 { return "akāsittha" }
return "akāsiṃsu"
}
fn pi_karoti_future(slot: Int) -> String {
if slot == 0 { return "karissāmi" }
if slot == 1 { return "karissasi" }
if slot == 2 { return "karissati" }
if slot == 3 { return "karissāma" }
if slot == 4 { return "karissatha" }
return "karissanti"
}
// Canonical verb mapping
//
// Maps English semantic labels to Pali verb citation forms (3rd sg present).
fn pi_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "hoti" }
if str_eq(verb, "go") { return "gacchati" }
if str_eq(verb, "see") { return "passati" }
if str_eq(verb, "say") { return "vadati" }
if str_eq(verb, "do") { return "karoti" }
if str_eq(verb, "make") { return "karoti" }
return verb
}
// Regular verb stem derivation
//
// For bhū-ādi class verbs, the present stem is derived by stripping -ati from
// the 3sg present form (the citation form), then appending the appropriate ending.
// The same root (without -ati) is used for the aorist and future.
//
// e.g. "bhavati" -> root "bhava" -> present "bhavāmi", future "bhavissāmi"
// -> aorist root = "bhavi" -> "bhaviṃ"
fn pi_regular_root(verb: String) -> String {
// Strip -ati if present to get the thematic stem
if pi_str_ends(verb, "ati") { return pi_drop(verb, 3) }
// Some forms end in -eti (e-class verbs)
if pi_str_ends(verb, "eti") { return pi_drop(verb, 3) }
// Otherwise treat the whole form as the root
return verb
}
// pi_conjugate: main conjugation entry point
//
// verb: Pali 3sg present (citation form, e.g. "bhavati", "hoti") or English
// canonical label
// tense: "present" | "past" | "future"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns the inflected form. Falls back to the citation form on unknown input.
fn pi_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = pi_map_canonical(verb)
let slot: Int = pi_slot(person, number)
// Irregulars
if str_eq(v, "hoti") {
if str_eq(tense, "present") { return pi_hoti_present(slot) }
if str_eq(tense, "past") { return pi_hoti_aorist(slot) }
if str_eq(tense, "future") { return pi_hoti_future(slot) }
return v
}
if str_eq(v, "atthi") {
if str_eq(tense, "present") { return pi_atthi_present(slot) }
if str_eq(tense, "past") { return pi_hoti_aorist(slot) }
if str_eq(tense, "future") { return pi_hoti_future(slot) }
return v
}
if str_eq(v, "gacchati") {
if str_eq(tense, "present") { return pi_gacchati_present(slot) }
if str_eq(tense, "past") { return pi_gacchati_aorist(slot) }
if str_eq(tense, "future") { return pi_gacchati_future(slot) }
return v
}
if str_eq(v, "passati") {
if str_eq(tense, "present") { return pi_passati_present(slot) }
if str_eq(tense, "past") { return pi_passati_aorist(slot) }
if str_eq(tense, "future") { return pi_passati_future(slot) }
return v
}
if str_eq(v, "vadati") {
if str_eq(tense, "present") { return pi_vadati_present(slot) }
if str_eq(tense, "past") { return pi_vadati_aorist(slot) }
if str_eq(tense, "future") { return pi_vadati_future(slot) }
return v
}
if str_eq(v, "karoti") {
if str_eq(tense, "present") { return pi_karoti_present(slot) }
if str_eq(tense, "past") { return pi_karoti_aorist(slot) }
if str_eq(tense, "future") { return pi_karoti_future(slot) }
return v
}
// Regular bhū-ādi class verb
let root: String = pi_regular_root(v)
if str_eq(tense, "present") {
return root + pi_present_ending(slot)
}
if str_eq(tense, "past") {
// Aorist: root + i-aorist ending
return root + pi_aorist_ending(slot)
}
if str_eq(tense, "future") {
return root + pi_future_ending(slot)
}
// Unknown tense: return citation form unchanged
return v
}
// a-stem masculine declension ("purisa" man)
//
// The a-stem masculine is the most frequent Pali declension class.
// Paradigm based on "puriso" (nominative singular).
//
// Eight cases: nominative, accusative, instrumental, dative, ablative,
// genitive, locative, vocative.
//
// Singular:
// nom puriso acc purisaṃ instr purisena dat purisāya (or purissā)
// abl purisā gen purisassa loc purisasmiṃ voc purisa
//
// Plural:
// nom purisā acc purise instr purisehi dat purisānaṃ
// abl purisānaṃ gen purisānaṃ loc purisesu voc purisā
//
// The caller passes the stem (without final vowel), e.g. "purisa".
// Detection: strip the final -o (nom sg) or use the stem directly.
fn pi_decline_a_masc_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "o" }
if str_eq(gram_case, "accusative") { return stem + "" }
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 + "ā" }
if str_eq(gram_case, "genitive") { return stem + "ssa" }
if str_eq(gram_case, "locative") { return stem + "smiṃ" }
if str_eq(gram_case, "vocative") { return stem }
// Default: nominative
return stem + "o"
}
fn pi_decline_a_masc_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "ā" }
if str_eq(gram_case, "accusative") { return stem + "e" }
if str_eq(gram_case, "instrumental") { return stem + "ehi" }
if str_eq(gram_case, "dative") { return stem + "ānaṃ" }
if str_eq(gram_case, "ablative") { return stem + "ānaṃ" }
if str_eq(gram_case, "genitive") { return stem + "ānaṃ" }
if str_eq(gram_case, "locative") { return stem + "esu" }
if str_eq(gram_case, "vocative") { return stem + "ā" }
return stem + "ā"
}
// ā-stem feminine declension ("kaññā" girl)
//
// ā-stem feminines are the second most common class. Paradigm based on "kaññā".
//
// Singular:
// nom kaññā acc kaññaṃ instr kaññāya dat kaññāya
// abl kaññāya gen kaññāya loc kaññāyaṃ voc kaññe
//
// Plural (approximated from standard tables; many forms merge):
// nom kaññā acc kaññā instr kaññāhi dat kaññānaṃ
// abl kaññānaṃ gen kaññānaṃ loc kaññāsu voc kaññā
//
// The caller passes the stem without the final ā, e.g. "kaññ".
fn pi_decline_a_fem_sg(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "ā" }
if str_eq(gram_case, "accusative") { return stem + "aṃ" }
if str_eq(gram_case, "instrumental") { return stem + "āya" }
if str_eq(gram_case, "dative") { return stem + "āya" }
if str_eq(gram_case, "ablative") { return stem + "āya" }
if str_eq(gram_case, "genitive") { return stem + "āya" }
if str_eq(gram_case, "locative") { return stem + "āyaṃ" }
if str_eq(gram_case, "vocative") { return stem + "e" }
return stem + "ā"
}
fn pi_decline_a_fem_pl(stem: String, gram_case: String) -> String {
if str_eq(gram_case, "nominative") { return stem + "ā" }
if str_eq(gram_case, "accusative") { return stem + "ā" }
if str_eq(gram_case, "instrumental") { return stem + "āhi" }
if str_eq(gram_case, "dative") { return stem + "ānaṃ" }
if str_eq(gram_case, "ablative") { return stem + "ānaṃ" }
if str_eq(gram_case, "genitive") { return stem + "ānaṃ" }
if str_eq(gram_case, "locative") { return stem + "āsu" }
if str_eq(gram_case, "vocative") { return stem + "ā" }
return stem + "ā"
}
// Declension class detection
//
// Detect whether a noun is an a-stem masculine or ā-stem feminine based on its
// citation form (nominative singular).
//
// ends in -o -> a-stem masculine (puriso, devo, loko)
// ends in -> ā-stem feminine (kaññā, mātu[?], nadī)
// ends in -a -> treat as a-stem masculine stem (some words cited without -o)
//
// For other stems the module falls back to a-stem masculine as the safest default.
fn pi_detect_class(noun: String) -> String {
if pi_str_ends(noun, "o") { return "a_masc" }
if pi_str_ends(noun, "ā") { return "a_fem" }
// Some a-masc nouns may be cited as stems ending in -a
if pi_str_ends(noun, "a") { return "a_masc" }
// Default to a-stem masculine
return "a_masc"
}
// pi_decline: main declension entry point
//
// noun: Pali nominative singular (citation form, e.g. "puriso", "kaññā")
// gram_case: "nominative" | "accusative" | "instrumental" | "dative" |
// "ablative" | "genitive" | "locative" | "vocative"
// number: "singular" | "plural"
//
// Returns the inflected form. Falls back to the citation form on unknown input.
fn pi_decline(noun: String, gram_case: String, number: String) -> String {
let nclass: String = pi_detect_class(noun)
if str_eq(nclass, "a_masc") {
// Derive stem: strip final -o or -a to get the consonant stem
let stem: String = noun
if pi_str_ends(noun, "o") {
let stem = pi_drop(noun, 1)
}
if pi_str_ends(noun, "a") {
let stem = pi_drop(noun, 1)
}
if str_eq(number, "singular") { return pi_decline_a_masc_sg(stem, gram_case) }
return pi_decline_a_masc_pl(stem, gram_case)
}
if str_eq(nclass, "a_fem") {
// Derive stem: strip final
let stem: String = noun
if pi_str_ends(noun, "ā") {
let stem = pi_drop(noun, 1)
}
if str_eq(number, "singular") { return pi_decline_a_fem_sg(stem, gram_case) }
return pi_decline_a_fem_pl(stem, gram_case)
}
// Unknown class: return citation form unchanged
return noun
}
// pi_noun_phrase: noun phrase builder
//
// Pali has no articles (definite or indefinite). The declined form is the
// complete noun phrase. The definite parameter is accepted for interface
// compatibility with other language modules but has no effect.
//
// noun: nominative singular Pali noun (e.g. "puriso", "kaññā")
// gram_case: "nominative" | "accusative" | "instrumental" | "dative" |
// "ablative" | "genitive" | "locative" | "vocative"
// number: "singular" | "plural"
// definite: ignored (Pali has no articles)
//
// Returns the inflected noun form.
fn pi_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return pi_decline(noun, gram_case, number)
}