// morphology-cop.el - Coptic (Sahidic dialect) morphology for the NLG engine. // // Implements Coptic verb conjugation (bipartite and tripartite patterns), noun // phrase assembly with definite and indefinite articles, and noun number marking. // Designed as a companion to morphology.el; called when language code is "cop". // // Language profile: code=cop, name=Coptic, morph_type=agglutinative, // word_order=SVO, question_strategy=particle, script=coptic, family=afro-asiatic-egyptian. // // Script: Coptic uses the Greek alphabet plus seven additional letters borrowed // from Demotic Egyptian. All Coptic-script characters in this file use their // correct Unicode code points (Coptic block U+2C80–U+2CFF; Coptic letters also // appear in the Greek block: ϣ U+03E3, ϥ U+03E5, ϩ U+03E9, ϫ U+03EB, ϭ U+03ED). // // The El runtime stores strings as byte arrays. String literals with Coptic // Unicode characters are encoded as UTF-8 and compared via str_eq byte equality. // The runtime limitation on non-ASCII *output display* does not affect internal // string logic — str_eq and concatenation work correctly. // // Grammatical notes (Sahidic Coptic, ca. 200–1000 CE): // - SVO word order (Greek influence; reversed from classical Egyptian) // - Definite articles prefixed directly to the noun (no space): // p- (masc sg), t- (fem sg), n- (plural) — definite // ou- (sg indefinite), hen- (pl indefinite) // - Grammatical gender: masculine / feminine (still active) // - No case endings — grammatical role expressed by word order + prepositions // - Verb tense/aspect expressed by conjugation base (bipartite pattern): // Present I: pronoun prefix + verb stem ("f-bwk" = he goes) // Perfect: a- + pronoun prefix + verb ("a-f-bwk" = he went) // Future: pronoun prefix + na- + verb ("f-na-bwk" = he will go) // - Pronoun prefixes (Sahidic — used as subject markers in bipartite conjugation): // 1sg: a-/t- (full: ⲁⲛⲟⲕ) 2sg m: k- 2sg f: te- // 3sg m: f- 3sg f: s- // 1pl: n- 2pl: teten- 3pl: se- // - Copula: "pe" (m sg), "te" (f sg), "ne" (pl); zero copula for adj predicates // - "to be/become": ϣωπε (Sahidic; present: fϣoop / sϣoop; past: afϣwpe) // // Verbs covered (Sahidic transliteration / Coptic script): // ϣωπε (shwpe) — to be / become bwk — to go // nau — to see jw — to say / speak // di — to give // // Canonical English → Coptic mapping: // "be" → ϣωπε / zero copula "go" → bwk // "see" → nau "say" → jw // "give" → di // // Persons/numbers covered: // person: "first" | "second" | "third" // gender: "m" | "f" (relevant for 2sg and 3sg pronoun prefix selection) // number: "singular" | "plural" // // Depends on: morphology.el (str_eq, str_len, str_slice, str_ends_with) // ── String helpers ────────────────────────────────────────────────────────────── import "morphology.el" fn cop_str_ends(s: String, suf: String) -> Bool { return str_ends_with(s, suf) } fn cop_str_len(s: String) -> Int { return str_len(s) } fn cop_drop(s: String, n: Int) -> String { let len: Int = str_len(s) if n >= len { return "" } return str_slice(s, 0, len - n) } fn cop_last_char(s: String) -> String { let n: Int = str_len(s) if n == 0 { return "" } return str_slice(s, n - 1, n) } // ── Person/number slot ────────────────────────────────────────────────────────── // // Maps person × number to a 0-based index used in paradigm tables. // Gender is not encoded in the slot index here; it is passed separately to // cop_subject_prefix where it matters (2sg and 3sg distinction). // // Slot layout: // 0 = 1st singular (ⲁⲛⲟⲕ anok) // 1 = 2nd singular (ⲛⲧⲟⲕ/ⲛⲧⲟ ntok/nto) — gender resolved in cop_subject_prefix // 2 = 3rd singular (ⲛⲧⲟϥ/ⲛⲧⲟⲥ ntof/ntos) — gender resolved in cop_subject_prefix // 3 = 1st plural (ⲁⲛⲟⲛ anon) // 4 = 2nd plural (ⲛⲧⲱⲧⲉⲛ ntwten) // 5 = 3rd plural (ⲛⲧⲟⲩ ntou) fn cop_slot(person: String, number: String) -> Int { if str_eq(person, "first") { if str_eq(number, "singular") { return 0 } return 3 } if str_eq(person, "second") { if str_eq(number, "singular") { return 1 } return 4 } // third if str_eq(number, "singular") { return 2 } return 5 } // ── Subject pronoun prefixes ───────────────────────────────────────────────────── // // Coptic bipartite conjugation uses short pronoun prefixes attached directly to // the verb stem (or to the tense base in tripartite). These are the Sahidic // bound subject pronouns. // // Full independent pronouns (for reference): // 1sg: ⲁⲛⲟⲕ (anok) prefix: ⲁ- / ⲧ- (varies by tense base) // 2sg m: ⲛⲧⲟⲕ (ntok) prefix: ⲕ- // 2sg f: ⲛⲧⲟ (nto) prefix: ⲧⲉ- // 3sg m: ⲛⲧⲟϥ (ntof) prefix: ϥ- // 3sg f: ⲛⲧⲟⲥ (ntos) prefix: ⲥ- // 1pl: ⲁⲛⲟⲛ (anon) prefix: ⲛ- // 2pl: ⲛⲧⲱⲧⲉⲛ (ntwten) prefix: ⲧⲉⲧⲉⲛ- // 3pl: ⲛⲧⲟⲩ (ntou) prefix: ⲥⲉ- // // cop_subject_prefix returns the short bound prefix used in bipartite conjugation. // For the perfect (a-prefix tense base), the subject prefix follows "a-" directly. fn cop_subject_prefix(person: String, number: String) -> String { if str_eq(person, "first") { if str_eq(number, "singular") { return "ⲁ" } return "ⲛ" } if str_eq(person, "second") { if str_eq(number, "singular") { return "ⲕ" } return "ⲧⲉⲧⲉⲛ" } // third if str_eq(number, "singular") { return "ϥ" } return "ⲥⲉ" } // cop_subject_prefix_gendered: like cop_subject_prefix but handles the // 2sg feminine (ⲧⲉ-) and 3sg feminine (ⲥ-) distinction. fn cop_subject_prefix_gendered(person: String, gender: String, number: String) -> String { if str_eq(person, "first") { if str_eq(number, "singular") { return "ⲁ" } return "ⲛ" } if str_eq(person, "second") { if str_eq(number, "singular") { if str_eq(gender, "f") { return "ⲧⲉ" } return "ⲕ" } return "ⲧⲉⲧⲉⲛ" } // third person if str_eq(number, "singular") { if str_eq(gender, "f") { return "ⲥ" } return "ϥ" } return "ⲥⲉ" } // ── Copula ────────────────────────────────────────────────────────────────────── // // The Coptic nominal/adjectival copula is a standalone particle that agrees with // the gender and number of the subject: // Masculine sg: ⲡⲉ (pe) // Feminine sg: ⲧⲉ (te) // Plural: ⲛⲉ (ne) // // For adjective predicates in the present tense, the copula is often zero // (following the inherited Egyptian zero-copula rule). This engine returns "" // for the present adjective predicate and the full copula particle otherwise. fn cop_copula_particle(gender: String, number: String) -> String { if str_eq(number, "plural") { return "ⲛⲉ" } if str_eq(gender, "f") { return "ⲧⲉ" } return "ⲡⲉ" } // ── Verb: ϣωπε (to be / become) ──────────────────────────────────────────────── // // ϣωπε is the Sahidic verb meaning "to be" or "to become". It is used as a // substantive/existential copula. For adjective predicate sentences the zero // copula is preferred (inherited from Egyptian). // // Sahidic forms: // Present I (bipartite): prefix + ϣⲟⲟⲡ (e.g. ϥϣⲟⲟⲡ "he is/exists") // Perfect (a- base): ⲁ + prefix + ϣⲱⲡⲉ (e.g. ⲁϥϣⲱⲡⲉ "he became") // Future (na- infix): prefix + ⲛⲁϣⲱⲡⲉ (e.g. ϥⲛⲁϣⲱⲡⲉ "he will become") // // Note: ϣⲟⲟⲡ (shoop) is the present stem; ϣⲱⲡⲉ (shwpe) is the infinitive/perfect stem. fn cop_shwpe_present(prefix: String) -> String { return prefix + "ϣⲟⲟⲡ" } fn cop_shwpe_perfect(prefix: String) -> String { return "ⲁ" + prefix + "ϣⲱⲡⲉ" } fn cop_shwpe_future(prefix: String) -> String { return prefix + "ⲛⲁϣⲱⲡⲉ" } // ── Verb: bwk (to go) — written ⲃⲱⲕ ─────────────────────────────────────────── // // A common strong verb. The standard bipartite/tripartite pattern applies. // Present: prefix + ⲃⲱⲕ (e.g. ϥⲃⲱⲕ "he goes") // Perfect: ⲁ + prefix + ⲃⲱⲕ (e.g. ⲁϥⲃⲱⲕ "he went") // Future: prefix + ⲛⲁⲃⲱⲕ (e.g. ϥⲛⲁⲃⲱⲕ "he will go") fn cop_bwk_present(prefix: String) -> String { return prefix + "ⲃⲱⲕ" } fn cop_bwk_perfect(prefix: String) -> String { return "ⲁ" + prefix + "ⲃⲱⲕ" } fn cop_bwk_future(prefix: String) -> String { return prefix + "ⲛⲁⲃⲱⲕ" } // ── Verb: nau (to see) — written ⲛⲁⲩ ────────────────────────────────────────── // // nau is a biconsonantal verb. Regular bipartite conjugation: // Present: prefix + ⲛⲁⲩ (e.g. ϥⲛⲁⲩ "he sees") // Perfect: ⲁ + prefix + ⲛⲁⲩ (e.g. ⲁϥⲛⲁⲩ "he saw") // Future: prefix + ⲛⲁⲛⲁⲩ (e.g. ϥⲛⲁⲛⲁⲩ "he will see") // // Note: the future prefix "na-" followed by "nau" produces "nanau" — standard. fn cop_nau_present(prefix: String) -> String { return prefix + "ⲛⲁⲩ" } fn cop_nau_perfect(prefix: String) -> String { return "ⲁ" + prefix + "ⲛⲁⲩ" } fn cop_nau_future(prefix: String) -> String { return prefix + "ⲛⲁⲛⲁⲩ" } // ── Verb: jw (to say / speak) — written ϫⲱ ──────────────────────────────────── // // ϫⲱ is the Sahidic verb for "to say". Bipartite pattern: // Present: prefix + ϫⲱ (e.g. ϥϫⲱ "he says") // Perfect: ⲁ + prefix + ϫⲱ (e.g. ⲁϥϫⲱ "he said") // Future: prefix + ⲛⲁϫⲱ (e.g. ϥⲛⲁϫⲱ "he will say") fn cop_jw_present(prefix: String) -> String { return prefix + "ϫⲱ" } fn cop_jw_perfect(prefix: String) -> String { return "ⲁ" + prefix + "ϫⲱ" } fn cop_jw_future(prefix: String) -> String { return prefix + "ⲛⲁϫⲱ" } // ── Verb: di (to give) — written ϯ ───────────────────────────────────────────── // // ϯ (ti/di) is a monosyllabic verb meaning "to give". It is very common in // Coptic texts. Bipartite pattern: // Present: prefix + ϯ (e.g. ϥϯ "he gives") // Perfect: ⲁ + prefix + ϯ (e.g. ⲁϥϯ "he gave") // Future: prefix + ⲛⲁϯ (e.g. ϥⲛⲁϯ "he will give") fn cop_di_present(prefix: String) -> String { return prefix + "ϯ" } fn cop_di_perfect(prefix: String) -> String { return "ⲁ" + prefix + "ϯ" } fn cop_di_future(prefix: String) -> String { return prefix + "ⲛⲁϯ" } // ── Copula detection ───────────────────────────────────────────────────────────── fn cop_is_copula(verb: String) -> Bool { if str_eq(verb, "ϣωπε") { return true } if str_eq(verb, "shwpe") { return true } if str_eq(verb, "be") { return true } return false } // ── Known-verb dispatcher ──────────────────────────────────────────────────────── // // Returns the inflected form for a known verb given the subject prefix string // and tense. Returns "" if the verb is not in the table. fn cop_known_verb_prefixed(verb: String, tense: String, prefix: String) -> String { // ── ϣωπε / shwpe / "be" — to be / become ──────────────────────────────────── if str_eq(verb, "ϣωπε") { if str_eq(tense, "present") { return cop_shwpe_present(prefix) } if str_eq(tense, "past") { return cop_shwpe_perfect(prefix) } if str_eq(tense, "future") { return cop_shwpe_future(prefix) } return cop_shwpe_present(prefix) } if str_eq(verb, "shwpe") { if str_eq(tense, "present") { return cop_shwpe_present(prefix) } if str_eq(tense, "past") { return cop_shwpe_perfect(prefix) } if str_eq(tense, "future") { return cop_shwpe_future(prefix) } return cop_shwpe_present(prefix) } // ── bwk / ⲃⲱⲕ — to go ──────────────────────────────────────────────────────── if str_eq(verb, "bwk") { if str_eq(tense, "present") { return cop_bwk_present(prefix) } if str_eq(tense, "past") { return cop_bwk_perfect(prefix) } if str_eq(tense, "future") { return cop_bwk_future(prefix) } return cop_bwk_present(prefix) } if str_eq(verb, "ⲃⲱⲕ") { if str_eq(tense, "present") { return cop_bwk_present(prefix) } if str_eq(tense, "past") { return cop_bwk_perfect(prefix) } if str_eq(tense, "future") { return cop_bwk_future(prefix) } return cop_bwk_present(prefix) } if str_eq(verb, "go") { if str_eq(tense, "present") { return cop_bwk_present(prefix) } if str_eq(tense, "past") { return cop_bwk_perfect(prefix) } if str_eq(tense, "future") { return cop_bwk_future(prefix) } return cop_bwk_present(prefix) } // ── nau / ⲛⲁⲩ — to see ─────────────────────────────────────────────────────── if str_eq(verb, "nau") { if str_eq(tense, "present") { return cop_nau_present(prefix) } if str_eq(tense, "past") { return cop_nau_perfect(prefix) } if str_eq(tense, "future") { return cop_nau_future(prefix) } return cop_nau_present(prefix) } if str_eq(verb, "ⲛⲁⲩ") { if str_eq(tense, "present") { return cop_nau_present(prefix) } if str_eq(tense, "past") { return cop_nau_perfect(prefix) } if str_eq(tense, "future") { return cop_nau_future(prefix) } return cop_nau_present(prefix) } if str_eq(verb, "see") { if str_eq(tense, "present") { return cop_nau_present(prefix) } if str_eq(tense, "past") { return cop_nau_perfect(prefix) } if str_eq(tense, "future") { return cop_nau_future(prefix) } return cop_nau_present(prefix) } // ── jw / ϫⲱ — to say / speak ───────────────────────────────────────────────── if str_eq(verb, "jw") { if str_eq(tense, "present") { return cop_jw_present(prefix) } if str_eq(tense, "past") { return cop_jw_perfect(prefix) } if str_eq(tense, "future") { return cop_jw_future(prefix) } return cop_jw_present(prefix) } if str_eq(verb, "ϫⲱ") { if str_eq(tense, "present") { return cop_jw_present(prefix) } if str_eq(tense, "past") { return cop_jw_perfect(prefix) } if str_eq(tense, "future") { return cop_jw_future(prefix) } return cop_jw_present(prefix) } if str_eq(verb, "say") { if str_eq(tense, "present") { return cop_jw_present(prefix) } if str_eq(tense, "past") { return cop_jw_perfect(prefix) } if str_eq(tense, "future") { return cop_jw_future(prefix) } return cop_jw_present(prefix) } // ── di / ϯ — to give ────────────────────────────────────────────────────────── if str_eq(verb, "di") { if str_eq(tense, "present") { return cop_di_present(prefix) } if str_eq(tense, "past") { return cop_di_perfect(prefix) } if str_eq(tense, "future") { return cop_di_future(prefix) } return cop_di_present(prefix) } if str_eq(verb, "ϯ") { if str_eq(tense, "present") { return cop_di_present(prefix) } if str_eq(tense, "past") { return cop_di_perfect(prefix) } if str_eq(tense, "future") { return cop_di_future(prefix) } return cop_di_present(prefix) } if str_eq(verb, "give") { if str_eq(tense, "present") { return cop_di_present(prefix) } if str_eq(tense, "past") { return cop_di_perfect(prefix) } if str_eq(tense, "future") { return cop_di_future(prefix) } return cop_di_present(prefix) } // Verb not in table return "" } // ── Regular verb conjugation ───────────────────────────────────────────────────── // // For verbs not in the explicit table, apply the productive bipartite pattern: // Present: prefix + stem // Perfect: ⲁ + prefix + stem // Future: prefix + ⲛⲁ + stem fn cop_regular_present(prefix: String, stem: String) -> String { return prefix + stem } fn cop_regular_perfect(prefix: String, stem: String) -> String { return "ⲁ" + prefix + stem } fn cop_regular_future(prefix: String, stem: String) -> String { return prefix + "ⲛⲁ" + stem } // ── cop_conjugate: main conjugation entry point ────────────────────────────────── // // verb: Coptic verb (Sahidic stem, transliterated, or English canonical label) // tense: "present" | "past" | "future" // person: "first" | "second" | "third" // number: "singular" | "plural" // // Returns the fully conjugated form with subject prefix embedded. // Zero copula ("") is returned for present "be" (adj predicate context). // For unknown verbs the regular bipartite pattern is applied as a productive fallback. fn cop_conjugate(verb: String, tense: String, person: String, number: String) -> String { let prefix: String = cop_subject_prefix(person, number) // Handle "be" canonical → zero copula in present; ϣωπε otherwise if str_eq(verb, "be") { if str_eq(tense, "present") { return "" } if str_eq(tense, "past") { return cop_shwpe_perfect(prefix) } if str_eq(tense, "future") { return cop_shwpe_future(prefix) } return "" } // Try the known-verb table let known: String = cop_known_verb_prefixed(verb, tense, prefix) if !str_eq(known, "") { return known } // Regular productive bipartite conjugation if str_eq(tense, "present") { return cop_regular_present(prefix, verb) } if str_eq(tense, "past") { return cop_regular_perfect(prefix, verb) } if str_eq(tense, "future") { return cop_regular_future(prefix, verb) } // Unknown tense: return verb as safe fallback return verb } // ── Article system ──────────────────────────────────────────────────────────────── // // cop_article: return the Coptic article string for the given gender/number/definiteness. // // Definite articles (prefixed directly to noun, no space): // Masculine singular: ⲡ- (p-) // Feminine singular: ⲧ- (t-) // Plural (both): ⲛ- (n-) // // Indefinite articles: // Singular (both genders): ⲟⲩ- (ou-) // Plural: ϩⲉⲛ- (hen-) // // gender: "m" | "f" // number: "singular" | "plural" // definite: "true" | "false" // // Returns the article prefix string (to be concatenated with the noun). fn cop_article(gender: String, number: String, definite: String) -> String { if str_eq(definite, "true") { if str_eq(number, "plural") { return "ⲛ" } if str_eq(gender, "f") { return "ⲧ" } return "ⲡ" } // Indefinite if str_eq(number, "plural") { return "ϩⲉⲛ" } return "ⲟⲩ" } // ── Noun number ─────────────────────────────────────────────────────────────────── // // cop_decline: return the noun in the appropriate number form. // // Coptic nouns have no case endings. Grammatical role is expressed entirely by // word order and prepositions. The gram_case parameter is accepted for API // symmetry with other morphology modules but has no effect. // // Plural formation: // Coptic plural morphology is highly irregular (inherited from Egyptian and // influenced by Greek loanwords). Common patterns: // - Many nouns show no suffix change — plurality is indicated only by the plural article ⲛ-. // - Some nouns take -ⲟⲟⲩⲉ (-ooue): e.g. ϩⲟ (face) → ϩⲟⲟⲩⲉ // - Greek loanwords often add -ⲟⲥ / -ⲟⲩ in Greek fashion // // This function implements: // - No suffix change (base form) as the productive default — the article carries number. // - Words ending in ⲉ (a common Coptic nominal ending) may take -ⲟⲟⲩⲉ in the plural; // this suffix is applied only when the caller explicitly requests plural and the // noun ends in ⲉ (productive pattern). // Vocabulary-layer irregular plurals should be stored in vocabulary-cop.el and // passed already inflected. fn cop_decline(noun: String, gram_case: String, number: String) -> String { if str_eq(number, "singular") { return noun } // Plural: if noun ends in ⲉ, attempt -ooue suffix (common productive pattern) if cop_str_ends(noun, "ⲉ") { let stem: String = cop_drop(noun, 1) return stem + "ⲟⲟⲩⲉ" } // Default: base form (article carries the plural signal) return noun } // ── Noun phrase assembly ────────────────────────────────────────────────────────── // // cop_noun_phrase: build a complete Coptic noun phrase. // // noun: base noun (Coptic script or transliteration) // gram_case: accepted for API symmetry; has no effect (Coptic is caseless) // number: "singular" | "plural" // definite: "true" | "false" // // The article is prefixed directly to the noun with no intervening space, // following standard Coptic orthographic convention. // Gender defaults to masculine when not determinable from context; the caller // should supply the declined noun already in its correct form if gender-sensitive // plural forms are needed. fn cop_noun_phrase(noun: String, gram_case: String, number: String, definite: String) -> String { let form: String = cop_decline(noun, gram_case, number) // Infer gender from number: if plural, gender is moot for the article (always ⲛ-) // For singular, default to masculine (caller provides gender via article if known) let art: String = cop_article("m", number, definite) if str_eq(definite, "true") { return art + form } if str_eq(definite, "false") { // Indefinite article + noun (no space — Coptic convention for proclitic articles) return art + form } return form } // cop_noun_phrase_gendered: noun phrase with explicit gender for correct article selection. // // gender: "m" | "f" fn cop_noun_phrase_gendered(noun: String, gram_case: String, number: String, definite: String, gender: String) -> String { let form: String = cop_decline(noun, gram_case, number) let art: String = cop_article(gender, number, definite) if str_eq(definite, "true") { return art + form } if str_eq(definite, "false") { return art + form } return form } // ── Canonical verb mapping ──────────────────────────────────────────────────────── // // cop_map_canonical: map cross-lingual English canonical verb labels to their // Sahidic Coptic equivalents before dispatching to cop_conjugate. fn cop_map_canonical(verb: String) -> String { if str_eq(verb, "be") { return "be" } if str_eq(verb, "go") { return "bwk" } if str_eq(verb, "see") { return "nau" } if str_eq(verb, "say") { return "jw" } if str_eq(verb, "speak") { return "jw" } if str_eq(verb, "give") { return "di" } // Unknown: return as-is; cop_conjugate will apply the regular pattern return verb }