c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
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
349 lines
15 KiB
EmacsLisp
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 "dā" }
|
|
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, "dā") {
|
|
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)
|
|
}
|