Rename: nlg → elp (Engram Language Protocol)

This commit is contained in:
Will Anderson
2026-05-02 22:15:25 -05:00
parent cbb27b8d87
commit 34725a3988
125 changed files with 129214 additions and 780 deletions
+728
View File
@@ -0,0 +1,728 @@
// morphology-ar.el - Arabic morphology for the NLG engine.
//
// Implements Arabic verb conjugation, noun inflection (gram_case, gender, number,
// definiteness), and definite-article attachment with sun/moon letter handling.
//
// Arabic is a Semitic language with a trilateral root system: most words derive
// from 3-consonant roots by inserting vowel patterns (أوزان awzan) around the
// root consonants. Verb conjugation is realised as prefix + stem + suffix.
//
// Strategy: the engine takes the 3ms perfect (past tense) form as the canonical
// dictionary key (e.g. كَتَبَ kataba) and applies affix patterns to derive all
// other conjugated forms for Form I (الفعل المجرد) regular verbs. A lookup
// table covers essential irregular and hollow verbs.
//
// Verb tenses covered: "past" (perfect/الماضي), "present" (imperfect/المضارع),
// "future" (سَيَفْعَلُ = sa- + imperfect).
// Persons: first/second/third × masculine/feminine × singular/plural (+ dual stubs).
// Gender params: "m" (masculine) | "f" (feminine).
//
// Depends on: morphology.el (str_ends_with, str_len, str_slice, str_eq, str_drop_last concept)
// String helpers
fn ar_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn ar_str_len(s: String) -> Int {
return str_len(s)
}
fn ar_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)
}
fn ar_str_last_char(s: String) -> String {
let n: Int = str_len(s)
if n == 0 {
return ""
}
return str_slice(s, n - 1, n)
}
// Slot index
//
// Maps person × gender × number to a 0-based slot for table lookups.
// Slot layout (10 cells, matching classical Arabic conjugation paradigm):
// 0 = 3ms (he)
// 1 = 3fs (she)
// 2 = 2ms (you m sg)
// 3 = 2fs (you f sg)
// 4 = 1s (I)
// 5 = 3mp (they m pl)
// 6 = 3fp (they f pl)
// 7 = 2mp (you m pl)
// 8 = 2fp (you f pl)
// 9 = 1p (we)
fn ar_slot(person: String, gender: String, number: String) -> Int {
if str_eq(person, "third") {
if str_eq(number, "singular") {
if str_eq(gender, "f") { return 1 }
return 0
}
// plural
if str_eq(gender, "f") { return 6 }
return 5
}
if str_eq(person, "second") {
if str_eq(number, "singular") {
if str_eq(gender, "f") { return 3 }
return 2
}
// plural
if str_eq(gender, "f") { return 8 }
return 7
}
// first
if str_eq(number, "plural") { return 9 }
return 4
}
// Perfect (past) suffixes
//
// Form I perfect: root-past-stem (e.g. كَتَبَ kataba) + suffix.
// The 3ms form IS the base (no suffix added). All other persons add a suffix
// that replaces or follows the final short vowel of the base.
//
// Pattern (dropping the final -a of the 3ms base, then adding):
// 3ms: -a (base as given)
// 3fs: -at
// 2ms: -ta
// 2fs: -ti
// 1s: -tu
// 3mp: -uu
// 3fp: -na
// 2mp: -tum
// 2fp: -tunna
// 1p: -naa
//
// The base passed to ar_conjugate_form1 is the full 3ms form (ends in -a).
// For suffixed forms we drop the final vowel character (1 byte = the -a) then
// apply the suffix. In Arabic script the final short vowel (fatha ـَ) on the
// last consonant of the base is part of the grapheme cluster of that consonant;
// for our stored strings the form كَتَبَ is stored with the final fatha attached
// to the ب. The suffix strings already include the vowel that replaces it, so
// we drop 1 character from the base.
//
// For simplicity the suffixes below are given as Arabic transliteration that
// the El string system handles as UTF-8. The actual Arabic forms are stored
// as UTF-8 Arabic script literals.
//
// Returns the suffix string (including the vowel carried on the junction
// consonant for suffixed forms). Returns "" for 3ms (base is the full form).
fn ar_perfect_suffix(slot: Int) -> String {
if slot == 0 { return "" } // 3ms: base is already complete
if slot == 1 { return "ت" } // 3fs: -at (تْ taa saakina)
if slot == 2 { return "تَ" } // 2ms: -ta
if slot == 3 { return "تِ" } // 2fs: -ti
if slot == 4 { return "تُ" } // 1s: -tu
if slot == 5 { return "وا" } // 3mp: -uu (واو + alif farika)
if slot == 6 { return "نَ" } // 3fp: -na
if slot == 7 { return "تُمْ" } // 2mp: -tum
if slot == 8 { return "تُنَّ" } // 2fp: -tunna
return "نَا" // 1p: -naa (9)
}
// Imperfect (present) prefixes
//
// Form I imperfect: prefix + middle vowel pattern + suffix.
// Prefix depends on person (and for 1s the prefix is أَ).
fn ar_imperfect_prefix(slot: Int) -> String {
if slot == 0 { return "يَ" } // 3ms: ya-
if slot == 1 { return "تَ" } // 3fs: ta-
if slot == 2 { return "تَ" } // 2ms: ta-
if slot == 3 { return "تَ" } // 2fs: ta-
if slot == 4 { return "أَ" } // 1s: a-
if slot == 5 { return "يَ" } // 3mp: ya-
if slot == 6 { return "يَ" } // 3fp: ya-
if slot == 7 { return "تَ" } // 2mp: ta-
if slot == 8 { return "تَ" } // 2fp: ta-
return "نَ" // 1p: na- (9)
}
// Imperfect (present) suffixes
//
// Standard Form I imperfect yaf'ulu / yaf'alu / yaf'ilu vowel class.
// The stem vowel is encoded in the verb's imperfect stem (stored in the lookup
// table or derived from the base). The suffix encodes number/gender/person.
//
// Suffix pattern (after the u-class stem: yaktubu):
// 3ms: -u (yaktub-u)
// 3fs: -u (taktub-u)
// 2ms: -u (taktub-u)
// 2fs: -iina (taktub-iina)
// 1s: -u (aktub-u)
// 3mp: -uuna (yaktub-uuna)
// 3fp: -na (yaktub-na)
// 2mp: -uuna (taktub-uuna)
// 2fp: -na (taktub-na)
// 1p: -u (naktub-u)
fn ar_imperfect_suffix(slot: Int) -> String {
if slot == 0 { return "ُ" } // 3ms: -u
if slot == 1 { return "ُ" } // 3fs: -u
if slot == 2 { return "ُ" } // 2ms: -u
if slot == 3 { return "ِينَ" } // 2fs: -iina
if slot == 4 { return "ُ" } // 1s: -u
if slot == 5 { return "ُونَ" } // 3mp: -uuna
if slot == 6 { return "نَ" } // 3fp: -na
if slot == 7 { return "ُونَ" } // 2mp: -uuna
if slot == 8 { return "نَ" } // 2fp: -na
return "ُ" // 1p: -u (9)
}
// Form I conjugation
//
// ar_conjugate_form1: conjugate a regular Form I verb.
//
// past_base: the 3ms perfect form (e.g. "كَتَبَ")
// present_stem: the imperfect stem without prefix (e.g. "كْتُبُ" for yaktubu)
// This is the middle part after stripping the prefix: for يَكْتُبُ
// the stem = "كْتُبُ". We strip the final -u vowel diacritic
// (1 char) from the stem and re-add via the suffix.
// tense: "past" | "present" | "future"
// slot: ar_slot result
fn ar_conjugate_form1(past_base: String, present_stem: String, tense: String, slot: Int) -> String {
if str_eq(tense, "past") {
// 3ms: return base as-is
if slot == 0 { return past_base }
// All other forms: drop final character of base (the short -a vowel mark
// on the last root consonant), then append the suffix.
let suf: String = ar_perfect_suffix(slot)
// Drop the last character (the fatha diacritic or final vowel-letter)
let stem: String = ar_str_drop_last(past_base, 1)
return stem + suf
}
if str_eq(tense, "present") {
let pre: String = ar_imperfect_prefix(slot)
let suf: String = ar_imperfect_suffix(slot)
// present_stem already includes the medial vowel pattern (e.g. "كْتُبُ")
// Drop its final character (the -u diacritic) before adding the suffix.
let mid: String = ar_str_drop_last(present_stem, 1)
return pre + mid + suf
}
if str_eq(tense, "future") {
// Future = سَ (sa-) + imperfect 3ms form
let pres_3ms: String = ar_conjugate_form1(past_base, present_stem, "present", 0)
return "سَ" + pres_3ms
}
// Unknown tense: return base form
return past_base
}
// Irregular verb lookup table
//
// Returns the inflected form for verbs that cannot be derived by Form I rules,
// or "" if the verb is not in the table.
//
// Covered verbs (by their 3ms past / dictionary key):
// كَانَ kaana to be (hollow verb, waw-medial)
// ذَهَبَ dhahaba to go (Form I, regular; explicit table for certainty)
// جَاءَ jaa'a to come (hamzated + defective)
// قَالَ qaala to say (hollow verb, waw-medial)
// رَأَى ra'aa to see (hamzated + defective)
// أَكَلَ akala to eat (hamzated initial)
// شَرِبَ shariba to drink (Form I i-class)
// عَرَفَ arafa to know (Form I a-class)
// أَرَادَ araada to want (Form IV hollow)
// اِسْتَطَاعَ istata'a can/be able (Form X)
// فَعَلَ fa'ala to do/act (Form I; paradigm verb)
// أَخَذَ akhadha to take (hamzated initial)
// عَمِلَ amila to work (Form I i-class)
//
// For each verb: [past_3ms, past_3fs, past_2ms, past_2fs, past_1s,
// past_3mp, past_3fp, past_2mp, past_2fp, past_1p,
// pres_3ms, pres_3fs, pres_2ms, pres_2fs, pres_1s,
// pres_3mp, pres_3fp, pres_2mp, pres_2fp, pres_1p]
fn ar_irregular_kaana(slot: Int, tense: String) -> String {
// كَانَ to be
if str_eq(tense, "past") {
if slot == 0 { return "كَانَ" }
if slot == 1 { return "كَانَتْ" }
if slot == 2 { return "كُنْتَ" }
if slot == 3 { return "كُنْتِ" }
if slot == 4 { return "كُنْتُ" }
if slot == 5 { return "كَانُوا" }
if slot == 6 { return "كُنَّ" }
if slot == 7 { return "كُنْتُمْ" }
if slot == 8 { return "كُنْتُنَّ" }
return "كُنَّا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يَكُونُ" }
if slot == 1 { return "تَكُونُ" }
if slot == 2 { return "تَكُونُ" }
if slot == 3 { return "تَكُونِينَ" }
if slot == 4 { return "أَكُونُ" }
if slot == 5 { return "يَكُونُونَ" }
if slot == 6 { return "يَكُنَّ" }
if slot == 7 { return "تَكُونُونَ" }
if slot == 8 { return "تَكُنَّ" }
return "نَكُونُ"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_kaana(slot, "present")
return "سَ" + pres
}
return "كَانَ"
}
fn ar_irregular_qaala(slot: Int, tense: String) -> String {
// قَالَ to say (hollow waw-medial)
if str_eq(tense, "past") {
if slot == 0 { return "قَالَ" }
if slot == 1 { return "قَالَتْ" }
if slot == 2 { return "قُلْتَ" }
if slot == 3 { return "قُلْتِ" }
if slot == 4 { return "قُلْتُ" }
if slot == 5 { return "قَالُوا" }
if slot == 6 { return "قُلْنَ" }
if slot == 7 { return "قُلْتُمْ" }
if slot == 8 { return "قُلْتُنَّ" }
return "قُلْنَا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يَقُولُ" }
if slot == 1 { return "تَقُولُ" }
if slot == 2 { return "تَقُولُ" }
if slot == 3 { return "تَقُولِينَ" }
if slot == 4 { return "أَقُولُ" }
if slot == 5 { return "يَقُولُونَ" }
if slot == 6 { return "يَقُلْنَ" }
if slot == 7 { return "تَقُولُونَ" }
if slot == 8 { return "تَقُلْنَ" }
return "نَقُولُ"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_qaala(slot, "present")
return "سَ" + pres
}
return "قَالَ"
}
fn ar_irregular_jaa(slot: Int, tense: String) -> String {
// جَاءَ to come (hamzated defective)
if str_eq(tense, "past") {
if slot == 0 { return "جَاءَ" }
if slot == 1 { return "جَاءَتْ" }
if slot == 2 { return "جِئْتَ" }
if slot == 3 { return "جِئْتِ" }
if slot == 4 { return "جِئْتُ" }
if slot == 5 { return "جَاءُوا" }
if slot == 6 { return "جِئْنَ" }
if slot == 7 { return "جِئْتُمْ" }
if slot == 8 { return "جِئْتُنَّ" }
return "جِئْنَا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يَجِيءُ" }
if slot == 1 { return "تَجِيءُ" }
if slot == 2 { return "تَجِيءُ" }
if slot == 3 { return "تَجِيئِينَ" }
if slot == 4 { return "أَجِيءُ" }
if slot == 5 { return "يَجِيئُونَ" }
if slot == 6 { return "يَجِئْنَ" }
if slot == 7 { return "تَجِيئُونَ" }
if slot == 8 { return "تَجِئْنَ" }
return "نَجِيءُ"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_jaa(slot, "present")
return "سَ" + pres
}
return "جَاءَ"
}
fn ar_irregular_raaa(slot: Int, tense: String) -> String {
// رَأَى to see (hamzated defective)
if str_eq(tense, "past") {
if slot == 0 { return "رَأَى" }
if slot == 1 { return "رَأَتْ" }
if slot == 2 { return "رَأَيْتَ" }
if slot == 3 { return "رَأَيْتِ" }
if slot == 4 { return "رَأَيْتُ" }
if slot == 5 { return "رَأَوْا" }
if slot == 6 { return "رَأَيْنَ" }
if slot == 7 { return "رَأَيْتُمْ" }
if slot == 8 { return "رَأَيْتُنَّ" }
return "رَأَيْنَا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يَرَى" }
if slot == 1 { return "تَرَى" }
if slot == 2 { return "تَرَى" }
if slot == 3 { return "تَرَيْنَ" }
if slot == 4 { return "أَرَى" }
if slot == 5 { return "يَرَوْنَ" }
if slot == 6 { return "يَرَيْنَ" }
if slot == 7 { return "تَرَوْنَ" }
if slot == 8 { return "تَرَيْنَ" }
return "نَرَى"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_raaa(slot, "present")
return "سَ" + pres
}
return "رَأَى"
}
fn ar_irregular_araada(slot: Int, tense: String) -> String {
// أَرَادَ to want (Form IV hollow)
if str_eq(tense, "past") {
if slot == 0 { return "أَرَادَ" }
if slot == 1 { return "أَرَادَتْ" }
if slot == 2 { return "أَرَدْتَ" }
if slot == 3 { return "أَرَدْتِ" }
if slot == 4 { return "أَرَدْتُ" }
if slot == 5 { return "أَرَادُوا" }
if slot == 6 { return "أَرَدْنَ" }
if slot == 7 { return "أَرَدْتُمْ" }
if slot == 8 { return "أَرَدْتُنَّ" }
return "أَرَدْنَا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يُرِيدُ" }
if slot == 1 { return "تُرِيدُ" }
if slot == 2 { return "تُرِيدُ" }
if slot == 3 { return "تُرِيدِينَ" }
if slot == 4 { return "أُرِيدُ" }
if slot == 5 { return "يُرِيدُونَ" }
if slot == 6 { return "يُرِدْنَ" }
if slot == 7 { return "تُرِيدُونَ" }
if slot == 8 { return "تُرِدْنَ" }
return "نُرِيدُ"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_araada(slot, "present")
return "سَ" + pres
}
return "أَرَادَ"
}
fn ar_irregular_istata(slot: Int, tense: String) -> String {
// اِسْتَطَاعَ can / be able (Form X hollow)
if str_eq(tense, "past") {
if slot == 0 { return "اِسْتَطَاعَ" }
if slot == 1 { return "اِسْتَطَاعَتْ" }
if slot == 2 { return "اِسْتَطَعْتَ" }
if slot == 3 { return "اِسْتَطَعْتِ" }
if slot == 4 { return "اِسْتَطَعْتُ" }
if slot == 5 { return "اِسْتَطَاعُوا" }
if slot == 6 { return "اِسْتَطَعْنَ" }
if slot == 7 { return "اِسْتَطَعْتُمْ" }
if slot == 8 { return "اِسْتَطَعْتُنَّ" }
return "اِسْتَطَعْنَا"
}
if str_eq(tense, "present") {
if slot == 0 { return "يَسْتَطِيعُ" }
if slot == 1 { return "تَسْتَطِيعُ" }
if slot == 2 { return "تَسْتَطِيعُ" }
if slot == 3 { return "تَسْتَطِيعِينَ" }
if slot == 4 { return "أَسْتَطِيعُ" }
if slot == 5 { return "يَسْتَطِيعُونَ" }
if slot == 6 { return "يَسْتَطِعْنَ" }
if slot == 7 { return "تَسْتَطِيعُونَ" }
if slot == 8 { return "تَسْتَطِعْنَ" }
return "نَسْتَطِيعُ"
}
if str_eq(tense, "future") {
let pres: String = ar_irregular_istata(slot, "present")
return "سَ" + pres
}
return "اِسْتَطَاعَ"
}
// Irregular verb dispatcher
//
// ar_irregular: returns the inflected form if verb is in the lookup table,
// or "" if not found (caller should use Form I rules).
//
// verb: 3ms past form (dictionary key) as Arabic string
// tense: "past" | "present" | "future"
// slot: ar_slot result
fn ar_irregular(verb: String, tense: String, slot: Int) -> String {
if str_eq(verb, "كَانَ") { return ar_irregular_kaana(slot, tense) }
if str_eq(verb, "قَالَ") { return ar_irregular_qaala(slot, tense) }
if str_eq(verb, "جَاءَ") { return ar_irregular_jaa(slot, tense) }
if str_eq(verb, "رَأَى") { return ar_irregular_raaa(slot, tense) }
if str_eq(verb, "أَرَادَ") { return ar_irregular_araada(slot, tense) }
if str_eq(verb, "اِسْتَطَاعَ") { return ar_irregular_istata(slot, tense) }
return ""
}
// Regular Form I verb table
//
// For regular Form I verbs that would be correctly generated by ar_conjugate_form1
// but whose imperfect stem must be looked up (Arabic verbs have three vowel
// classes for the imperfect medial vowel: a, i, u فَعَلَ/يَفْعَلُ,
// فَعِلَ/يَفْعَلُ, فَعَلَ/يَفْعُلُ). We store the present stem for each.
//
// Returns present_stem (the imperfect without prefix, e.g. "كْتُبُ" for yaktubu),
// or "" if not in table.
fn ar_present_stem(verb: String) -> String {
if str_eq(verb, "كَتَبَ") { return "كْتُبُ" } // kataba -> yaktubu (u-class)
if str_eq(verb, "ذَهَبَ") { return "ذْهَبُ" } // dhahaba -> yadhhabu (a-class)
if str_eq(verb, "أَكَلَ") { return "أْكُلُ" } // akala -> yaakulu (u-class)
if str_eq(verb, "شَرِبَ") { return "شْرَبُ" } // shariba -> yashrabu (a-class)
if str_eq(verb, "عَرَفَ") { return "عْرِفُ" } // arafa -> yarifu (i-class)
if str_eq(verb, "فَعَلَ") { return "فْعَلُ" } // fa'ala -> yaf'alu (a-class)
if str_eq(verb, "أَخَذَ") { return "أْخُذُ" } // akhadha -> yaakhudhu (u-class)
if str_eq(verb, "عَمِلَ") { return "عْمَلُ" } // amila -> ya'malu (a-class)
if str_eq(verb, "دَرَسَ") { return "دْرُسُ" } // darasa -> yadrusu (u-class)
if str_eq(verb, "فَهِمَ") { return "فْهَمُ" } // fahima -> yafhamu (a-class)
if str_eq(verb, "سَمِعَ") { return "سْمَعُ" } // sami'a -> yasma'u (a-class)
if str_eq(verb, "جَلَسَ") { return "جْلِسُ" } // jalasa -> yajlisu (i-class)
if str_eq(verb, "فَتَحَ") { return "فْتَحُ" } // fataha -> yaftahu (a-class)
if str_eq(verb, "خَرَجَ") { return "خْرُجُ" } // kharaja -> yakhruju (u-class)
if str_eq(verb, "دَخَلَ") { return "دْخُلُ" } // dakhala -> yadkhulu (u-class)
if str_eq(verb, "وَجَدَ") { return "جِدُ" } // wajada -> yajidu (i-class, waw-initial)
if str_eq(verb, "صَنَعَ") { return "صْنَعُ" } // sana'a -> yasna'u (a-class)
if str_eq(verb, "رَجَعَ") { return "رْجِعُ" } // raja'a -> yarji'u (i-class)
if str_eq(verb, "وَقَفَ") { return "قِفُ" } // waqafa -> yaqifu (i-class, waw-initial)
if str_eq(verb, "قَرَأَ") { return "قْرَأُ" } // qara'a -> yaqra'u (a-class)
if str_eq(verb, "كَذَبَ") { return "كْذِبُ" } // kadhaba -> yakdhibu (i-class)
return ""
}
// Main conjugation dispatcher
//
// ar_conjugate: conjugate an Arabic verb.
//
// verb: 3ms perfect form (dictionary key), e.g. "كَتَبَ"
// tense: "past" | "present" | "future"
// person: "first" | "second" | "third"
// gender: "m" | "f"
// number: "singular" | "plural"
fn ar_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String {
let slot: Int = ar_slot(person, gender, number)
// 1. Check irregular table
let irreg: String = ar_irregular(verb, tense, slot)
if !str_eq(irreg, "") {
return irreg
}
// 2. Look up present stem for regular Form I
let present_stem: String = ar_present_stem(verb)
if !str_eq(present_stem, "") {
return ar_conjugate_form1(verb, present_stem, tense, slot)
}
// 3. Fallback: return base form (3ms past) unknown verb
return verb
}
// Definite article
//
// ar_definite_article: prefix ال (al-) to a noun with sun/moon letter handling.
//
// Sun letters (الحروف الشمسية) cause the lam of the article to assimilate to
// the first letter of the noun. Moon letters (الحروف القمرية) do not.
//
// Sun letters (Unicode Arabic code points):
// ت ث د ذ ر ز س ش ص ض ط ظ ل ن
//
// Moon letters (all others):
// أ ب ج ح خ ع غ ف ق ك م ه و ي
//
// In Arabic orthography the assimilation is shown with a shadda on the sun letter.
// Here we return "ال" (al-) for moon letters and the assimilated form for sun
// letters. The noun is prefixed with the article; the article lam is replaced
// by a shadda on the sun consonant.
fn ar_is_sun_letter(c: String) -> Bool {
if str_eq(c, "ت") { return true }
if str_eq(c, "ث") { return true }
if str_eq(c, "د") { return true }
if str_eq(c, "ذ") { return true }
if str_eq(c, "ر") { return true }
if str_eq(c, "ز") { return true }
if str_eq(c, "س") { return true }
if str_eq(c, "ش") { return true }
if str_eq(c, "ص") { return true }
if str_eq(c, "ض") { return true }
if str_eq(c, "ط") { return true }
if str_eq(c, "ظ") { return true }
if str_eq(c, "ل") { return true }
if str_eq(c, "ن") { return true }
return false
}
fn ar_definite_article(noun: String) -> String {
// Extract first character to determine sun/moon
let n: Int = ar_str_len(noun)
if n == 0 {
return noun
}
let first: String = str_slice(noun, 0, 1)
if ar_is_sun_letter(first) {
// Sun letter: article lam assimilates -> الـ + shadda on first letter
// Written as: أَلْ + first + shadda + rest
// We represent this as "ال" + first_with_shadda + rest_of_noun
// The shadda diacritic (U+0651) attaches to the sun letter.
let shadda: String = "ّ"
let rest: String = str_slice(noun, 1, n)
return "ال" + first + shadda + rest
}
// Moon letter: simple al- prefix
return "ال" + noun
}
// Case endings
//
// ar_case_ending: return the short vowel ending for a noun given its gram_case
// and definiteness.
//
// case: "nom" | "acc" | "gen"
// definite: "true" | "false"
//
// Indefinite endings carry nunation (tanwin):
// nom: -un (ٌ)
// acc: -an (ً)
// gen: -in (ٍ)
//
// Definite endings are single short vowels:
// nom: -u (ُ)
// acc: -a (َ)
// gen: -i (ِ)
fn ar_case_ending(kase: String, definite: String) -> String {
let is_def: Bool = str_eq(definite, "true")
if str_eq(kase, "nom") {
if is_def { return "ُ" }
return "ٌ"
}
if str_eq(kase, "acc") {
if is_def { return "َ" }
return "ً"
}
if str_eq(kase, "gen") {
if is_def { return "ِ" }
return "ٍ"
}
return ""
}
// Gender inference
//
// ar_gender: infer gender from noun form.
// Returns "f" for nouns ending in taa marbuta (ة or ـة), otherwise "m".
// This covers the most reliable heuristic; broken plurals and loanwords may
// vary but are handled by explicit lookup in the Engram.
fn ar_gender(noun: String) -> String {
if ar_str_ends(noun, "ة") { return "f" }
if ar_str_ends(noun, "ـة") { return "f" }
return "m"
}
// Sound plurals
//
// ar_sound_plural: form the sound masculine or feminine plural.
//
// Sound masculine plural (جمع المذكر السالم):
// nom: -uuna (ونَ)
// acc/gen: -iina (ينَ)
//
// Sound feminine plural (جمع المؤنث السالم):
// Remove final ة (taa marbuta) if present, then add -aat (اتٌ/اتُ).
//
// This function returns the base plural form (without case ending) suitable
// for passing to ar_noun_form. For masculine plural case variation, callers
// should use ar_masc_pl_ending.
fn ar_masc_pl_ending(kase: String) -> String {
if str_eq(kase, "nom") { return "ونَ" }
// acc and gen both use -iina in sound masculine plural
return "ينَ"
}
fn ar_sound_plural(noun: String, gender: String) -> String {
if str_eq(gender, "f") {
// Feminine sound plural: drop ة, add ات
if ar_str_ends(noun, "ة") {
let base: String = ar_str_drop_last(noun, 1)
return base + "ات"
}
return noun + "ات"
}
// Masculine sound plural (nominative form as default): -uuna
return noun + "ون"
}
// Full noun inflection
//
// ar_noun_form: produce the inflected noun form.
//
// noun: base (singular) noun string
// gender: "m" | "f" (pass "" to infer from noun ending)
// kase: "nom" | "acc" | "gen" | "" (no case ending added)
// number: "singular" | "plural"
// definite: "true" | "false"
//
// For plurals, the function applies the sound plural (broken plurals are
// language-external and must be supplied via Engram vocabulary nodes).
fn ar_noun_form(noun: String, gender: String, kase: String, number: String, definite: String) -> String {
// Resolve gender
let g: String = gender
if str_eq(g, "") {
let g = ar_gender(noun)
}
// Build the stem (with definiteness and number)
let stem: String = noun
if str_eq(number, "plural") {
if str_eq(g, "m") {
// Masculine sound plural: stem + case-dependent ending
let pl_suf: String = ar_masc_pl_ending(kase)
if str_eq(definite, "true") {
let def_stem: String = ar_definite_article(noun)
return def_stem + pl_suf
}
return noun + pl_suf
}
// Feminine plural: drop ة, add ات + case ending
let fem_pl: String = ar_sound_plural(noun, "f")
let case_end: String = ar_case_ending(kase, definite)
if str_eq(definite, "true") {
return ar_definite_article(fem_pl) + case_end
}
return fem_pl + case_end
}
// Singular
let case_end: String = ar_case_ending(kase, definite)
if str_eq(definite, "true") {
let def_stem: String = ar_definite_article(noun)
return def_stem + case_end
}
return noun + case_end
}
// Convenience: verb inflect entry point
//
// ar_verb_form: thin wrapper matching the signature style of the main engine.
// Accepts gender as part of person encoding: "third_m" | "third_f" | "first" | "second_m" | "second_f".
// Alternatively accepts explicit gender param.
fn ar_verb_form(verb: String, tense: String, person: String, number: String) -> String {
// Default gender to masculine
return ar_conjugate(verb, tense, person, "m", number)
}