Files
el/elp/src/morphology-peo.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

349 lines
15 KiB
EmacsLisp

// morphology-peo.el - Old Persian morphology for the NLG engine.
//
// Implements Old Persian verb conjugation and noun declension for the ca. 600-300 BCE
// period (Achaemenid Empire). Designed as a companion to morphology.el and called
// by the engine when the language profile code is "peo".
//
// Language profile: code=peo, name=Old Persian, morph_type=fusional, word_order=SOV,
// question_strategy=particle, script=latin, family=iranian.
//
// Old Persian is attested primarily in royal cuneiform inscriptions (Behistun,
// Persepolis, Naqsh-e Rostam, etc.). The transliteration used here follows the
// standard scholarly convention with macrons for long vowels (ā, ī, ū). The corpus
// is small most productive forms are individually attested in the inscriptions.
//
// Verb conjugation covered:
// Tenses: present, past (imperfect)
// Persons: first/second/third x singular/plural (slots 0-5)
// Irregulars: ah- (to be), kar- (to do/make), xšāya- (to rule),
// tar- (to cross/overcome), dā- (to give)
// Canonical map: "be" -> "ah-"
// Pro-drop: the subject pronoun is typically omitted; the engine provides
// conjugated forms only and does not add pronouns.
//
// Noun declension covered:
// a-stem masculine (like "dahyu" country/land)
// Cases: nominative, accusative, genitive, dative x singular, plural
// (Many case distinctions collapsed in practice; 4 cases implemented)
// Articles: none Old Persian has no article system
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
import "morphology.el"
fn peo_drop(s: String, n: Int) -> String {
let len: Int = str_len(s)
if n >= len { return "" }
return str_slice(s, 0, len - n)
}
fn peo_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
// Person/number slot
//
// Maps person x number to a 0-based paradigm slot.
// 0 = 1st singular (adam)
// 1 = 2nd singular (tuvam)
// 2 = 3rd singular (hauv)
// 3 = 1st plural (vayam)
// 4 = 2nd plural (yuvam)
// 5 = 3rd plural (taiy)
fn peo_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
}
// Regular present endings
//
// Old Persian present active indicative endings (Schmitt 1991 reconstruction):
// Sg: 1st -āmiy 2nd -ahiy 3rd -atiy
// Pl: 1st -āmahy 2nd -ātā 3rd -antiy
//
// These attach to the present stem. The present stem typically equals the
// verbal root (sometimes with a thematic vowel -a-).
fn peo_present_suffix(slot: Int) -> String {
if slot == 0 { return "āmiy" }
if slot == 1 { return "ahiy" }
if slot == 2 { return "atiy" }
if slot == 3 { return "āmahy" }
if slot == 4 { return "ātā" }
return "antiy"
}
// Regular past (imperfect) endings
//
// Old Persian imperfect (past) endings. The imperfect is formed with the augment
// a- prefixed to the stem, but since many verbs in the inscriptions appear without
// the augment or with it fossilised, the engine omits the augment prefix and
// returns only the stem + ending combination.
//
// Imperfect endings:
// Sg: 1st -am 2nd 3rd -a
// Pl: 1st -āmā 2nd -ātā 3rd
fn peo_past_suffix(slot: Int) -> String {
if slot == 0 { return "am" }
if slot == 1 { return "ā" }
if slot == 2 { return "a" }
if slot == 3 { return "āmā" }
if slot == 4 { return "ātā" }
return "ā"
}
// Irregular verb tables
// ah- (to be) the Old Persian verb for being/existence
// Present: amiy, ahiy, astiy, amahy, astā, hatiy
// Past: āham, āha, āha, āhama, āhata, āhan
fn peo_ah_present(slot: Int) -> String {
if slot == 0 { return "amiy" }
if slot == 1 { return "ahiy" }
if slot == 2 { return "astiy" }
if slot == 3 { return "amahy" }
if slot == 4 { return "astā" }
return "hatiy"
}
fn peo_ah_past(slot: Int) -> String {
if slot == 0 { return "āham" }
if slot == 1 { return "āha" }
if slot == 2 { return "āha" }
if slot == 3 { return "āhama" }
if slot == 4 { return "āhata" }
return "āhan"
}
// kar- (to do/make) highly productive verb, uses present stem kun-/kunav-
// Present: kunāmiy, kunāhiy, kunautiy, kunāmahy, kunātā, kunavantiy
// Past: akunavam (1sg), akunava (3sg) limited attestation; simplified
fn peo_kar_present(slot: Int) -> String {
if slot == 0 { return "kunāmiy" }
if slot == 1 { return "kunāhiy" }
if slot == 2 { return "kunautiy" }
if slot == 3 { return "kunāmahy" }
if slot == 4 { return "kunātā" }
return "kunavantiy"
}
fn peo_kar_past(slot: Int) -> String {
if slot == 0 { return "akunavam" }
if slot == 1 { return "akunavā" }
if slot == 2 { return "akunava" }
if slot == 3 { return "akunavāmā" }
if slot == 4 { return "akunavātā" }
return "akunavan"
}
// xšāya- (to rule) verb of the royal inscriptions, thematic stem xšāya-
// Present: xšāyāmiy, xšāyāhiy, xšāyatiy, xšāyāmahy, xšāyātā, xšāyantiy
fn peo_xsaya_present(slot: Int) -> String {
if slot == 0 { return "xšāyāmiy" }
if slot == 1 { return "xšāyāhiy" }
if slot == 2 { return "xšāyatiy" }
if slot == 3 { return "xšāyāmahy" }
if slot == 4 { return "xšāyātā" }
return "xšāyantiy"
}
// tar- (to cross/overcome) limited attestation in inscriptions
// Attested: taratiy (3sg pres), tarantiy (3pl pres)
// Other slots approximated using regular present endings on stem tar-
fn peo_tar_present(slot: Int) -> String {
if slot == 2 { return "taratiy" }
if slot == 5 { return "tarantiy" }
// Other slots: apply regular endings to stem tar-
return "tar" + peo_present_suffix(slot)
}
// dā- (to give) long-vowel root, contracts in some forms
// Present: dāmiy, dāhiy, dātiy, dāmahy, dātā, dantiy
fn peo_da_present(slot: Int) -> String {
if slot == 0 { return "dāmiy" }
if slot == 1 { return "dāhiy" }
if slot == 2 { return "dātiy" }
if slot == 3 { return "dāmahy" }
if slot == 4 { return "dātā" }
return "dantiy"
}
fn peo_da_past(slot: Int) -> String {
if slot == 0 { return "adām" }
if slot == 1 { return "adāā" }
if slot == 2 { return "adā" }
if slot == 3 { return "adāmā" }
if slot == 4 { return "adātā" }
return "adān"
}
// Canonical verb mapping
//
// Maps English semantic labels to Old Persian verbal stems / citation forms.
fn peo_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "ah" }
if str_eq(verb, "do") { return "kar" }
if str_eq(verb, "make") { return "kar" }
if str_eq(verb, "rule") { return "xšāya" }
if str_eq(verb, "cross") { return "tar" }
if str_eq(verb, "give") { return "" }
return verb
}
// peo_conjugate: main conjugation entry point
//
// verb: Old Persian verbal stem or English canonical label
// tense: "present" | "past"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns the inflected form. Pro-drop language the engine produces conjugated
// verb forms only; subject pronouns are omitted unless explicitly generated by
// the semantic layer.
fn peo_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let v: String = peo_map_canonical(verb)
let slot: Int = peo_slot(person, number)
// ah- (to be)
if str_eq(v, "ah") {
if str_eq(tense, "present") { return peo_ah_present(slot) }
if str_eq(tense, "past") { return peo_ah_past(slot) }
return v
}
// kar- (to do/make)
if str_eq(v, "kar") {
if str_eq(tense, "present") { return peo_kar_present(slot) }
if str_eq(tense, "past") { return peo_kar_past(slot) }
return v
}
// xšāya- (to rule)
if str_eq(v, "xšāya") {
if str_eq(tense, "present") { return peo_xsaya_present(slot) }
// Past of xšāya-: apply imperfect endings to xšāya- stem
if str_eq(tense, "past") { return "xšāya" + peo_past_suffix(slot) }
return v
}
// tar- (to cross/overcome)
if str_eq(v, "tar") {
if str_eq(tense, "present") { return peo_tar_present(slot) }
if str_eq(tense, "past") { return "tar" + peo_past_suffix(slot) }
return v
}
// dā- (to give)
if str_eq(v, "") {
if str_eq(tense, "present") { return peo_da_present(slot) }
if str_eq(tense, "past") { return peo_da_past(slot) }
return v
}
// Regular thematic verb
//
// Apply present or past endings directly to the stem. The stem is assumed
// to be in the form supplied (Old Persian thematic stems typically end in -a-).
if str_eq(tense, "present") { return v + peo_present_suffix(slot) }
if str_eq(tense, "past") { return v + peo_past_suffix(slot) }
// Unknown tense: return the bare stem
return v
}
// a-stem masculine declension ("dahyu" country/land)
//
// The a-stem is the primary masculine nominal class in Old Persian. The paradigm
// below uses "dahyu" (country, land the most frequent noun in the inscriptions)
// as the fully specified exemplar.
//
// Singular: nom dahyāuš acc dahyum gen dahyāuš dat dahyavā
// Plural: nom dahyāva acc dahyūn gen dahyūnām dat dahyubiyā
//
// The singular nom/gen syncretism (both dahyāuš) is historically inherited from
// Old Iranian; it is not a scribal error.
fn peo_decline_astem(noun: String, gram_case: String, number: String) -> String {
if str_eq(noun, "dahyu") {
if str_eq(number, "singular") {
if str_eq(gram_case, "nominative") { return "dahyāuš" }
if str_eq(gram_case, "accusative") { return "dahyum" }
if str_eq(gram_case, "genitive") { return "dahyāuš" }
if str_eq(gram_case, "dative") { return "dahyavā" }
return "dahyāuš"
}
if str_eq(gram_case, "nominative") { return "dahyāva" }
if str_eq(gram_case, "accusative") { return "dahyūn" }
if str_eq(gram_case, "genitive") { return "dahyūnām" }
if str_eq(gram_case, "dative") { return "dahyubiyā" }
return "dahyāva"
}
// Generic a-stem masculine: apply Old Persian nominal endings to base.
// Base form assumed to be the uninflected stem (without final -u/-a).
if str_eq(number, "singular") {
if str_eq(gram_case, "nominative") { return noun + "āuš" }
if str_eq(gram_case, "accusative") { return noun + "am" }
if str_eq(gram_case, "genitive") { return noun + "āuš" }
if str_eq(gram_case, "dative") { return noun + "avā" }
return noun + "āuš"
}
// plural
if str_eq(gram_case, "nominative") { return noun + "āva" }
if str_eq(gram_case, "accusative") { return noun + "ūn" }
if str_eq(gram_case, "genitive") { return noun + "ūnām" }
if str_eq(gram_case, "dative") { return noun + "ubiyā" }
return noun + "āva"
}
// peo_decline: main declension entry point
//
// noun: Old Persian noun (nominative singular or uninflected stem)
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
//
// Only the a-stem masculine paradigm is currently implemented; other stem classes
// fall back to the nominative singular form. The corpus is small enough that
// most attested nouns are a-stems or i-stems whose oblique forms can be
// approximated by the a-stem pattern.
fn peo_decline(noun: String, gram_case: String, number: String) -> String {
return peo_decline_astem(noun, gram_case, number)
}
// peo_noun_phrase: noun phrase builder
//
// Old Persian has no definite or indefinite articles. Definiteness was expressed
// contextually or through word order, not morphologically. This function ignores
// the "definite" parameter and returns the declined noun form directly.
//
// noun: Old Persian noun (nominative singular or stem)
// gram_case: "nominative" | "accusative" | "genitive" | "dative"
// number: "singular" | "plural"
// definite: "true" | "false" (ignored no articles in Old Persian)
//
// Returns the declined noun form.
fn peo_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return peo_decline(noun, gram_case, number)
}