// 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) }