Files
el/elp/src/morphology-he.el
T
will.anderson c2cd5e01e1
El SDK CI - dev / build-and-test (pull_request) Successful in 3m34s
fix: elb macOS OpenSSL + C master declarations header; add ELP missing imports
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
2026-05-08 19:44:31 -05:00

634 lines
29 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// morphology-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
}