Files
el/elp/src/morphology-de.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

815 lines
30 KiB
EmacsLisp

// morphology-de.el - German morphology: articles, adjective endings, noun
// plurals, and verb conjugation.
//
// German is a fusional language with:
// - 4 grammatical cases: nominative, accusative, dative, genitive
// - 3 genders: masculine (m), feminine (f), neuter (n)
// - 2 numbers: singular, plural
// - Strong and weak verb classes
//
// Conventions used throughout:
// gender: "m" | "f" | "n"
// case: "nom" | "acc" | "dat" | "gen"
// number: "sg" | "pl"
// person: "1" | "2" | "3"
// tense: "present" | "past" | "future"
// article_type: "def" | "indef" | "none"
//
// Depends on: language-profile (str_eq, str_len, str_slice, str_drop_last,
// str_ends_with)
// Definite articles (der-words)
//
// Masc Fem Neut Plural
// Nom: der die das die
// Acc: den die das die
// Dat: dem der dem den
// Gen: des der des der
import "morphology.el"
fn de_article_def(gender: String, gram_case: String, number: String) -> String {
if str_eq(number, "pl") {
if str_eq(gram_case, "nom") { return "die" }
if str_eq(gram_case, "acc") { return "die" }
if str_eq(gram_case, "dat") { return "den" }
if str_eq(gram_case, "gen") { return "der" }
return "die"
}
if str_eq(gender, "m") {
if str_eq(gram_case, "nom") { return "der" }
if str_eq(gram_case, "acc") { return "den" }
if str_eq(gram_case, "dat") { return "dem" }
if str_eq(gram_case, "gen") { return "des" }
return "der"
}
if str_eq(gender, "f") {
if str_eq(gram_case, "nom") { return "die" }
if str_eq(gram_case, "acc") { return "die" }
if str_eq(gram_case, "dat") { return "der" }
if str_eq(gram_case, "gen") { return "der" }
return "die"
}
// neuter
if str_eq(gram_case, "nom") { return "das" }
if str_eq(gram_case, "acc") { return "das" }
if str_eq(gram_case, "dat") { return "dem" }
if str_eq(gram_case, "gen") { return "des" }
return "das"
}
// Indefinite articles (ein-words)
//
// Masc Fem Neut Plural
// Nom: ein eine ein
// Acc: einen eine ein
// Dat: einem einer einem
// Gen: eines einer eines
fn de_article_indef(gender: String, gram_case: String, number: String) -> String {
if str_eq(number, "pl") {
// Indefinite article has no plural form
return ""
}
if str_eq(gender, "m") {
if str_eq(gram_case, "nom") { return "ein" }
if str_eq(gram_case, "acc") { return "einen" }
if str_eq(gram_case, "dat") { return "einem" }
if str_eq(gram_case, "gen") { return "eines" }
return "ein"
}
if str_eq(gender, "f") {
if str_eq(gram_case, "nom") { return "eine" }
if str_eq(gram_case, "acc") { return "eine" }
if str_eq(gram_case, "dat") { return "einer" }
if str_eq(gram_case, "gen") { return "einer" }
return "eine"
}
// neuter
if str_eq(gram_case, "nom") { return "ein" }
if str_eq(gram_case, "acc") { return "ein" }
if str_eq(gram_case, "dat") { return "einem" }
if str_eq(gram_case, "gen") { return "eines" }
return "ein"
}
// de_article: unified article dispatch.
// definite: "def" | "indef" | "none"
fn de_article(gender: String, gram_case: String, number: String, definite: String) -> String {
if str_eq(definite, "def") { return de_article_def(gender, gram_case, number) }
if str_eq(definite, "indef") { return de_article_indef(gender, gram_case, number) }
return ""
}
// Adjective endings
//
// Weak endings (after a definite article or der-word):
//
// Masc Fem Neut Plural
// Nom: -e -e -e -en
// Acc: -en -e -e -en
// Dat: -en -en -en -en
// Gen: -en -en -en -en
//
// Mixed endings (after ein-words with no marking, i.e. indef article):
//
// Masc Fem Neut Plural
// Nom: -er -e -es -en
// Acc: -en -e -es -en
// Dat: -en -en -en -en
// Gen: -en -en -en -en
//
// Strong endings (no preceding article):
//
// Masc Fem Neut Plural
// Nom: -er -e -es -e
// Acc: -en -e -es -e
// Dat: -em -er -em -en
// Gen: -en -er -en -er
//
// article_type: "def" | "indef" | "none"
fn de_adj_ending(gender: String, gram_case: String, number: String, article_type: String) -> String {
if str_eq(article_type, "def") {
// Weak declension
if str_eq(number, "pl") {
return "en"
}
if str_eq(gender, "m") {
if str_eq(gram_case, "nom") { return "e" }
return "en"
}
if str_eq(gender, "f") {
if str_eq(gram_case, "nom") { return "e" }
if str_eq(gram_case, "acc") { return "e" }
return "en"
}
// neuter
if str_eq(gram_case, "nom") { return "e" }
if str_eq(gram_case, "acc") { return "e" }
return "en"
}
if str_eq(article_type, "indef") {
// Mixed declension
if str_eq(number, "pl") {
return "en"
}
if str_eq(gender, "m") {
if str_eq(gram_case, "nom") { return "er" }
return "en"
}
if str_eq(gender, "f") {
if str_eq(gram_case, "nom") { return "e" }
if str_eq(gram_case, "acc") { return "e" }
return "en"
}
// neuter
if str_eq(gram_case, "nom") { return "es" }
if str_eq(gram_case, "acc") { return "es" }
return "en"
}
// Strong declension (no article)
if str_eq(number, "pl") {
if str_eq(gram_case, "nom") { return "e" }
if str_eq(gram_case, "acc") { return "e" }
if str_eq(gram_case, "dat") { return "en" }
if str_eq(gram_case, "gen") { return "er" }
return "e"
}
if str_eq(gender, "m") {
if str_eq(gram_case, "nom") { return "er" }
if str_eq(gram_case, "acc") { return "en" }
if str_eq(gram_case, "dat") { return "em" }
if str_eq(gram_case, "gen") { return "en" }
return "er"
}
if str_eq(gender, "f") {
if str_eq(gram_case, "nom") { return "e" }
if str_eq(gram_case, "acc") { return "e" }
if str_eq(gram_case, "dat") { return "er" }
if str_eq(gram_case, "gen") { return "er" }
return "e"
}
// neuter
if str_eq(gram_case, "nom") { return "es" }
if str_eq(gram_case, "acc") { return "es" }
if str_eq(gram_case, "dat") { return "em" }
if str_eq(gram_case, "gen") { return "en" }
return "es"
}
// Noun plural formation
//
// Major patterns, keyed on lemma. Where a noun is known irregular, the full
// plural is returned. Otherwise a productive heuristic by gender and ending
// is applied:
//
// Masculine hard nouns +e (der Tag Tage)
// Feminine nouns in -e +n (die Katze Katzen)
// Feminine nouns +en (die Frau Frauen)
// Neuter nouns in -chen/-lein (das Mädchen Mädchen)
// Neuter nouns in -um -um +en (das Zentrum Zentren)
// Loanwords in -a,-o,-i +s (das Auto Autos)
// Default +e
fn de_noun_plural(noun: String, gender: String) -> String {
// Lexical irregulars
if str_eq(noun, "Mann") { return "Männer" }
if str_eq(noun, "Kind") { return "Kinder" }
if str_eq(noun, "Haus") { return "Häuser" }
if str_eq(noun, "Buch") { return "Bücher" }
if str_eq(noun, "Mutter") { return "Mütter" }
if str_eq(noun, "Vater") { return "Väter" }
if str_eq(noun, "Bruder") { return "Brüder" }
if str_eq(noun, "Tochter") { return "Töchter" }
if str_eq(noun, "Nacht") { return "Nächte" }
if str_eq(noun, "Stadt") { return "Städte" }
if str_eq(noun, "Wort") { return "Wörter" }
if str_eq(noun, "Gott") { return "Götter" }
if str_eq(noun, "Wald") { return "Wälder" }
if str_eq(noun, "Band") { return "Bände" }
if str_eq(noun, "Hund") { return "Hunde" }
if str_eq(noun, "Baum") { return "Bäume" }
if str_eq(noun, "Raum") { return "Räume" }
if str_eq(noun, "Traum") { return "Träume" }
if str_eq(noun, "Zug") { return "Züge" }
if str_eq(noun, "Flug") { return "Flüge" }
if str_eq(noun, "Fuß") { return "Füße" }
if str_eq(noun, "Gruß") { return "Grüße" }
if str_eq(noun, "Geist") { return "Geister" }
if str_eq(noun, "Schwanz") { return "Schwänze" }
if str_eq(noun, "Stuhl") { return "Stühle" }
if str_eq(noun, "Stuhl") { return "Stühle" }
if str_eq(noun, "Sohn") { return "Söhne" }
if str_eq(noun, "Ton") { return "Töne" }
if str_eq(noun, "Fluss") { return "Flüsse" }
if str_eq(noun, "Frau") { return "Frauen" }
if str_eq(noun, "Straße") { return "Straßen" }
if str_eq(noun, "Schule") { return "Schulen" }
if str_eq(noun, "Blume") { return "Blumen" }
if str_eq(noun, "Katze") { return "Katzen" }
if str_eq(noun, "Sprache") { return "Sprachen" }
if str_eq(noun, "Kirche") { return "Kirchen" }
if str_eq(noun, "Tür") { return "Türen" }
if str_eq(noun, "Uhr") { return "Uhren" }
if str_eq(noun, "Zahl") { return "Zahlen" }
if str_eq(noun, "Wahl") { return "Wahlen" }
if str_eq(noun, "Bahn") { return "Bahnen" }
if str_eq(noun, "Zahn") { return "Zähne" }
if str_eq(noun, "Nase") { return "Nasen" }
if str_eq(noun, "Maus") { return "Mäuse" }
if str_eq(noun, "Mädchen") { return "Mädchen" }
if str_eq(noun, "Messer") { return "Messer" }
if str_eq(noun, "Fenster") { return "Fenster" }
if str_eq(noun, "Zimmer") { return "Zimmer" }
if str_eq(noun, "Wasser") { return "Wasser" }
if str_eq(noun, "Bett") { return "Betten" }
if str_eq(noun, "Auto") { return "Autos" }
if str_eq(noun, "Kino") { return "Kinos" }
if str_eq(noun, "Radio") { return "Radios" }
if str_eq(noun, "Foto") { return "Fotos" }
if str_eq(noun, "Cafe") { return "Cafes" }
if str_eq(noun, "Zentrum") { return "Zentren" }
if str_eq(noun, "Museum") { return "Museen" }
if str_eq(noun, "Gymnasium") { return "Gymnasien" }
if str_eq(noun, "Studium") { return "Studien" }
if str_eq(noun, "Datum") { return "Daten" }
// Productive heuristics
// Nouns ending in -chen or -lein: no change (diminutives)
if str_ends_with(noun, "chen") { return noun }
if str_ends_with(noun, "lein") { return noun }
// Nouns ending in -um: replace with -en
if str_ends_with(noun, "um") {
return str_drop_last(noun, 2) + "en"
}
// Loanwords ending in vowel or -s: add -s
if str_ends_with(noun, "a") { return noun + "s" }
if str_ends_with(noun, "o") { return noun + "s" }
if str_ends_with(noun, "i") { return noun + "s" }
if str_ends_with(noun, "u") { return noun + "s" }
if str_ends_with(noun, "y") { return noun + "s" }
// Feminine nouns ending in -e: add -n
if str_eq(gender, "f") {
if str_ends_with(noun, "e") {
return noun + "n"
}
// Feminine nouns ending in -in: add -nen
if str_ends_with(noun, "in") {
return noun + "nen"
}
// Most other feminines: add -en
return noun + "en"
}
// Neuter and masculine: default to +e
return noun + "e"
}
// Noun case endings
//
// In German, noun case inflection is mostly carried by the article and
// adjective. The noun itself only changes in two regular situations:
// - Genitive singular masculine/neuter: -(e)s
// - Dative plural: -n (if not already ending in -n or -s)
//
// Irregular genitive forms (e.g. N-declension: Herr Herrn) are
// handled per-lemma in de_case_ending.
fn de_case_ending(noun: String, gender: String, gram_case: String, number: String) -> String {
// N-declension masculines (weak nouns): all non-nominative singular forms + all plural add -(e)n
if str_eq(noun, "Herr") {
if str_eq(number, "sg") {
if str_eq(gram_case, "nom") { return "Herr" }
return "Herrn"
}
return "Herren"
}
if str_eq(noun, "Mensch") {
if str_eq(number, "sg") {
if str_eq(gram_case, "nom") { return "Mensch" }
return "Menschen"
}
return "Menschen"
}
if str_eq(noun, "Student") {
if str_eq(number, "sg") {
if str_eq(gram_case, "nom") { return "Student" }
return "Studenten"
}
return "Studenten"
}
if str_eq(noun, "Kollege") {
if str_eq(number, "sg") {
if str_eq(gram_case, "nom") { return "Kollege" }
return "Kollegen"
}
return "Kollegen"
}
if str_eq(noun, "Name") {
if str_eq(number, "sg") {
if str_eq(gram_case, "nom") { return "Name" }
if str_eq(gram_case, "gen") { return "Namens" }
return "Namen"
}
return "Namen"
}
// Regular masculine/neuter: genitive singular gets -(e)s
if str_eq(number, "sg") {
if str_eq(gram_case, "gen") {
if str_eq(gender, "m") {
if str_ends_with(noun, "s") { return noun + "es" }
if str_ends_with(noun, "x") { return noun + "es" }
if str_ends_with(noun, "z") { return noun + "es" }
if str_ends_with(noun, "sch") { return noun + "es" }
return noun + "s"
}
if str_eq(gender, "n") {
if str_ends_with(noun, "s") { return noun + "es" }
if str_ends_with(noun, "x") { return noun + "es" }
if str_ends_with(noun, "z") { return noun + "es" }
return noun + "s"
}
}
// All other singular cases: noun unchanged
return noun
}
// Plural dative: add -n unless already ending in -n or -s
if str_eq(gram_case, "dat") {
let pl: String = de_noun_plural(noun, gender)
if str_ends_with(pl, "n") { return pl }
if str_ends_with(pl, "s") { return pl }
return pl + "n"
}
// All other plural cases: return the standard plural form
return de_noun_plural(noun, gender)
}
// Weak verb conjugation
//
// Model: machen (mach-)
//
// Present:
// 1sg ich mache 2sg du machst 3sg er/sie/es macht
// 1pl wir machen 2pl ihr macht 3pl sie machen
//
// Past (Präteritum):
// 1sg ich machte 2sg du machtest 3sg er/sie/es machte
// 1pl wir machten 2pl ihr machtet 3pl sie machten
fn de_conjugate_weak(stem: String, tense: String, person: String, number: String) -> String {
if str_eq(tense, "present") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return stem + "e" }
if str_eq(person, "2") {
// Stems ending in -t or -d insert -e- before -st
if str_ends_with(stem, "t") { return stem + "est" }
if str_ends_with(stem, "d") { return stem + "est" }
return stem + "st"
}
// 3sg
if str_ends_with(stem, "t") { return stem + "et" }
if str_ends_with(stem, "d") { return stem + "et" }
return stem + "t"
}
// plural
if str_eq(person, "1") { return stem + "en" }
if str_eq(person, "2") {
if str_ends_with(stem, "t") { return stem + "et" }
if str_ends_with(stem, "d") { return stem + "et" }
return stem + "t"
}
// 3pl
return stem + "en"
}
if str_eq(tense, "past") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return stem + "te" }
if str_eq(person, "2") { return stem + "test" }
return stem + "te"
}
if str_eq(person, "1") { return stem + "ten" }
if str_eq(person, "2") { return stem + "tet" }
return stem + "ten"
}
// Future: werden + infinitive caller must prepend the auxiliary
return stem + "en"
}
// Strong / irregular verb present-tense forms
//
// Returns the correct surface form if the verb is irregular, or "" if unknown.
// Only present-tense irregulars are encoded here because past tense for strong
// verbs is stored as a separate stem (Ablaut) see de_conjugate.
fn de_irregular_present(verb: String, person: String, number: String) -> String {
// sein fully irregular
if str_eq(verb, "sein") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "bin" }
if str_eq(person, "2") { return "bist" }
return "ist"
}
if str_eq(person, "1") { return "sind" }
if str_eq(person, "2") { return "seid" }
return "sind"
}
// haben
if str_eq(verb, "haben") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "habe" }
if str_eq(person, "2") { return "hast" }
return "hat"
}
if str_eq(person, "1") { return "haben" }
if str_eq(person, "2") { return "habt" }
return "haben"
}
// werden
if str_eq(verb, "werden") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "werde" }
if str_eq(person, "2") { return "wirst" }
return "wird"
}
if str_eq(person, "1") { return "werden" }
if str_eq(person, "2") { return "werdet" }
return "werden"
}
// gehen
if str_eq(verb, "gehen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "gehe" }
if str_eq(person, "2") { return "gehst" }
return "geht"
}
if str_eq(person, "1") { return "gehen" }
if str_eq(person, "2") { return "geht" }
return "gehen"
}
// kommen
if str_eq(verb, "kommen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "komme" }
if str_eq(person, "2") { return "kommst" }
return "kommt"
}
if str_eq(person, "1") { return "kommen" }
if str_eq(person, "2") { return "kommt" }
return "kommen"
}
// sehen vowel change eie in 2sg/3sg
if str_eq(verb, "sehen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "sehe" }
if str_eq(person, "2") { return "siehst" }
return "sieht"
}
if str_eq(person, "1") { return "sehen" }
if str_eq(person, "2") { return "seht" }
return "sehen"
}
// essen vowel change ei in 2sg/3sg
if str_eq(verb, "essen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "esse" }
if str_eq(person, "2") { return "isst" }
return "isst"
}
if str_eq(person, "1") { return "essen" }
if str_eq(person, "2") { return "esst" }
return "essen"
}
// geben vowel change ei in 2sg/3sg
if str_eq(verb, "geben") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "gebe" }
if str_eq(person, "2") { return "gibst" }
return "gibt"
}
if str_eq(person, "1") { return "geben" }
if str_eq(person, "2") { return "gebt" }
return "geben"
}
// nehmen vowel change ei + consonant change in 2sg/3sg
if str_eq(verb, "nehmen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "nehme" }
if str_eq(person, "2") { return "nimmst" }
return "nimmt"
}
if str_eq(person, "1") { return "nehmen" }
if str_eq(person, "2") { return "nehmt" }
return "nehmen"
}
// fahren vowel change aä in 2sg/3sg
if str_eq(verb, "fahren") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "fahre" }
if str_eq(person, "2") { return "fährst" }
return "fährt"
}
if str_eq(person, "1") { return "fahren" }
if str_eq(person, "2") { return "fahrt" }
return "fahren"
}
// laufen vowel change auäu in 2sg/3sg
if str_eq(verb, "laufen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "laufe" }
if str_eq(person, "2") { return "läufst" }
return "läuft"
}
if str_eq(person, "1") { return "laufen" }
if str_eq(person, "2") { return "lauft" }
return "laufen"
}
// wissen irregular throughout
if str_eq(verb, "wissen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "weiß" }
if str_eq(person, "2") { return "weißt" }
return "weiß"
}
if str_eq(person, "1") { return "wissen" }
if str_eq(person, "2") { return "wisst" }
return "wissen"
}
// können modal
if str_eq(verb, "können") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "kann" }
if str_eq(person, "2") { return "kannst" }
return "kann"
}
if str_eq(person, "1") { return "können" }
if str_eq(person, "2") { return "könnt" }
return "können"
}
// müssen modal
if str_eq(verb, "müssen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "muss" }
if str_eq(person, "2") { return "musst" }
return "muss"
}
if str_eq(person, "1") { return "müssen" }
if str_eq(person, "2") { return "müsst" }
return "müssen"
}
// wollen modal
if str_eq(verb, "wollen") {
if str_eq(number, "sg") {
if str_eq(person, "1") { return "will" }
if str_eq(person, "2") { return "willst" }
return "will"
}
if str_eq(person, "1") { return "wollen" }
if str_eq(person, "2") { return "wollt" }
return "wollen"
}
// Unknown: signal caller to fall through to weak conjugation
return ""
}
// Strong verb past-tense (Präteritum) Ablaut stems
//
// Returns the past stem for strong verbs (Ablaut form), or "" if unknown/weak.
// The past-tense endings for strong verbs differ from weak:
// 1sg/3sg: bare stem (no ending)
// 2sg: stem + -st
// 1pl/3pl: stem + -en
// 2pl: stem + -t
fn de_strong_past_stem(verb: String) -> String {
if str_eq(verb, "gehen") { return "ging" }
if str_eq(verb, "kommen") { return "kam" }
if str_eq(verb, "sehen") { return "sah" }
if str_eq(verb, "geben") { return "gab" }
if str_eq(verb, "nehmen") { return "nahm" }
if str_eq(verb, "fahren") { return "fuhr" }
if str_eq(verb, "laufen") { return "lief" }
if str_eq(verb, "schreiben") { return "schrieb" }
if str_eq(verb, "bleiben") { return "blieb" }
if str_eq(verb, "steigen") { return "stieg" }
if str_eq(verb, "lesen") { return "las" }
if str_eq(verb, "sprechen") { return "sprach" }
if str_eq(verb, "treffen") { return "traf" }
if str_eq(verb, "essen") { return "" }
if str_eq(verb, "trinken") { return "trank" }
if str_eq(verb, "finden") { return "fand" }
if str_eq(verb, "denken") { return "dachte" }
if str_eq(verb, "bringen") { return "brachte" }
if str_eq(verb, "stehen") { return "stand" }
if str_eq(verb, "liegen") { return "lag" }
if str_eq(verb, "sitzen") { return "saß" }
if str_eq(verb, "fallen") { return "fiel" }
if str_eq(verb, "halten") { return "hielt" }
if str_eq(verb, "rufen") { return "rief" }
if str_eq(verb, "tragen") { return "trug" }
if str_eq(verb, "schlagen") { return "schlug" }
if str_eq(verb, "ziehen") { return "zog" }
if str_eq(verb, "wachsen") { return "wuchs" }
if str_eq(verb, "helfen") { return "half" }
if str_eq(verb, "werfen") { return "warf" }
return ""
}
// Normalization helpers
//
// The realizer sends long-form labels ("singular", "first").
// German morphology uses short forms ("sg", "1"). Normalize on entry.
fn de_norm_number(number: String) -> String {
if str_eq(number, "singular") { return "sg" }
if str_eq(number, "plural") { return "pl" }
return number
}
fn de_norm_person(person: String) -> String {
if str_eq(person, "first") { return "1" }
if str_eq(person, "second") { return "2" }
if str_eq(person, "third") { return "3" }
return person
}
// Unified German verb conjugation
//
// tense: "present" | "past" | "future"
// person: "1" | "2" | "3" (also accepts "first" | "second" | "third")
// number: "sg" | "pl" (also accepts "singular" | "plural")
fn de_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let number = de_norm_number(number)
let person = de_norm_person(person)
// Future tense: werden (conjugated) + infinitive
if str_eq(tense, "future") {
let aux: String = de_irregular_present("werden", person, number)
return aux + " " + verb
}
// sein past is also fully irregular
if str_eq(verb, "sein") {
if str_eq(tense, "present") {
return de_irregular_present("sein", person, number)
}
// Past (war)
if str_eq(number, "sg") {
if str_eq(person, "1") { return "war" }
if str_eq(person, "2") { return "warst" }
return "war"
}
if str_eq(person, "1") { return "waren" }
if str_eq(person, "2") { return "wart" }
return "waren"
}
// haben past: hatte
if str_eq(verb, "haben") {
if str_eq(tense, "present") {
return de_irregular_present("haben", person, number)
}
if str_eq(number, "sg") {
if str_eq(person, "1") { return "hatte" }
if str_eq(person, "2") { return "hattest" }
return "hatte"
}
if str_eq(person, "1") { return "hatten" }
if str_eq(person, "2") { return "hattet" }
return "hatten"
}
// wissen past: wusste (mixed/irregular)
if str_eq(verb, "wissen") {
if str_eq(tense, "present") {
return de_irregular_present("wissen", person, number)
}
if str_eq(number, "sg") {
if str_eq(person, "1") { return "wusste" }
if str_eq(person, "2") { return "wusstest" }
return "wusste"
}
if str_eq(person, "1") { return "wussten" }
if str_eq(person, "2") { return "wusstet" }
return "wussten"
}
// Modals: können, müssen, wollen past uses weak -te suffix on preterite stem
if str_eq(verb, "können") {
if str_eq(tense, "present") {
return de_irregular_present("können", person, number)
}
return de_conjugate_weak("konnt", "past", person, number)
}
if str_eq(verb, "müssen") {
if str_eq(tense, "present") {
return de_irregular_present("müssen", person, number)
}
return de_conjugate_weak("musst", "past", person, number)
}
if str_eq(verb, "wollen") {
if str_eq(tense, "present") {
return de_irregular_present("wollen", person, number)
}
return de_conjugate_weak("wollt", "past", person, number)
}
// Present: try irregular table first
if str_eq(tense, "present") {
let irr: String = de_irregular_present(verb, person, number)
if !str_eq(irr, "") {
return irr
}
// Fall through to weak conjugation using infinitive stem (drop -en)
let stem: String = str_drop_last(verb, 2)
return de_conjugate_weak(stem, "present", person, number)
}
// Past: try strong Ablaut first
if str_eq(tense, "past") {
let ps: String = de_strong_past_stem(verb)
if !str_eq(ps, "") {
// Strong past endings: 1sg/3sg bare, 2sg+st, 1pl/3pl+en, 2pl+t
if str_eq(number, "sg") {
if str_eq(person, "1") { return ps }
if str_eq(person, "2") { return ps + "st" }
return ps
}
if str_eq(person, "1") { return ps + "en" }
if str_eq(person, "2") { return ps + "t" }
return ps + "en"
}
// Weak past
let stem: String = str_drop_last(verb, 2)
return de_conjugate_weak(stem, "past", person, number)
}
// Fallback: return infinitive
return verb
}