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
815 lines
30 KiB
EmacsLisp
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 e→ie 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 e→i 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 e→i 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 e→i + 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 "aß" }
|
|
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
|
|
}
|