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