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

422 lines
16 KiB
EmacsLisp
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// morphology-uga.el - Ugaritic morphology for the NLG engine.
// 𐎀𐎎𐎗𐎚 Ugaritic, the language of the city-state of Ugarit.
//
// Implements Ugaritic verb conjugation (G-stem, suffix and prefix conjugations),
// noun declension (three cases, gender, number, dual), and noun-phrase construction.
//
// Ugaritic is a Northwest Semitic language (ca. 14001200 BCE), written in a
// 30-character alphabetic cuneiform. It is closely related to Biblical Hebrew
// and Phoenician. We work in standard Latin transliteration:
// ʼ = aleph (glottal stop) ʻ = ʿayin = het ġ = ghayin
// = thet = emphatic z š = shin = tsade
//
// Language profile:
// code=uga, name=Ugaritic, morph_type=semitic, word_order=VSO,
// script=alphabetic cuneiform (transliterated), family=semitic/northwest-semitic
//
// Key grammatical facts:
// - Semitic trilateral root system (nearly identical to Hebrew/Arabic)
// - Grammatical gender: masculine / feminine
// - Cases: nominative (-u), accusative (-a), genitive (-i)
// (mirrors Akkadian but with shorter endings no mimation in Ugaritic)
// - Number: singular / plural / dual
// - Dual: nom -āma, gen/acc -ēma
// - Verb tenses:
// qtl (perfect, suffix conjugation) completed action
// yqtl (imperfect, prefix conjugation) ongoing / future action
// - Verb stems: G, D, Š, N (this file: G-stem throughout)
// - No separate definite article (unlike later Hebrew ha-)
// - Copula: root kn (to be)
//
// Verb conjugation conventions:
// person: "first" | "second" | "third"
// gender: "m" | "f"
// number: "singular" | "plural"
// tense: "perfect" | "imperfect"
//
// Noun declension conventions:
// gram_case: "nom" | "acc" | "gen"
// number: "singular" | "plural" | "dual"
//
// Verbs covered (G-stem root, transliterated):
// "kn" to be (copula)
// "hlk" to go
// "rʼy" to see
// "ʼmr" to say
//
// Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with)
// String helpers
import "morphology.el"
fn uga_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn uga_str_len(s: String) -> Int {
return str_len(s)
}
fn uga_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)
}
// Slot index
//
// Maps person × number to a 0-based slot index.
// Gender is used separately for third-person disambiguation.
//
// Slot layout (6 primary cells, matching classic NW-Semitic paradigm):
// 0 = 1sg (I)
// 1 = 2sg (you sg masc and fem conflate in many forms)
// 2 = 3sg m (he)
// 3 = 3sg f (she)
// 4 = 1pl (we)
// 5 = 3pl (they masc default)
fn uga_slot(person: String, number: String) -> Int {
if str_eq(person, "first") {
if str_eq(number, "plural") { return 4 }
return 0
}
if str_eq(person, "second") {
return 1
}
// third
if str_eq(number, "plural") { return 5 }
return 2
}
// uga_slot_g: gender-sensitive slot for third-person singular.
fn uga_slot_g(person: String, gender: String, number: String) -> Int {
let base: Int = uga_slot(person, number)
if str_eq(person, "third") {
if str_eq(number, "singular") {
if str_eq(gender, "f") { return 3 }
}
}
return base
}
// Copula: kn to be
//
// qtl (perfect): kāna (3sg m), kānat (3sg f), kānu (3pl)
// yqtl (imperfect): yakūnu (3sg m), takūnu (3sg f / 2sg), ʼakūnu (1sg)
//
// The root kn is a weak verb (hollow: middle w/y). The perfect stem is kāna-;
// the imperfect stem is -kūn-.
fn uga_kn_perfect(slot: Int) -> String {
if slot == 0 { return "kāntu" } // 1sg (kān- + suffix -tu)
if slot == 1 { return "kānta" } // 2sg (-ta suffix)
if slot == 2 { return "kāna" } // 3sg m (base)
if slot == 3 { return "kānat" } // 3sg f (-at suffix)
if slot == 4 { return "kānnu" } // 1pl (-nu suffix)
return "kānu" // 3pl (-u suffix)
}
fn uga_kn_imperfect(slot: Int) -> String {
if slot == 0 { return "ʼakūnu" } // 1sg (ʼa- prefix)
if slot == 1 { return "takūnu" } // 2sg (ta- prefix)
if slot == 2 { return "yakūnu" } // 3sg m (ya- prefix)
if slot == 3 { return "takūnu" } // 3sg f (ta- prefix, same as 2sg)
if slot == 4 { return "nakūnu" } // 1pl (na- prefix)
return "yakūnuna" // 3pl (ya- + -ūna energic suffix)
}
fn uga_is_copula(verb: String) -> Bool {
if str_eq(verb, "kn") { return true }
if str_eq(verb, "kāna") { return true }
if str_eq(verb, "be") { return true }
return false
}
fn uga_conjugate_copula(tense: String, slot: Int) -> String {
if str_eq(tense, "perfect") { return uga_kn_perfect(slot) }
return uga_kn_imperfect(slot)
}
// hlk to go
//
// Regular strong verb. Perfect stem: halak-; imperfect stem: -hluk-.
fn uga_hlk_perfect(slot: Int) -> String {
if slot == 0 { return "halaktu" } // 1sg
if slot == 1 { return "halakta" } // 2sg
if slot == 2 { return "halaka" } // 3sg m
if slot == 3 { return "halakat" } // 3sg f
if slot == 4 { return "halaknu" } // 1pl
return "halaku" // 3pl
}
fn uga_hlk_imperfect(slot: Int) -> String {
if slot == 0 { return "ʼahluku" } // 1sg
if slot == 1 { return "tahluku" } // 2sg
if slot == 2 { return "yahluku" } // 3sg m
if slot == 3 { return "tahluku" } // 3sg f
if slot == 4 { return "nahluku" } // 1pl
return "yahlukuna" // 3pl
}
// rʼy to see
//
// Third-weak verb (final y). Perfect: raʼaya (3sg m); imperfect: yarʼā (3sg m).
fn uga_ray_perfect(slot: Int) -> String {
if slot == 0 { return "raʼaytu" } // 1sg
if slot == 1 { return "raʼayta" } // 2sg
if slot == 2 { return "raʼaya" } // 3sg m
if slot == 3 { return "raʼayat" } // 3sg f
if slot == 4 { return "raʼaynu" } // 1pl
return "raʼayu" // 3pl
}
fn uga_ray_imperfect(slot: Int) -> String {
if slot == 0 { return "ʼarʼā" } // 1sg
if slot == 1 { return "tarʼā" } // 2sg
if slot == 2 { return "yarʼā" } // 3sg m
if slot == 3 { return "tarʼā" } // 3sg f
if slot == 4 { return "narʼā" } // 1pl
return "yarʼayna" // 3pl (fem plural ending for final-weak)
}
// ʼmr to say
//
// Strong verb. Perfect: ʼamara (3sg m); imperfect: yaʼmuru (3sg m).
fn uga_amr_perfect(slot: Int) -> String {
if slot == 0 { return "ʼamartu" } // 1sg
if slot == 1 { return "ʼamarta" } // 2sg
if slot == 2 { return "ʼamara" } // 3sg m
if slot == 3 { return "ʼamarat" } // 3sg f
if slot == 4 { return "ʼamarnu" } // 1pl
return "ʼamaru" // 3pl
}
fn uga_amr_imperfect(slot: Int) -> String {
if slot == 0 { return "ʼaʼmuru" } // 1sg
if slot == 1 { return "taʼmuru" } // 2sg
if slot == 2 { return "yaʼmuru" } // 3sg m
if slot == 3 { return "taʼmuru" } // 3sg f
if slot == 4 { return "naʼmuru" } // 1pl
return "yaʼmuruna" // 3pl
}
// Generic G-stem paradigm (regular strong verbs)
//
// For verbs not in the lookup table, apply the regular pattern.
// Caller supplies the 3sg perfect form (qtl) and 3sg imperfect (yqtl) form.
// We derive person forms by suffix/prefix substitution.
fn uga_generic_perfect(base3sg: String, slot: Int) -> String {
// Perfect suffixes: 1sg -tu, 2sg -ta, 3sg m , 3sg f -at, 1pl -nu, 3pl -u
if slot == 0 { return base3sg + "tu" }
if slot == 1 { return base3sg + "ta" }
if slot == 2 { return base3sg }
if slot == 3 { return base3sg + "at" }
if slot == 4 { return base3sg + "nu" }
return base3sg + "u"
}
fn uga_generic_imperfect(base3sg: String, slot: Int) -> String {
// Strip leading ya- to get the core, re-prefix per person.
// This is a heuristic for display purposes when the stem is unknown.
if slot == 0 { return "ʼa" + base3sg }
if slot == 1 { return "ta" + base3sg }
if slot == 2 { return "ya" + base3sg }
if slot == 3 { return "ta" + base3sg }
if slot == 4 { return "na" + base3sg }
return "ya" + base3sg + "una"
}
// Known-verb dispatcher
fn uga_known_verb(verb: String, tense: String, slot: Int) -> String {
// kn to be
if str_eq(verb, "kn") {
return uga_conjugate_copula(tense, slot)
}
if str_eq(verb, "kāna") {
return uga_conjugate_copula(tense, slot)
}
// hlk to go
if str_eq(verb, "hlk") {
if str_eq(tense, "perfect") { return uga_hlk_perfect(slot) }
return uga_hlk_imperfect(slot)
}
if str_eq(verb, "halaka") {
if str_eq(tense, "perfect") { return uga_hlk_perfect(slot) }
return uga_hlk_imperfect(slot)
}
// rʼy to see
if str_eq(verb, "rʼy") {
if str_eq(tense, "perfect") { return uga_ray_perfect(slot) }
return uga_ray_imperfect(slot)
}
if str_eq(verb, "raʼaya") {
if str_eq(tense, "perfect") { return uga_ray_perfect(slot) }
return uga_ray_imperfect(slot)
}
// ʼmr to say
if str_eq(verb, "ʼmr") {
if str_eq(tense, "perfect") { return uga_amr_perfect(slot) }
return uga_amr_imperfect(slot)
}
if str_eq(verb, "ʼamara") {
if str_eq(tense, "perfect") { return uga_amr_perfect(slot) }
return uga_amr_imperfect(slot)
}
return ""
}
// Main conjugation entry point
//
// uga_conjugate: conjugate a Ugaritic verb (G-stem).
//
// verb: root (23 consonants, e.g. "kn", "hlk") or 3sg perfect citation form
// tense: "perfect" | "imperfect"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
//
// Returns:
// - Inflected form for known verbs
// - verb unchanged as safe fallback for unknown verbs
fn uga_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let slot: Int = uga_slot(person, number)
if uga_is_copula(verb) {
return uga_conjugate_copula(tense, slot)
}
let known: String = uga_known_verb(verb, tense, slot)
if !str_eq(known, "") {
return known
}
return verb
}
// Noun declension
//
// uga_decline: decline a Ugaritic noun for gram_case and number.
//
// Ugaritic case endings (no mimation unlike Akkadian):
// Masculine singular:
// Nominative: -u (e.g. malku "king")
// Accusative: -a (e.g. malka)
// Genitive: -i (e.g. malki)
// Masculine plural:
// Nominative: -ūma (e.g. malkūma)
// Genitive/Accusative: -īma (e.g. malkīma)
// Feminine singular (typically -atu base):
// Nominative: -atu (e.g. malaktu)
// Accusative: -ata (e.g. malakta)
// Genitive: -ati (e.g. malakti)
// Feminine plural:
// Nominative: -ātu (e.g. malakātu)
// Genitive/Accusative: -āti
// Dual:
// Nominative: -āma (e.g. malkāma)
// Genitive/Accusative: -ēma
//
// Strategy: strip the known nominative ending to get the bare stem,
// then apply the requested ending.
fn uga_strip_nom(noun: String) -> String {
// Strip masc sg -u
if uga_str_ends(noun, "u") {
let len: Int = uga_str_len(noun)
if len > 1 {
return uga_str_drop_last(noun, 1)
}
}
// Strip fem sg -atu
if uga_str_ends(noun, "atu") {
return uga_str_drop_last(noun, 3)
}
return noun
}
fn uga_is_fem(noun: String) -> Bool {
if uga_str_ends(noun, "atu") { return true }
if uga_str_ends(noun, "ata") { return true }
if uga_str_ends(noun, "ati") { return true }
if uga_str_ends(noun, "ātu") { return true }
if uga_str_ends(noun, "āti") { return true }
return false
}
fn uga_decline(noun: String, gram_case: String, number: String) -> String {
let fem: Bool = uga_is_fem(noun)
let stem: String = uga_strip_nom(noun)
if str_eq(number, "dual") {
if str_eq(gram_case, "nom") { return stem + "āma" }
return stem + "ēma"
}
if str_eq(number, "plural") {
if fem {
if str_eq(gram_case, "nom") { return stem + "ātu" }
return stem + "āti"
}
// Masculine plural
if str_eq(gram_case, "nom") { return stem + "ūma" }
return stem + "īma"
}
// Singular
if fem {
if str_eq(gram_case, "nom") { return stem + "atu" }
if str_eq(gram_case, "acc") { return stem + "ata" }
if str_eq(gram_case, "gen") { return stem + "ati" }
return stem + "atu"
}
// Masculine singular
if str_eq(gram_case, "nom") { return stem + "u" }
if str_eq(gram_case, "acc") { return stem + "a" }
if str_eq(gram_case, "gen") { return stem + "i" }
return stem + "u"
}
// Noun phrase
//
// uga_noun_phrase: produce the surface noun phrase.
//
// Ugaritic has no separate definite article (unlike later Hebrew ha-).
// Determination is expressed through word order, the construct chain (genitive),
// and pronominal suffixes. The definite parameter is accepted for interface
// uniformity but has no surface effect.
//
// noun: base noun (nominative singular form, e.g. "malku")
// gram_case: "nom" | "acc" | "gen"
// number: "singular" | "plural" | "dual"
// definite: "true" | "false" (no surface effect in Ugaritic)
fn uga_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String {
return uga_decline(noun, gram_case, number)
}
// Canonical verb mapping
//
// uga_map_canonical: map cross-lingual English canonical verb labels to
// Ugaritic G-stem roots or citation forms.
fn uga_map_canonical(verb: String) -> String {
if str_eq(verb, "be") { return "kn" }
if str_eq(verb, "go") { return "hlk" }
if str_eq(verb, "see") { return "rʼy" }
if str_eq(verb, "say") { return "ʼmr" }
if str_eq(verb, "speak") { return "ʼmr" }
return verb
}