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
624 lines
26 KiB
EmacsLisp
624 lines
26 KiB
EmacsLisp
// morphology-fi.el - Finnish morphology: noun case inflection and verb conjugation.
|
|
//
|
|
// Finnish is SOV, agglutinative, 15 grammatical cases, no grammatical gender,
|
|
// no articles. Morphology is suffix-chain based.
|
|
//
|
|
// Key facts:
|
|
// - Vowel harmony: suffixes harmonize with the stem's vowel class.
|
|
// Back vowels (a, o, u) → back-harmony suffixes (-ssa, -sta, -an, etc.)
|
|
// Front vowels (ä, ö, y) or neutral-only stems → front-harmony (-ssä, -stä, -än, etc.)
|
|
// - Consonant gradation: alternation between strong and weak consonant grades.
|
|
// Full gradation tables are large; this module implements the most common
|
|
// patterns. Irregular/exceptional stems must be supplied in inflected form.
|
|
// - Verbs conjugate for person (1/2/3) and number (singular/plural), plus tense
|
|
// (present/past) and polarity (affirmative/negative).
|
|
// - Question suffix: -ko (back harmony) / -kö (front harmony), appended to verb.
|
|
//
|
|
// Depends on: (no dependencies - standalone morphology module)
|
|
|
|
// ── Vowel harmony ─────────────────────────────────────────────────────────────
|
|
//
|
|
// fi_harmony(word) -> "back" | "front"
|
|
//
|
|
// Scan the word right-to-left for the last unambiguously back (a, o, u) or
|
|
// front (ä, ö, y) vowel. Neutral vowels (e, i) do not determine the class.
|
|
// If only neutral vowels are found, default to "front" (the conservative choice
|
|
// for borrowed words and those without clear back vowels).
|
|
|
|
import "morphology.el"
|
|
fn fi_harmony(word: String) -> String {
|
|
let n: Int = str_len(word)
|
|
let i: Int = n - 1
|
|
while i >= 0 {
|
|
let c: String = str_slice(word, i, i + 1)
|
|
// Back vowels
|
|
if str_eq(c, "a") { return "back" }
|
|
if str_eq(c, "o") { return "back" }
|
|
if str_eq(c, "u") { return "back" }
|
|
// Front vowels (UTF-8; Finnish ä is U+00E4, ö is U+00F6)
|
|
// In a byte scan we may land mid-codepoint; we do a substring comparison
|
|
// by checking two-byte sequences at position i-1 when c is a lead byte.
|
|
if str_eq(c, "ä") { return "front" }
|
|
if str_eq(c, "ö") { return "front" }
|
|
if str_eq(c, "y") { return "front" }
|
|
let i = i - 1
|
|
}
|
|
// Default: front (covers neutral-only and unknown stems)
|
|
return "front"
|
|
}
|
|
|
|
// ── Suffix harmonization ──────────────────────────────────────────────────────
|
|
//
|
|
// fi_suffix(base, harmony) -> String
|
|
//
|
|
// Given a back-harmony base suffix, return the harmonized form.
|
|
// Only the a/ä alternation is handled here; all other vowels stay the same.
|
|
// E.g. fi_suffix("ssa", "front") -> "ssä"
|
|
// fi_suffix("ssa", "back") -> "ssa"
|
|
|
|
fn fi_suffix(base: String, harmony: String) -> String {
|
|
if str_eq(harmony, "front") {
|
|
// Replace every 'a' with 'ä' and every 'o' in suffix with 'ö'.
|
|
// We handle only the common patterns used in Finnish case suffixes.
|
|
if str_eq(base, "a") { return "ä" }
|
|
if str_eq(base, "ssa") { return "ssä" }
|
|
if str_eq(base, "sta") { return "stä" }
|
|
if str_eq(base, "an") { return "än" }
|
|
if str_eq(base, "aan") { return "ään" }
|
|
if str_eq(base, "lla") { return "llä" }
|
|
if str_eq(base, "lta") { return "ltä" }
|
|
if str_eq(base, "lle") { return "lle" }
|
|
if str_eq(base, "na") { return "nä" }
|
|
if str_eq(base, "ksi") { return "ksi" }
|
|
if str_eq(base, "tta") { return "ttä" }
|
|
if str_eq(base, "ta") { return "tä" }
|
|
if str_eq(base, "ja") { return "jä" }
|
|
if str_eq(base, "oja") { return "öjä" }
|
|
if str_eq(base, "issa") { return "issä" }
|
|
if str_eq(base, "ista") { return "istä" }
|
|
if str_eq(base, "ihin") { return "ihin" }
|
|
if str_eq(base, "illa") { return "illä" }
|
|
if str_eq(base, "ilta") { return "iltä" }
|
|
if str_eq(base, "ille") { return "ille" }
|
|
if str_eq(base, "ina") { return "inä" }
|
|
if str_eq(base, "itta") { return "ittä" }
|
|
if str_eq(base, "ko") { return "kö" }
|
|
if str_eq(base, "pa") { return "pä" }
|
|
if str_eq(base, "va") { return "vä" }
|
|
if str_eq(base, "ma") { return "mä" }
|
|
if str_eq(base, "han") { return "hän" }
|
|
if str_eq(base, "lla") { return "llä" }
|
|
// Fall back: return the back-harmony form unchanged
|
|
return base
|
|
}
|
|
// Back harmony: return as-is
|
|
return base
|
|
}
|
|
|
|
// ── Noun case inflection ──────────────────────────────────────────────────────
|
|
//
|
|
// fi_noun_case(stem, gram_case, number, harmony) -> String
|
|
//
|
|
// Computes the inflected noun form from the oblique stem, case name, number
|
|
// ("singular" | "plural"), and vowel harmony class.
|
|
//
|
|
// The "stem" is the oblique/genitive stem (e.g. talo for talo, puhu for puhua).
|
|
// For most Type-1 nouns the oblique stem = dictionary form minus final vowel.
|
|
// For the singular nominative the dictionary form itself is used (passed directly).
|
|
//
|
|
// Cases and their suffixes (talo → back, stem "talo"):
|
|
// nominative sg : stem (no suffix) → talo
|
|
// nominative pl : stem + t → talot
|
|
// genitive sg : stem + n → talon
|
|
// genitive pl : stem + jen / jen → talojen
|
|
// accusative sg : stem + n (= gen sg) → talon
|
|
// accusative pl : stem + t (= nom pl) → talot
|
|
// partitive sg : stem + a/ä → taloa
|
|
// partitive pl : stem + ja/jä → taloja
|
|
// inessive sg : stem + ssa/ssä → talossa
|
|
// inessive pl : stem + issa/issä → taloissa
|
|
// elative sg : stem + sta/stä → talosta
|
|
// elative pl : stem + ista/istä → taloista
|
|
// illative sg : stem + vowel + n → taloon (long vowel + n)
|
|
// illative pl : stem + ihin → taloihin
|
|
// adessive sg : stem + lla/llä → talolla
|
|
// adessive pl : stem + illa/illä → taloilla
|
|
// ablative sg : stem + lta/ltä → talolta
|
|
// ablative pl : stem + ilta/iltä → taloilta
|
|
// allative sg : stem + lle → talolle
|
|
// allative pl : stem + ille → taloille
|
|
// essive sg : stem + na/nä → talona
|
|
// essive pl : stem + ina/inä → taloina
|
|
// translative sg : stem + ksi → taloksi
|
|
// translative pl : stem + iksi → taloiksi
|
|
// instructive pl : stem + in → taloin (plural only)
|
|
// abessive sg : stem + tta/ttä → talotta
|
|
// abessive pl : stem + itta/ittä → taloitta
|
|
// comitative pl : stem + ineen → taloineen (plural only)
|
|
|
|
fn fi_noun_case(stem: String, gram_case: String, number: String, harmony: String) -> String {
|
|
let sg: Bool = str_eq(number, "singular")
|
|
|
|
if str_eq(gram_case, "nominative") {
|
|
if sg { return stem }
|
|
return stem + "t"
|
|
}
|
|
|
|
if str_eq(gram_case, "genitive") {
|
|
if sg { return stem + "n" }
|
|
return stem + "jen"
|
|
}
|
|
|
|
if str_eq(gram_case, "accusative") {
|
|
if sg { return stem + "n" }
|
|
return stem + "t"
|
|
}
|
|
|
|
if str_eq(gram_case, "partitive") {
|
|
if sg { return stem + fi_suffix("a", harmony) }
|
|
return stem + fi_suffix("ja", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "inessive") {
|
|
if sg { return stem + fi_suffix("ssa", harmony) }
|
|
return stem + fi_suffix("issa", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "elative") {
|
|
if sg { return stem + fi_suffix("sta", harmony) }
|
|
return stem + fi_suffix("ista", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "illative") {
|
|
if sg {
|
|
// Singular illative: final vowel of stem is lengthened + n
|
|
// e.g. talo → taloon, puu → puuhun, käsi → käteen
|
|
// We take the last character of the stem and double it, then add n.
|
|
let last: String = fi_str_last_char(stem)
|
|
return stem + last + "n"
|
|
}
|
|
return stem + fi_suffix("ihin", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "adessive") {
|
|
if sg { return stem + fi_suffix("lla", harmony) }
|
|
return stem + fi_suffix("illa", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "ablative") {
|
|
if sg { return stem + fi_suffix("lta", harmony) }
|
|
return stem + fi_suffix("ilta", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "allative") {
|
|
// Allative is -lle for both numbers (only the stem differs)
|
|
if sg { return stem + "lle" }
|
|
return stem + "ille"
|
|
}
|
|
|
|
if str_eq(gram_case, "essive") {
|
|
if sg { return stem + fi_suffix("na", harmony) }
|
|
return stem + fi_suffix("ina", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "translative") {
|
|
if sg { return stem + "ksi" }
|
|
return stem + "iksi"
|
|
}
|
|
|
|
if str_eq(gram_case, "instructive") {
|
|
// Instructive is plural only in modern Finnish
|
|
return stem + "in"
|
|
}
|
|
|
|
if str_eq(gram_case, "abessive") {
|
|
if sg { return stem + fi_suffix("tta", harmony) }
|
|
return stem + fi_suffix("itta", harmony)
|
|
}
|
|
|
|
if str_eq(gram_case, "comitative") {
|
|
// Comitative is plural only
|
|
return stem + "ineen"
|
|
}
|
|
|
|
// Unknown case: return stem unchanged
|
|
return stem
|
|
}
|
|
|
|
// ── Noun helper: str_last_char ─────────────────────────────────────────────────
|
|
//
|
|
// Return the last Unicode character of a string.
|
|
// Mirrors the helper in morphology.el; redefined here for standalone use.
|
|
|
|
fn fi_str_last_char(s: String) -> String {
|
|
let n: Int = str_len(s)
|
|
if n == 0 { return "" }
|
|
return str_slice(s, n - 1, n)
|
|
}
|
|
|
|
// ── Full noun inflection (convenience wrapper) ────────────────────────────────
|
|
//
|
|
// fi_apply_case(noun, gram_case, number) -> String
|
|
//
|
|
// Accepts the nominative singular form (dictionary form), derives the harmony
|
|
// class, and produces the requested case form.
|
|
//
|
|
// For most regular nouns the oblique stem equals the dictionary form. The
|
|
// illative singular is handled by appending the last vowel + n.
|
|
|
|
fn fi_apply_case(noun: String, gram_case: String, number: String) -> String {
|
|
let harmony: String = fi_harmony(noun)
|
|
// For nominative singular, return the noun as-is.
|
|
if str_eq(gram_case, "nominative") {
|
|
if str_eq(number, "singular") { return noun }
|
|
return noun + "t"
|
|
}
|
|
// For all other cases, use the noun as the oblique stem.
|
|
// (Callers that need consonant-gradated stems must pass the graded stem
|
|
// directly via fi_noun_case.)
|
|
return fi_noun_case(noun, gram_case, number, harmony)
|
|
}
|
|
|
|
// ── Verb stem extraction ──────────────────────────────────────────────────────
|
|
//
|
|
// fi_verb_stem(dict_form) -> String
|
|
//
|
|
// Strip the infinitive ending to get the present-tense stem.
|
|
//
|
|
// Type 1 verbs (most common): infinitive ends in -a/-ä, stem = infinitive - a/ä
|
|
// puhua → puhu, juosta → juos (irregular, handled separately)
|
|
// Type 2 verbs: end in -da/-dä, stem = infinitive - da/dä
|
|
// syödä → syö, juoda → juo
|
|
// Type 3 verbs: end in -la/-lä, -ra/-rä, -na/-nä, -sta/-stä
|
|
// tulla → tul + l → stem "tull" (double consonant)
|
|
// Type 4 verbs: end in -ata/-ätä
|
|
// tavata → tapaa (irregular lengthening, handled as irregular)
|
|
//
|
|
// For NLG purposes we handle Type 1 and Type 2 as the most frequent.
|
|
|
|
fn fi_verb_stem(dict_form: String) -> String {
|
|
// Type 2: -da/-dä → drop 2 characters
|
|
if str_ends_with(dict_form, "da") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
if str_ends_with(dict_form, "dä") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
// Type 3: -lla/-llä, -rra, -nna → drop last 2 chars (keeps double consonant)
|
|
if str_ends_with(dict_form, "lla") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
if str_ends_with(dict_form, "llä") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
if str_ends_with(dict_form, "rra") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
if str_ends_with(dict_form, "nna") {
|
|
return str_drop_last(dict_form, 2)
|
|
}
|
|
// Type 1 (and default): -a/-ä → drop 1 character
|
|
if str_ends_with(dict_form, "a") {
|
|
return str_drop_last(dict_form, 1)
|
|
}
|
|
if str_ends_with(dict_form, "ä") {
|
|
return str_drop_last(dict_form, 1)
|
|
}
|
|
return dict_form
|
|
}
|
|
|
|
// ── Irregular verb table ──────────────────────────────────────────────────────
|
|
//
|
|
// Returns an 18-element list encoding essential irregular paradigm forms, or an
|
|
// empty list if the verb is regular.
|
|
//
|
|
// Slot layout (0-indexed):
|
|
// 0 inf infinitive (dictionary form)
|
|
// 1 pres_1sg present 1sg
|
|
// 2 pres_2sg present 2sg
|
|
// 3 pres_3sg present 3sg
|
|
// 4 pres_1pl present 1pl
|
|
// 5 pres_2pl present 2pl
|
|
// 6 pres_3pl present 3pl
|
|
// 7 past_1sg past 1sg
|
|
// 8 past_2sg past 2sg
|
|
// 9 past_3sg past 3sg
|
|
// 10 past_1pl past 1pl
|
|
// 11 past_2pl past 2pl
|
|
// 12 past_3pl past 3pl
|
|
// 13 neg_stem negative stem (used with en/et/ei/emme/ette/eivät)
|
|
// 14 cond_stem conditional stem (for future use)
|
|
// 15 imp_2sg imperative 2sg
|
|
// 16 part_pres present participle stem
|
|
// 17 part_past past participle
|
|
|
|
fn fi_irregular_verb(dict_form: String) -> [String] {
|
|
let empty: [String] = []
|
|
|
|
// olla — to be (the most irregular Finnish verb)
|
|
if str_eq(dict_form, "olla") {
|
|
let r: [String] = ["olla", "olen", "olet", "on", "olemme", "olette", "ovat",
|
|
"olin", "olit", "oli", "olimme", "olitte", "olivat",
|
|
"ole", "olis", "ole", "oleva", "ollut"]
|
|
return r
|
|
}
|
|
|
|
// voida — can / to be able to
|
|
if str_eq(dict_form, "voida") {
|
|
let r: [String] = ["voida", "voin", "voit", "voi", "voimme", "voitte", "voivat",
|
|
"voin", "voit", "voi", "voimme", "voitte", "voivat",
|
|
"voi", "vois", "voi", "voiva", "voinut"]
|
|
return r
|
|
}
|
|
|
|
// mennä — to go (Type 3 with irregularities)
|
|
if str_eq(dict_form, "mennä") {
|
|
let r: [String] = ["mennä", "menen", "menet", "menee", "menemme", "menette", "menevät",
|
|
"menin", "menit", "meni", "menimme", "menitte", "menivät",
|
|
"mene", "menis", "mene", "menevä", "mennyt"]
|
|
return r
|
|
}
|
|
|
|
// tulla — to come (Type 3)
|
|
if str_eq(dict_form, "tulla") {
|
|
let r: [String] = ["tulla", "tulen", "tulet", "tulee", "tulemme", "tulette", "tulevat",
|
|
"tulin", "tulit", "tuli", "tulimme", "tulitte", "tulivat",
|
|
"tule", "tulis", "tule", "tuleva", "tullut"]
|
|
return r
|
|
}
|
|
|
|
// tehdä — to do / make (Type 2, irregular)
|
|
if str_eq(dict_form, "tehdä") {
|
|
let r: [String] = ["tehdä", "teen", "teet", "tekee", "teemme", "teette", "tekevät",
|
|
"tein", "teit", "teki", "teimme", "teitte", "tekivät",
|
|
"tee", "tekis", "tee", "tekevä", "tehnyt"]
|
|
return r
|
|
}
|
|
|
|
// nähdä — to see (Type 2, irregular)
|
|
if str_eq(dict_form, "nähdä") {
|
|
let r: [String] = ["nähdä", "näen", "näet", "näkee", "näemme", "näette", "näkevät",
|
|
"näin", "näit", "näki", "näimme", "näitte", "näkivät",
|
|
"näe", "näkis", "näe", "näkevä", "nähnyt"]
|
|
return r
|
|
}
|
|
|
|
// saada — to get / to be able to (Type 2)
|
|
if str_eq(dict_form, "saada") {
|
|
let r: [String] = ["saada", "saan", "saat", "saa", "saamme", "saatte", "saavat",
|
|
"sain", "sait", "sai", "saimme", "saitte", "saivat",
|
|
"saa", "sais", "saa", "saava", "saanut"]
|
|
return r
|
|
}
|
|
|
|
// pitää — must / to like (Type 1 with stem change)
|
|
if str_eq(dict_form, "pitää") {
|
|
let r: [String] = ["pitää", "pidän", "pidät", "pitää", "pidämme", "pidätte", "pitävät",
|
|
"pidin", "pidit", "piti", "pidimme", "piditte", "pitivät",
|
|
"pidä", "pitäis", "pidä", "pitävä", "pitänyt"]
|
|
return r
|
|
}
|
|
|
|
// tietää — to know (Type 1 with stem change)
|
|
if str_eq(dict_form, "tietää") {
|
|
let r: [String] = ["tietää", "tiedän", "tiedät", "tietää", "tiedämme", "tiedätte", "tietävät",
|
|
"tiesin", "tiesit", "tiesi", "tiesimme", "tiesitte", "tiesivät",
|
|
"tiedä", "tietäis", "tiedä", "tietävä", "tiennyt"]
|
|
return r
|
|
}
|
|
|
|
return empty
|
|
}
|
|
|
|
// ── Present-tense personal endings ───────────────────────────────────────────
|
|
//
|
|
// Type-1 verb present tense endings (suffix onto stem):
|
|
// 1sg: -n 2sg: -t 3sg: stem-final vowel lengthened (no suffix)
|
|
// 1pl: -mme 2pl: -tte 3pl: -vat/-vät
|
|
//
|
|
// The 3sg form doubles the final vowel of the stem (puhu → puhuu, tule → tulee).
|
|
// The 3pl uses the harmony-dependent -vat/-vät suffix.
|
|
|
|
fn fi_present_ending(stem: String, person: String, number: String, harmony: String) -> String {
|
|
if str_eq(number, "singular") {
|
|
if str_eq(person, "first") { return stem + "n" }
|
|
if str_eq(person, "second") { return stem + "t" }
|
|
if str_eq(person, "third") {
|
|
// 3sg: lengthen final vowel
|
|
let last: String = fi_str_last_char(stem)
|
|
return stem + last
|
|
}
|
|
}
|
|
if str_eq(number, "plural") {
|
|
if str_eq(person, "first") { return stem + "mme" }
|
|
if str_eq(person, "second") { return stem + "tte" }
|
|
if str_eq(person, "third") { return stem + fi_suffix("vat", harmony) }
|
|
}
|
|
return stem
|
|
}
|
|
|
|
// ── Past-tense forms ──────────────────────────────────────────────────────────
|
|
//
|
|
// Type-1 verbs form the past by inserting -i- between the stem and the personal
|
|
// ending. The stem-final vowel may contract before -i-.
|
|
//
|
|
// puhua: puhu + i → puhui + n → puhuin (1sg past)
|
|
// Common contraction: stem final -a/-ä drops before -i-
|
|
// puhua stem puhu → puhu + i = puhui (no drop needed, -u not -a)
|
|
// tavata → contraction gives tavasi (handled as irregular or Type 4)
|
|
// For Type-1 verbs with -u/-y final stem the rule is simple concatenation.
|
|
|
|
fn fi_past_stem(stem: String) -> String {
|
|
// If stem ends in a or ä, they may contract. For Type-1 verbs where the
|
|
// infinitive is -aa/-ää the stem ends in -a/-ä; before -i- that often
|
|
// gives -oi-/-öi- (e.g. puhua: puhu → puhui, but sanoa: sano → sanoi).
|
|
// The heuristic: if the stem already ends in a vowel other than a/ä, just
|
|
// append i. If it ends in a/ä, convert to o/ö + i (common pattern).
|
|
let last: String = fi_str_last_char(stem)
|
|
if str_eq(last, "a") {
|
|
return str_drop_last(stem, 1) + "oi"
|
|
}
|
|
if str_eq(last, "ä") {
|
|
return str_drop_last(stem, 1) + "öi"
|
|
}
|
|
return stem + "i"
|
|
}
|
|
|
|
fn fi_past_ending(stem: String, person: String, number: String, harmony: String) -> String {
|
|
let pstem: String = fi_past_stem(stem)
|
|
if str_eq(number, "singular") {
|
|
if str_eq(person, "first") { return pstem + "n" }
|
|
if str_eq(person, "second") { return pstem + "t" }
|
|
if str_eq(person, "third") { return str_drop_last(pstem, 1) }
|
|
}
|
|
if str_eq(number, "plural") {
|
|
if str_eq(person, "first") { return pstem + "mme" }
|
|
if str_eq(person, "second") { return pstem + "tte" }
|
|
if str_eq(person, "third") { return pstem + fi_suffix("vat", harmony) }
|
|
}
|
|
return pstem
|
|
}
|
|
|
|
// ── Negative forms ────────────────────────────────────────────────────────────
|
|
//
|
|
// Finnish negation: negative auxiliary ei (conjugated for person/number) +
|
|
// verb in the connective (negative) stem = infinitive stem without personal ending.
|
|
//
|
|
// Negative auxiliary conjugation:
|
|
// 1sg: en 2sg: et 3sg: ei
|
|
// 1pl: emme 2pl: ette 3pl: eivät
|
|
//
|
|
// The negative stem for most verbs = present stem (the form without any ending).
|
|
|
|
fn fi_neg_aux(person: String, number: String) -> String {
|
|
if str_eq(number, "singular") {
|
|
if str_eq(person, "first") { return "en" }
|
|
if str_eq(person, "second") { return "et" }
|
|
if str_eq(person, "third") { return "ei" }
|
|
}
|
|
if str_eq(number, "plural") {
|
|
if str_eq(person, "first") { return "emme" }
|
|
if str_eq(person, "second") { return "ette" }
|
|
if str_eq(person, "third") { return "eivät" }
|
|
}
|
|
return "ei"
|
|
}
|
|
|
|
fn fi_negative(verb: String, person: String, number: String) -> String {
|
|
let irreg: [String] = fi_irregular_verb(verb)
|
|
let aux: String = fi_neg_aux(person, number)
|
|
if native_list_len(irreg) > 0 {
|
|
let neg_stem: String = native_list_get(irreg, 13)
|
|
return aux + " " + neg_stem
|
|
}
|
|
let stem: String = fi_verb_stem(verb)
|
|
return aux + " " + stem
|
|
}
|
|
|
|
// ── Main conjugation entry point ──────────────────────────────────────────────
|
|
//
|
|
// fi_conjugate(verb, tense, person, number) -> String
|
|
//
|
|
// tense: "present" | "past"
|
|
// person: "first" | "second" | "third"
|
|
// number: "singular" | "plural"
|
|
|
|
fn fi_conjugate(verb: String, tense: String, person: String, number: String) -> String {
|
|
let harmony: String = fi_harmony(verb)
|
|
|
|
// Check irregular table first
|
|
let irreg: [String] = fi_irregular_verb(verb)
|
|
if native_list_len(irreg) > 0 {
|
|
if str_eq(tense, "present") {
|
|
if str_eq(number, "singular") {
|
|
if str_eq(person, "first") { return native_list_get(irreg, 1) }
|
|
if str_eq(person, "second") { return native_list_get(irreg, 2) }
|
|
if str_eq(person, "third") { return native_list_get(irreg, 3) }
|
|
}
|
|
if str_eq(number, "plural") {
|
|
if str_eq(person, "first") { return native_list_get(irreg, 4) }
|
|
if str_eq(person, "second") { return native_list_get(irreg, 5) }
|
|
if str_eq(person, "third") { return native_list_get(irreg, 6) }
|
|
}
|
|
}
|
|
if str_eq(tense, "past") {
|
|
if str_eq(number, "singular") {
|
|
if str_eq(person, "first") { return native_list_get(irreg, 7) }
|
|
if str_eq(person, "second") { return native_list_get(irreg, 8) }
|
|
if str_eq(person, "third") { return native_list_get(irreg, 9) }
|
|
}
|
|
if str_eq(number, "plural") {
|
|
if str_eq(person, "first") { return native_list_get(irreg, 10) }
|
|
if str_eq(person, "second") { return native_list_get(irreg, 11) }
|
|
if str_eq(person, "third") { return native_list_get(irreg, 12) }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regular verbs
|
|
let stem: String = fi_verb_stem(verb)
|
|
|
|
if str_eq(tense, "present") {
|
|
return fi_present_ending(stem, person, number, harmony)
|
|
}
|
|
|
|
if str_eq(tense, "past") {
|
|
return fi_past_ending(stem, person, number, harmony)
|
|
}
|
|
|
|
return stem
|
|
}
|
|
|
|
// ── Question suffix ───────────────────────────────────────────────────────────
|
|
//
|
|
// Finnish questions are formed by appending -ko (back harmony) or -kö (front
|
|
// harmony) directly to the verb (or sometimes another focus word).
|
|
|
|
fn fi_question_suffix(harmony: String) -> String {
|
|
if str_eq(harmony, "front") { return "kö" }
|
|
return "ko"
|
|
}
|
|
|
|
// ── Question formation ────────────────────────────────────────────────────────
|
|
//
|
|
// fi_make_question: append the appropriate question suffix to a verb form.
|
|
|
|
fn fi_make_question(verb_form: String, harmony: String) -> String {
|
|
return verb_form + fi_question_suffix(harmony)
|
|
}
|
|
|
|
// ── Convenience: inflect a noun through all 15 cases ─────────────────────────
|
|
//
|
|
// Returns a 30-element list: [case_name, sg_form, case_name, pl_form, ...]
|
|
// for all 15 cases. Plural-only cases (instructive, comitative) have an
|
|
// empty string for the singular slot.
|
|
|
|
fn fi_full_paradigm(noun: String) -> [String] {
|
|
let harmony: String = fi_harmony(noun)
|
|
let r: [String] = []
|
|
let cases: [String] = ["nominative", "genitive", "accusative", "partitive",
|
|
"inessive", "elative", "illative", "adessive",
|
|
"ablative", "allative", "essive", "translative",
|
|
"instructive", "abessive", "comitative"]
|
|
let n: Int = native_list_len(cases)
|
|
let i: Int = 0
|
|
while i < n {
|
|
let c: String = native_list_get(cases, i)
|
|
let r = native_list_append(r, c)
|
|
// Singular
|
|
if str_eq(c, "instructive") {
|
|
let r = native_list_append(r, "")
|
|
} else {
|
|
if str_eq(c, "comitative") {
|
|
let r = native_list_append(r, "")
|
|
} else {
|
|
let r = native_list_append(r, fi_noun_case(noun, c, "singular", harmony))
|
|
}
|
|
}
|
|
// Plural
|
|
let r = native_list_append(r, fi_noun_case(noun, c, "plural", harmony))
|
|
let i = i + 1
|
|
}
|
|
return r
|
|
}
|