c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
elb.el: - Auto-detect Homebrew OpenSSL (-L$(brew --prefix openssl)/lib) so -lssl resolves on macOS without manual flags; no-op on Linux - Add -include elp-c-decls.h when present in out_dir: resolves undeclared cross-module calls in packages like ELP that lack explicit imports ELP source: - Add import "morphology.el" to all 29 language morphology modules - Add language module imports to morphology.el (all langs it dispatches to) These were missing since ELP was originally built as a monolithic unit
634 lines
29 KiB
EmacsLisp
634 lines
29 KiB
EmacsLisp
// 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
|
||
}
|