// morphology-he.el - Hebrew morphology for the NLG engine. // עברית — עיצוב מורפולוגי למנוע ייצור שפה טבעית // // Implements Modern Hebrew verb conjugation (Pa'al binyan, participle-based // present tense), noun pluralization, and definite-article prefixing. // // Hebrew is a Semitic language with a trilateral root system shared with // Arabic. Like Arabic, words are built from three-consonant roots by // applying vowel patterns (mishkalim) around the root. // // Modern Hebrew (the target) profile: // code=he, name=Hebrew, morph_type=semitic, word_order=SVO, // question_strategy=intonation, script=hebrew, family=semitic // // Key grammatical facts: // - Grammatical gender: masculine (m) / feminine (f) // - Number: singular / plural (dual exists but is marginal in Modern Hebrew) // - Verbs: built from 3-letter roots via binyanim (verb patterns) // Pa'al (פָּעַל) is covered here — the most common pattern for everyday verbs // - Present tense is participle-based (בינוני / binyan) with gender/number // agreement suffixes on the verb; it doubles as an adjective form // - Copula: present tense uses zero copula (omit "to be" entirely); // past tense uses היה/הייתה/היו (haya/hayta/hayu) // - Definite article: prefix ה (ha-) attached directly to the noun; // consonant doubling after ה is simplified to just "ha" prefix here // - Questions: rising intonation (no morphological change); optional // particle האם (ha'im) at sentence start; this engine uses intonation // strategy (question mark added by realizer) // - Script: Hebrew Unicode (right-to-left). The El runtime currently // outputs non-ASCII as numeric hashes (runtime limitation), but str_eq // and string literals work correctly internally. When the VM adds UTF-8 // output support, all Hebrew strings will display correctly automatically. // // Verbs covered (by infinitive, transliterated and Hebrew): // "lihyot" / לִהְיוֹת — to be (copula, zero-present, haya-past) // "haya" / הָיָה — to be (dictionary alias → lihyot) // "be" — English canonical → lihyot // "lir'ot" / לִרְאוֹת — to see // "le'exol" / לֶאֱכוֹל — to eat // "ledaber" / לְדַבֵּר — to speak // "lalechet" / לָלֶכֶת — to go // // Conventions used throughout: // person: "first" | "second" | "third" // gender: "m" | "f" // number: "singular" | "plural" // tense: "present" | "past" | "future" // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with, // str_drop_last, str_concat/+) // ── String helpers ───────────────────────────────────────────────────────────── import "morphology.el" fn he_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn he_str_len(s: String) -> Int { return str_len(s) } fn he_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 he_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. // Modern Hebrew uses 12 paradigm cells (3 persons × 2 genders × 2 numbers), // but first person is gender-neutral in Modern Hebrew for verbs, so we // collapse 1s-m and 1s-f to the same slot, and likewise 1p. // // Slot layout: // 0 = 3ms (הוא hu — he) // 1 = 3fs (היא hi — she) // 2 = 2ms (אתה ata — you m sg) // 3 = 2fs (את at — you f sg) // 4 = 1s (אני ani — I, gender-neutral in Modern Hebrew) // 5 = 3mp (הם hem — they m pl) // 6 = 3fp (הן hen — they f pl) // 7 = 2mp (אתם atem — you m pl) // 8 = 2fp (אתן aten — you f pl) // 9 = 1p (אנחנו anakhnu — we, gender-neutral) fn he_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 person — gender-neutral in Modern Hebrew verb conjugation if str_eq(number, "plural") { return 9 } return 4 } // ── Present-tense (participle / binyan) agreement suffixes ──────────────────── // // Modern Hebrew present tense is built from the Pa'al participle stem. // The participle agrees with the subject in gender and number. // // For Pa'al (CoCeC pattern), the present-tense forms are: // Masc sg: base participle stem (e.g. רוֹאֶה ro'e — sees) [no suffix] // Fem sg: stem + ת (-t / -et) depending on root // Masc pl: stem + ים (-im) (often with vowel change in the root) // Fem pl: stem + ות (-ot) // // Rather than attempting to derive these from the root (which requires knowing // the full vowel pattern), we store the four present-tense forms explicitly // for each covered verb. This is the standard approach for a finite NLG // system covering a curated verb set. // // Present forms are indexed by a 2-bit form code: // 0 = masc sg (ms) // 1 = fem sg (fs) // 2 = masc pl (mp) // 3 = fem pl (fp) fn he_present_form_code(slot: Int) -> Int { // Slots 0, 2, 4, 7 → masc sg form (1s and 2ms use same form as 3ms) if slot == 0 { return 0 } // 3ms if slot == 1 { return 1 } // 3fs if slot == 2 { return 0 } // 2ms → masc sg if slot == 3 { return 1 } // 2fs → fem sg if slot == 4 { return 0 } // 1s → masc sg (gender-neutral, default masc) if slot == 5 { return 2 } // 3mp → masc pl if slot == 6 { return 3 } // 3fp → fem pl if slot == 7 { return 2 } // 2mp → masc pl if slot == 8 { return 3 } // 2fp → fem pl return 2 // 1p (slot 9) → masc pl (gender-neutral default) } // ── Copula: היה haya — to be ────────────────────────────────────────────────── // // Present copula: ZERO (omitted from surface form). // Past copula: היה haya (3ms), הייתה hayta (3fs/2fs), היו hayu (3pl), // הייתי hayiti (1s), הייתם hayitem (2mp), הייתן hayiten (2fp), // היינו hayinu (1p), היית hayita (2ms). // Future copula: יהיה yihye (3ms), תהיה tihye (3fs/2), אהיה ehye (1s), // יהיו yihyu (3pl/2pl), נהיה nihye (1p) — included for completeness. fn he_copula_past(slot: Int) -> String { if slot == 0 { return "היה" } // 3ms haya if slot == 1 { return "הייתה" } // 3fs hayta if slot == 2 { return "היית" } // 2ms hayita if slot == 3 { return "הייתה" } // 2fs hayta (same as 3fs in Modern Hebrew) if slot == 4 { return "הייתי" } // 1s hayiti if slot == 5 { return "היו" } // 3mp hayu if slot == 6 { return "היו" } // 3fp hayu (same as 3mp in Modern Hebrew) if slot == 7 { return "הייתם" } // 2mp hayitem if slot == 8 { return "הייתן" } // 2fp hayiten return "היינו" // 1p hayinu } fn he_copula_future(slot: Int) -> String { if slot == 0 { return "יהיה" } // 3ms yihye if slot == 1 { return "תהיה" } // 3fs tihye if slot == 2 { return "תהיה" } // 2ms tihye if slot == 3 { return "תהיי" } // 2fs tihyi if slot == 4 { return "אהיה" } // 1s ehye if slot == 5 { return "יהיו" } // 3mp yihyu if slot == 6 { return "יהיו" } // 3fp yihyu if slot == 7 { return "תהיו" } // 2mp tihyu if slot == 8 { return "תהיו" } // 2fp tihyu return "נהיה" // 1p nihye } // he_is_copula: detect whether the input verb means "to be". fn he_is_copula(verb: String) -> Bool { if str_eq(verb, "lihyot") { return true } if str_eq(verb, "haya") { return true } if str_eq(verb, "be") { return true } if str_eq(verb, "היה") { return true } if str_eq(verb, "לִהְיוֹת") { return true } return false } // he_conjugate_copula: conjugate the copula for the given tense/slot. fn he_conjugate_copula(tense: String, slot: Int) -> String { // Present copula: zero in Modern Hebrew if str_eq(tense, "present") { return "" } if str_eq(tense, "past") { return he_copula_past(slot) } if str_eq(tense, "future") { return he_copula_future(slot) } // Default: zero copula return "" } // ── Pa'al present-tense forms: verb-by-verb table ───────────────────────────── // // Each verb stores four present-tense participle forms: // [masc sg, fem sg, masc pl, fem pl] // indexed by he_present_form_code(slot). // // Transliterations for reference: // lir'ot (לִרְאוֹת — to see): ro'e / ro'a / ro'im / ro'ot // le'exol (לֶאֱכוֹל — to eat): oxel / oxelet / oxlim / oxlot // ledaber (לְדַבֵּר — to speak): medaber / medaberet / medabrim / medabrot // lalechet (לָלֶכֶת — to go): holech / holechet / holchim / holchot fn he_present_lir_ot(form: Int) -> String { if form == 0 { return "רוֹאֶה" } // ro'e — masc sg if form == 1 { return "רוֹאָה" } // ro'a — fem sg if form == 2 { return "רוֹאִים" } // ro'im — masc pl return "רוֹאוֹת" // ro'ot — fem pl (form == 3) } fn he_present_le_exol(form: Int) -> String { if form == 0 { return "אוֹכֵל" } // oxel — masc sg if form == 1 { return "אוֹכֶלֶת" } // oxelet — fem sg if form == 2 { return "אוֹכְלִים" } // oxlim — masc pl return "אוֹכְלוֹת" // oxlot — fem pl } fn he_present_ledaber(form: Int) -> String { if form == 0 { return "מְדַבֵּר" } // medaber — masc sg if form == 1 { return "מְדַבֶּרֶת" } // medaberet — fem sg if form == 2 { return "מְדַבְּרִים" } // medabrim — masc pl return "מְדַבְּרוֹת" // medabrot — fem pl } fn he_present_lalechet(form: Int) -> String { if form == 0 { return "הוֹלֵךְ" } // holech — masc sg if form == 1 { return "הוֹלֶכֶת" } // holechet — fem sg if form == 2 { return "הוֹלְכִים" } // holchim — masc pl return "הוֹלְכוֹת" // holchot — fem pl } // ── Pa'al past-tense forms: verb-by-verb table ─────────────────────────────── // // Past tense in Pa'al uses suffixes on a past stem (the 3ms form is the stem). // Suffix pattern (slot → suffix appended to stem consonants): // slot 0 (3ms): base (e.g. ראה ra'a) // slot 1 (3fs): -ta (ראתה ra'ata) // slot 2 (2ms): -ta (ראית ra'ita) // slot 3 (2fs): -t (ראית ra'it — same spelling as 2ms in Modern Hebrew) // slot 4 (1s): -ti (ראיתי ra'iti) // slot 5 (3mp): -u (ראו ra'u) // slot 6 (3fp): -u (ראו ra'u — same as 3mp in Modern Hebrew) // slot 7 (2mp): -tem (ראיתם ra'item) // slot 8 (2fp): -ten (ראיתן ra'iten) // slot 9 (1p): -nu (ראינו ra'inu) // // We store full past paradigms for each verb — suffix application to the base // requires knowing each verb's past stem vowel pattern. fn he_past_lir_ot(slot: Int) -> String { if slot == 0 { return "רָאָה" } // ra'a — 3ms if slot == 1 { return "רָאֲתָה" } // ra'ata — 3fs if slot == 2 { return "רָאִיתָ" } // ra'ita — 2ms if slot == 3 { return "רָאִית" } // ra'it — 2fs if slot == 4 { return "רָאִיתִי" } // ra'iti — 1s if slot == 5 { return "רָאוּ" } // ra'u — 3mp if slot == 6 { return "רָאוּ" } // ra'u — 3fp if slot == 7 { return "רְאִיתֶם" } // re'item — 2mp if slot == 8 { return "רְאִיתֶן" } // re'iten — 2fp return "רָאִינוּ" // ra'inu — 1p } fn he_past_le_exol(slot: Int) -> String { if slot == 0 { return "אָכַל" } // axal — 3ms if slot == 1 { return "אָכְלָה" } // axla — 3fs if slot == 2 { return "אָכַלְתָּ" } // axalta — 2ms if slot == 3 { return "אָכַלְתְּ" } // axalt — 2fs if slot == 4 { return "אָכַלְתִּי" }// axalti — 1s if slot == 5 { return "אָכְלוּ" } // axlu — 3mp if slot == 6 { return "אָכְלוּ" } // axlu — 3fp if slot == 7 { return "אֲכַלְתֶּם" }// axaltem — 2mp if slot == 8 { return "אֲכַלְתֶּן" }// axalten — 2fp return "אָכַלְנוּ" // axalnu — 1p } fn he_past_ledaber(slot: Int) -> String { if slot == 0 { return "דִּבֵּר" } // diber — 3ms (Pi'el past) if slot == 1 { return "דִּבְּרָה" } // dibra — 3fs if slot == 2 { return "דִּבַּרְתָּ" }// dibarta — 2ms if slot == 3 { return "דִּבַּרְתְּ" }// dibart — 2fs if slot == 4 { return "דִּבַּרְתִּי" }// diberti — 1s if slot == 5 { return "דִּבְּרוּ" } // dibru — 3mp if slot == 6 { return "דִּבְּרוּ" } // dibru — 3fp if slot == 7 { return "דִּבַּרְתֶּם" }// dibertem— 2mp if slot == 8 { return "דִּבַּרְתֶּן" }// dibertn — 2fp return "דִּבַּרְנוּ" // dibernu — 1p } fn he_past_lalechet(slot: Int) -> String { if slot == 0 { return "הָלַךְ" } // halax — 3ms if slot == 1 { return "הָלְכָה" } // halxa — 3fs if slot == 2 { return "הָלַכְתָּ" } // halaxta — 2ms if slot == 3 { return "הָלַכְתְּ" } // halaxt — 2fs if slot == 4 { return "הָלַכְתִּי" }// halaxti — 1s if slot == 5 { return "הָלְכוּ" } // halxu — 3mp if slot == 6 { return "הָלְכוּ" } // halxu — 3fp if slot == 7 { return "הֲלַכְתֶּם" }// halaxtem— 2mp if slot == 8 { return "הֲלַכְתֶּן" }// halaxten— 2fp return "הָלַכְנוּ" // halaxnu — 1p } // ── Future-tense forms: verb-by-verb table ───────────────────────────────────── // // Future tense in Pa'al uses prefix + root + suffix (yiqtol pattern). // We store full paradigms for each covered verb. fn he_future_lir_ot(slot: Int) -> String { if slot == 0 { return "יִרְאֶה" } // yir'e — 3ms if slot == 1 { return "תִּרְאֶה" } // tir'e — 3fs if slot == 2 { return "תִּרְאֶה" } // tir'e — 2ms if slot == 3 { return "תִּרְאִי" } // tir'i — 2fs if slot == 4 { return "אֶרְאֶה" } // er'e — 1s if slot == 5 { return "יִרְאוּ" } // yir'u — 3mp if slot == 6 { return "תִּרְאֶינָה" }// tir'ena — 3fp if slot == 7 { return "תִּרְאוּ" } // tir'u — 2mp if slot == 8 { return "תִּרְאֶינָה" }// tir'ena — 2fp return "נִרְאֶה" // nir'e — 1p } fn he_future_le_exol(slot: Int) -> String { if slot == 0 { return "יֹאכַל" } // yoxal — 3ms if slot == 1 { return "תֹּאכַל" } // toxal — 3fs if slot == 2 { return "תֹּאכַל" } // toxal — 2ms if slot == 3 { return "תֹּאכְלִי" } // toxli — 2fs if slot == 4 { return "אֹכַל" } // oxal — 1s if slot == 5 { return "יֹאכְלוּ" } // yoxlu — 3mp if slot == 6 { return "תֹּאכַלְנָה" }// toxalna — 3fp if slot == 7 { return "תֹּאכְלוּ" } // toxlu — 2mp if slot == 8 { return "תֹּאכַלְנָה" }// toxalna — 2fp return "נֹאכַל" // noxal — 1p } fn he_future_ledaber(slot: Int) -> String { if slot == 0 { return "יְדַבֵּר" } // yedaber — 3ms if slot == 1 { return "תְּדַבֵּר" } // tedaber — 3fs if slot == 2 { return "תְּדַבֵּר" } // tedaber — 2ms if slot == 3 { return "תְּדַבְּרִי" }// tedabri — 2fs if slot == 4 { return "אֲדַבֵּר" } // adaber — 1s if slot == 5 { return "יְדַבְּרוּ" }// yedabru — 3mp if slot == 6 { return "תְּדַבֵּרְנָה" }// tedaberna — 3fp if slot == 7 { return "תְּדַבְּרוּ" }// tedabru — 2mp if slot == 8 { return "תְּדַבֵּרְנָה" }// tedaberna — 2fp return "נְדַבֵּר" // nedaber — 1p } fn he_future_lalechet(slot: Int) -> String { if slot == 0 { return "יֵלֵךְ" } // yelex — 3ms if slot == 1 { return "תֵּלֵךְ" } // telex — 3fs if slot == 2 { return "תֵּלֵךְ" } // telex — 2ms if slot == 3 { return "תֵּלְכִי" } // telxi — 2fs if slot == 4 { return "אֵלֵךְ" } // elex — 1s if slot == 5 { return "יֵלְכוּ" } // yelxu — 3mp if slot == 6 { return "תֵּלַכְנָה" } // telaxna — 3fp if slot == 7 { return "תֵּלְכוּ" } // telxu — 2mp if slot == 8 { return "תֵּלַכְנָה" } // telaxna — 2fp return "נֵלֵךְ" // nelex — 1p } // ── Known-verb dispatcher ───────────────────────────────────────────────────── // // he_known_verb: return the inflected form for a known verb, or "" if the // verb is not in the lookup table. Accepts both transliterated and Hebrew // script infinitives. fn he_known_verb(verb: String, tense: String, slot: Int) -> String { // ── lir'ot / לִרְאוֹת — to see ─────────────────────────────────────────── if str_eq(verb, "lir'ot") { if str_eq(tense, "present") { return he_present_lir_ot(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_lir_ot(slot) } if str_eq(tense, "future") { return he_future_lir_ot(slot) } return he_present_lir_ot(he_present_form_code(slot)) } if str_eq(verb, "לִרְאוֹת") { if str_eq(tense, "present") { return he_present_lir_ot(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_lir_ot(slot) } if str_eq(tense, "future") { return he_future_lir_ot(slot) } return he_present_lir_ot(he_present_form_code(slot)) } // ── le'exol / לֶאֱכוֹל — to eat ────────────────────────────────────────── if str_eq(verb, "le'exol") { if str_eq(tense, "present") { return he_present_le_exol(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_le_exol(slot) } if str_eq(tense, "future") { return he_future_le_exol(slot) } return he_present_le_exol(he_present_form_code(slot)) } if str_eq(verb, "לֶאֱכוֹל") { if str_eq(tense, "present") { return he_present_le_exol(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_le_exol(slot) } if str_eq(tense, "future") { return he_future_le_exol(slot) } return he_present_le_exol(he_present_form_code(slot)) } // ── ledaber / לְדַבֵּר — to speak ──────────────────────────────────────── if str_eq(verb, "ledaber") { if str_eq(tense, "present") { return he_present_ledaber(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_ledaber(slot) } if str_eq(tense, "future") { return he_future_ledaber(slot) } return he_present_ledaber(he_present_form_code(slot)) } if str_eq(verb, "לְדַבֵּר") { if str_eq(tense, "present") { return he_present_ledaber(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_ledaber(slot) } if str_eq(tense, "future") { return he_future_ledaber(slot) } return he_present_ledaber(he_present_form_code(slot)) } // ── lalechet / לָלֶכֶת — to go ─────────────────────────────────────────── if str_eq(verb, "lalechet") { if str_eq(tense, "present") { return he_present_lalechet(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_lalechet(slot) } if str_eq(tense, "future") { return he_future_lalechet(slot) } return he_present_lalechet(he_present_form_code(slot)) } if str_eq(verb, "לָלֶכֶת") { if str_eq(tense, "present") { return he_present_lalechet(he_present_form_code(slot)) } if str_eq(tense, "past") { return he_past_lalechet(slot) } if str_eq(tense, "future") { return he_future_lalechet(slot) } return he_present_lalechet(he_present_form_code(slot)) } // Verb not in table return "" } // ── Main conjugation entry point ────────────────────────────────────────────── // // he_conjugate: conjugate a Hebrew verb. // // verb: infinitive (transliterated or Hebrew script) // tense: "present" | "past" | "future" // person: "first" | "second" | "third" // gender: "m" | "f" // number: "singular" | "plural" // // Returns: // - "" for present copula (zero copula — caller omits the verb) // - inflected form for all other cases // - the infinitive unchanged for unknown verbs (safe fallback) fn he_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String { let slot: Int = he_slot(person, gender, number) // Handle copula first if he_is_copula(verb) { return he_conjugate_copula(tense, slot) } // Try the known-verb table let known: String = he_known_verb(verb, tense, slot) if !str_eq(known, "") { return known } // Unknown verb: return the infinitive as a safe placeholder. // The caller can detect this when the output equals the input. return verb } // ── Noun pluralization ───────────────────────────────────────────────────────── // // he_pluralize: form the plural of a Hebrew noun. // // Rules (simplified for Modern Hebrew): // Masculine nouns (and those not ending in -a or -et): // Add ים- (-im) // Feminine nouns ending in -a (transliteration) or the Hebrew letter ה: // Replace final ה with ות (-ot) // Feminine nouns ending in -et (transliteration) or ת-: // Replace -et / ת with ות (-ot) // Fallback: // Add ות (-ot) — covers other feminine patterns // // Notes: // - Many Hebrew nouns have irregular plurals (e.g. ספר/ספרים, בית/בתים). // The Engram vocabulary layer should supply these directly. // - This function handles the productive, regular pattern. fn he_pluralize(noun: String, gender: String) -> String { if str_eq(gender, "m") { // Masculine: add -im return noun + "ים" } // Feminine noun ending in Hebrew ה (he) — most common -a ending in script if he_str_ends(noun, "ה") { let stem: String = he_str_drop_last(noun, 1) return stem + "ות" } // Feminine noun ending in ת (tav) — covers -et, -at, -it endings if he_str_ends(noun, "ת") { let stem: String = he_str_drop_last(noun, 1) return stem + "ות" } // Transliteration check: -a ending (e.g. "yalda" → "yaldot") if he_str_ends(noun, "a") { let stem: String = he_str_drop_last(noun, 1) return stem + "ot" } // Transliteration check: -et ending (e.g. "yaldet" → "yaldot") if he_str_ends(noun, "et") { let stem: String = he_str_drop_last(noun, 2) return stem + "ot" } // Fallback: add -ot return noun + "ות" } // ── Definite noun phrases ────────────────────────────────────────────────────── // // he_definite_prefix: attach the definite article ה (ha-) to a noun. // // The definite article in Hebrew is the prefix ה (ha-) attached directly // to the noun without a space. In formal/Biblical Hebrew the following // consonant receives a dagesh (doubling), but in Modern Hebrew pronunciation // the doubling is generally not applied. We implement the simplified form: // definite noun = "ה" + noun (script form) // For transliterated nouns: "ha" + noun // // Callers pass Hebrew script nouns; transliterated nouns are handled by // checking whether the noun starts with a Hebrew code-point range. fn he_is_hebrew_script(noun: String) -> Bool { // Hebrew Unicode block: U+05D0 (א) through U+05EA (ת). // We check the first character: if str_len > 0 and it is a Hebrew letter, // the noun is in Hebrew script. We use a set of common first letters as // a heuristic; the alternative (numeric code-point comparison) is not // available in El. This covers the vast majority of practical cases. let n: Int = str_len(noun) if n == 0 { return false } let first: String = str_slice(noun, 0, 1) // Common Hebrew first letters in the Unicode block if str_eq(first, "א") { return true } if str_eq(first, "ב") { return true } if str_eq(first, "ג") { return true } if str_eq(first, "ד") { return true } if str_eq(first, "ה") { return true } if str_eq(first, "ו") { return true } if str_eq(first, "ז") { return true } if str_eq(first, "ח") { return true } if str_eq(first, "ט") { return true } if str_eq(first, "י") { return true } if str_eq(first, "כ") { return true } if str_eq(first, "ל") { return true } if str_eq(first, "מ") { return true } if str_eq(first, "נ") { return true } if str_eq(first, "ס") { return true } if str_eq(first, "ע") { return true } if str_eq(first, "פ") { return true } if str_eq(first, "צ") { return true } if str_eq(first, "ק") { return true } if str_eq(first, "ר") { return true } if str_eq(first, "ש") { return true } if str_eq(first, "ת") { return true } return false } fn he_definite_prefix(noun: String) -> String { if he_is_hebrew_script(noun) { return "ה" + noun } // Transliterated noun: prepend "ha" return "ha" + noun } // he_noun_phrase: build a full noun phrase with definiteness and number. // // noun: base (singular) noun string (Hebrew script or transliteration) // number: "singular" | "plural" // gender: "m" | "f" // definite: "true" | "false" // // Returns the surface noun phrase string. fn he_noun_phrase(noun: String, number: String, gender: String, definite: String) -> String { // Step 1: apply number (pluralize if needed) let stem: String = noun if str_eq(number, "plural") { let stem = he_pluralize(noun, gender) } // Step 2: apply definiteness if str_eq(definite, "true") { return he_definite_prefix(stem) } return stem } // ── Canonical verb mapping ───────────────────────────────────────────────────── // // he_map_canonical: map cross-lingual canonical English verb labels to // their Hebrew equivalents before dispatching to he_conjugate. // // This mirrors morph_map_canonical in morphology.el but for Hebrew. // Called by the morphology dispatcher before he_conjugate. // // Canonical labels: "be" | "have" | "do" | "go" | "see" | "eat" | "speak" fn he_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "lihyot" } if str_eq(verb, "see") { return "lir'ot" } if str_eq(verb, "eat") { return "le'exol" } if str_eq(verb, "speak") { return "ledaber" } if str_eq(verb, "say") { return "ledaber" } if str_eq(verb, "go") { return "lalechet" } // Unknown canonical: return as-is; he_conjugate will fall back to infinitive return verb }