Files
el/elp/src/morphology-ar.el
2026-05-02 22:15:25 -05:00

729 lines
31 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-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)
}