Files
el/elp/src/morphology-es.el
2026-05-02 22:15:25 -05:00

716 lines
25 KiB
EmacsLisp
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// morphology-es.el - Spanish morphology for the NLG engine.
//
// Implements fusional Spanish verb conjugation, noun pluralization, gender
// inference, and article agreement. Designed as a companion to morphology.el
// and called by the engine when the language profile code is "es".
//
// Verb tenses covered: present, preterite (past), future, imperfect.
// Persons: first/second/third × singular/plural (1s 2s 3s 1p 2p 3p).
// Verb classes: -ar, -er, -ir (regular) + a core set of common irregulars.
//
// Depends on: morphology.el (str_ends, str_drop_last, str_last_char, str_last2, str_last3, is_vowel)
// String helpers (local, matching morphology.el conventions)
fn es_str_ends(s: String, suf: String) -> Bool {
return str_ends_with(s, suf)
}
fn es_str_drop_last(s: String, n: Int) -> String {
let len: Int = str_len(s)
if n >= len {
return ""
}
return str_slice(s, 0, len - n)
}
fn es_str_last_char(s: String) -> String {
let n: Int = str_len(s)
if n == 0 {
return ""
}
return str_slice(s, n - 1, n)
}
fn es_str_last2(s: String) -> String {
let n: Int = str_len(s)
if n < 2 {
return s
}
return str_slice(s, n - 2, n)
}
fn es_str_last3(s: String) -> String {
let n: Int = str_len(s)
if n < 3 {
return s
}
return str_slice(s, n - 3, n)
}
// Verb class detection
//
// Spanish verbs fall into three conjugation classes defined by the infinitive
// ending: -ar, -er, -ir. The stem is the infinitive minus those two characters.
fn es_verb_class(base: String) -> String {
if es_str_ends(base, "ar") { return "ar" }
if es_str_ends(base, "er") { return "er" }
if es_str_ends(base, "ir") { return "ir" }
return "ar"
}
fn es_stem(base: String) -> String {
return es_str_drop_last(base, 2)
}
// Person/number index
//
// Maps person × number to a 0-based slot index used inside paradigm tables.
// 0 = 1s, 1 = 2s, 2 = 3s, 3 = 1p, 4 = 2p, 5 = 3p
fn es_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
}
// Irregular present tense
//
// Returns the fully-inflected form if the verb is irregular in the present
// tense for the given person/number slot, otherwise returns "".
//
// ser: soy, eres, es, somos, sois, son
// estar: estoy, estás, está, estamos, estáis, están
// tener: tengo, tienes, tiene, tenemos, tenéis, tienen
// hacer: hago, haces, hace, hacemos, hacéis, hacen
// ir: voy, vas, va, vamos, vais, van
// ver: veo, ves, ve, vemos, veis, ven
// dar: doy, das, da, damos, dais, dan
// saber: sé, sabes, sabe, sabemos, sabéis, saben
// poder: puedo, puedes, puede, podemos, podéis, pueden
// querer: quiero, quieres, quiere, queremos, queréis, quieren
// venir: vengo, vienes, viene, venimos, venís, vienen
// decir: digo, dices, dice, decimos, decís, dicen
// haber: he, has, ha, hemos, habéis, han
fn es_irregular_present(verb: String, person: String, number: String) -> String {
let slot: Int = es_slot(person, number)
if str_eq(verb, "ser") {
if slot == 0 { return "soy" }
if slot == 1 { return "eres" }
if slot == 2 { return "es" }
if slot == 3 { return "somos" }
if slot == 4 { return "sois" }
return "son"
}
if str_eq(verb, "estar") {
if slot == 0 { return "estoy" }
if slot == 1 { return "estás" }
if slot == 2 { return "está" }
if slot == 3 { return "estamos" }
if slot == 4 { return "estáis" }
return "están"
}
if str_eq(verb, "tener") {
if slot == 0 { return "tengo" }
if slot == 1 { return "tienes" }
if slot == 2 { return "tiene" }
if slot == 3 { return "tenemos" }
if slot == 4 { return "tenéis" }
return "tienen"
}
if str_eq(verb, "hacer") {
if slot == 0 { return "hago" }
if slot == 1 { return "haces" }
if slot == 2 { return "hace" }
if slot == 3 { return "hacemos" }
if slot == 4 { return "hacéis" }
return "hacen"
}
if str_eq(verb, "ir") {
if slot == 0 { return "voy" }
if slot == 1 { return "vas" }
if slot == 2 { return "va" }
if slot == 3 { return "vamos" }
if slot == 4 { return "vais" }
return "van"
}
if str_eq(verb, "ver") {
if slot == 0 { return "veo" }
if slot == 1 { return "ves" }
if slot == 2 { return "ve" }
if slot == 3 { return "vemos" }
if slot == 4 { return "veis" }
return "ven"
}
if str_eq(verb, "dar") {
if slot == 0 { return "doy" }
if slot == 1 { return "das" }
if slot == 2 { return "da" }
if slot == 3 { return "damos" }
if slot == 4 { return "dais" }
return "dan"
}
if str_eq(verb, "saber") {
if slot == 0 { return "" }
if slot == 1 { return "sabes" }
if slot == 2 { return "sabe" }
if slot == 3 { return "sabemos" }
if slot == 4 { return "sabéis" }
return "saben"
}
if str_eq(verb, "poder") {
if slot == 0 { return "puedo" }
if slot == 1 { return "puedes" }
if slot == 2 { return "puede" }
if slot == 3 { return "podemos" }
if slot == 4 { return "podéis" }
return "pueden"
}
if str_eq(verb, "querer") {
if slot == 0 { return "quiero" }
if slot == 1 { return "quieres" }
if slot == 2 { return "quiere" }
if slot == 3 { return "queremos" }
if slot == 4 { return "queréis" }
return "quieren"
}
if str_eq(verb, "venir") {
if slot == 0 { return "vengo" }
if slot == 1 { return "vienes" }
if slot == 2 { return "viene" }
if slot == 3 { return "venimos" }
if slot == 4 { return "venís" }
return "vienen"
}
if str_eq(verb, "decir") {
if slot == 0 { return "digo" }
if slot == 1 { return "dices" }
if slot == 2 { return "dice" }
if slot == 3 { return "decimos" }
if slot == 4 { return "decís" }
return "dicen"
}
if str_eq(verb, "haber") {
if slot == 0 { return "he" }
if slot == 1 { return "has" }
if slot == 2 { return "ha" }
if slot == 3 { return "hemos" }
if slot == 4 { return "habéis" }
return "han"
}
return ""
}
// Irregular preterite tense
//
// Returns the inflected preterite form for irregular verbs, or "" if regular.
//
// ser/ir (same preterite): fui, fuiste, fue, fuimos, fuisteis, fueron
// tener: tuve, tuviste, tuvo, tuvimos, tuvisteis, tuvieron
// hacer: hice, hiciste, hizo, hicimos, hicisteis, hicieron
// estar: estuve, estuviste, estuvo, estuvimos, estuvisteis, estuvieron
// dar: di, diste, dio, dimos, disteis, dieron
// saber: supe, supiste, supo, supimos, supisteis, supieron
// poder: pude, pudiste, pudo, pudimos, pudisteis, pudieron
// querer: quise, quisiste, quiso, quisimos, quisisteis, quisieron
// venir: vine, viniste, vino, vinimos, vinisteis, vinieron
// decir: dije, dijiste, dijo, dijimos, dijisteis, dijeron
// haber: hube, hubiste, hubo, hubimos, hubisteis, hubieron
// ver: vi, viste, vio, vimos, visteis, vieron
fn es_irregular_preterite(verb: String, person: String, number: String) -> String {
let slot: Int = es_slot(person, number)
if str_eq(verb, "ser") {
if slot == 0 { return "fui" }
if slot == 1 { return "fuiste" }
if slot == 2 { return "fue" }
if slot == 3 { return "fuimos" }
if slot == 4 { return "fuisteis" }
return "fueron"
}
if str_eq(verb, "ir") {
if slot == 0 { return "fui" }
if slot == 1 { return "fuiste" }
if slot == 2 { return "fue" }
if slot == 3 { return "fuimos" }
if slot == 4 { return "fuisteis" }
return "fueron"
}
if str_eq(verb, "tener") {
if slot == 0 { return "tuve" }
if slot == 1 { return "tuviste" }
if slot == 2 { return "tuvo" }
if slot == 3 { return "tuvimos" }
if slot == 4 { return "tuvisteis" }
return "tuvieron"
}
if str_eq(verb, "hacer") {
if slot == 0 { return "hice" }
if slot == 1 { return "hiciste" }
if slot == 2 { return "hizo" }
if slot == 3 { return "hicimos" }
if slot == 4 { return "hicisteis" }
return "hicieron"
}
if str_eq(verb, "estar") {
if slot == 0 { return "estuve" }
if slot == 1 { return "estuviste" }
if slot == 2 { return "estuvo" }
if slot == 3 { return "estuvimos" }
if slot == 4 { return "estuvisteis" }
return "estuvieron"
}
if str_eq(verb, "dar") {
if slot == 0 { return "di" }
if slot == 1 { return "diste" }
if slot == 2 { return "dio" }
if slot == 3 { return "dimos" }
if slot == 4 { return "disteis" }
return "dieron"
}
if str_eq(verb, "saber") {
if slot == 0 { return "supe" }
if slot == 1 { return "supiste" }
if slot == 2 { return "supo" }
if slot == 3 { return "supimos" }
if slot == 4 { return "supisteis" }
return "supieron"
}
if str_eq(verb, "poder") {
if slot == 0 { return "pude" }
if slot == 1 { return "pudiste" }
if slot == 2 { return "pudo" }
if slot == 3 { return "pudimos" }
if slot == 4 { return "pudisteis" }
return "pudieron"
}
if str_eq(verb, "querer") {
if slot == 0 { return "quise" }
if slot == 1 { return "quisiste" }
if slot == 2 { return "quiso" }
if slot == 3 { return "quisimos" }
if slot == 4 { return "quisisteis" }
return "quisieron"
}
if str_eq(verb, "venir") {
if slot == 0 { return "vine" }
if slot == 1 { return "viniste" }
if slot == 2 { return "vino" }
if slot == 3 { return "vinimos" }
if slot == 4 { return "vinisteis" }
return "vinieron"
}
if str_eq(verb, "decir") {
if slot == 0 { return "dije" }
if slot == 1 { return "dijiste" }
if slot == 2 { return "dijo" }
if slot == 3 { return "dijimos" }
if slot == 4 { return "dijisteis" }
return "dijeron"
}
if str_eq(verb, "haber") {
if slot == 0 { return "hube" }
if slot == 1 { return "hubiste" }
if slot == 2 { return "hubo" }
if slot == 3 { return "hubimos" }
if slot == 4 { return "hubisteis" }
return "hubieron"
}
if str_eq(verb, "ver") {
if slot == 0 { return "vi" }
if slot == 1 { return "viste" }
if slot == 2 { return "vio" }
if slot == 3 { return "vimos" }
if slot == 4 { return "visteis" }
return "vieron"
}
return ""
}
// Irregular imperfect tense
//
// Only three verbs are truly irregular in the imperfect:
// ser: era, eras, era, éramos, erais, eran
// ir: iba, ibas, iba, íbamos, ibais, iban
// ver: veía, veías, veía, veíamos, veíais, veían
fn es_irregular_imperfect(verb: String, person: String, number: String) -> String {
let slot: Int = es_slot(person, number)
if str_eq(verb, "ser") {
if slot == 0 { return "era" }
if slot == 1 { return "eras" }
if slot == 2 { return "era" }
if slot == 3 { return "éramos" }
if slot == 4 { return "erais" }
return "eran"
}
if str_eq(verb, "ir") {
if slot == 0 { return "iba" }
if slot == 1 { return "ibas" }
if slot == 2 { return "iba" }
if slot == 3 { return "íbamos" }
if slot == 4 { return "ibais" }
return "iban"
}
if str_eq(verb, "ver") {
if slot == 0 { return "veía" }
if slot == 1 { return "veías" }
if slot == 2 { return "veía" }
if slot == 3 { return "veíamos" }
if slot == 4 { return "veíais" }
return "veían"
}
return ""
}
// Regular present conjugation
//
// -ar: -o, -as, -a, -amos, -áis, -an
// -er: -o, -es, -e, -emos, -éis, -en
// -ir: -o, -es, -e, -imos, -ís, -en
fn es_regular_present(stem: String, vclass: String, slot: Int) -> String {
if str_eq(vclass, "ar") {
if slot == 0 { return stem + "o" }
if slot == 1 { return stem + "as" }
if slot == 2 { return stem + "a" }
if slot == 3 { return stem + "amos" }
if slot == 4 { return stem + "áis" }
return stem + "an"
}
if str_eq(vclass, "er") {
if slot == 0 { return stem + "o" }
if slot == 1 { return stem + "es" }
if slot == 2 { return stem + "e" }
if slot == 3 { return stem + "emos" }
if slot == 4 { return stem + "éis" }
return stem + "en"
}
// -ir
if slot == 0 { return stem + "o" }
if slot == 1 { return stem + "es" }
if slot == 2 { return stem + "e" }
if slot == 3 { return stem + "imos" }
if slot == 4 { return stem + "ís" }
return stem + "en"
}
// Regular preterite conjugation
//
// -ar: -é, -aste, -ó, -amos, -asteis, -aron
// -er: -í, -iste, -ió, -imos, -isteis, -ieron
// -ir: -í, -iste, -ió, -imos, -isteis, -ieron
fn es_regular_preterite(stem: String, vclass: String, slot: Int) -> String {
if str_eq(vclass, "ar") {
if slot == 0 { return stem + "é" }
if slot == 1 { return stem + "aste" }
if slot == 2 { return stem + "ó" }
if slot == 3 { return stem + "amos" }
if slot == 4 { return stem + "asteis" }
return stem + "aron"
}
// -er and -ir share the same preterite endings
if slot == 0 { return stem + "í" }
if slot == 1 { return stem + "iste" }
if slot == 2 { return stem + "" }
if slot == 3 { return stem + "imos" }
if slot == 4 { return stem + "isteis" }
return stem + "ieron"
}
// Regular future conjugation
//
// Future is formed from the full infinitive + endings (all classes):
// -é, -ás, -á, -emos, -éis, -án
//
// No stem change; the infinitive is the future stem.
fn es_regular_future(base: String, slot: Int) -> String {
if slot == 0 { return base + "é" }
if slot == 1 { return base + "ás" }
if slot == 2 { return base + "á" }
if slot == 3 { return base + "emos" }
if slot == 4 { return base + "éis" }
return base + "án"
}
// Irregular future stems
//
// Some verbs contract or alter their infinitive for the future stem.
// Returns the irregular future stem, or "" if the verb uses the regular stem.
fn es_irregular_future_stem(verb: String) -> String {
if str_eq(verb, "tener") { return "tendr" }
if str_eq(verb, "hacer") { return "har" }
if str_eq(verb, "poder") { return "podr" }
if str_eq(verb, "querer") { return "querr" }
if str_eq(verb, "venir") { return "vendr" }
if str_eq(verb, "decir") { return "dir" }
if str_eq(verb, "haber") { return "habr" }
if str_eq(verb, "saber") { return "sabr" }
if str_eq(verb, "salir") { return "saldr" }
if str_eq(verb, "poner") { return "pondr" }
return ""
}
// Regular imperfect conjugation
//
// -ar: -aba, -abas, -aba, -ábamos, -abais, -aban
// -er/-ir: -ía, -ías, -ía, -íamos, -íais, -ían
fn es_regular_imperfect(stem: String, vclass: String, slot: Int) -> String {
if str_eq(vclass, "ar") {
if slot == 0 { return stem + "aba" }
if slot == 1 { return stem + "abas" }
if slot == 2 { return stem + "aba" }
if slot == 3 { return stem + "ábamos" }
if slot == 4 { return stem + "abais" }
return stem + "aban"
}
// -er and -ir
if slot == 0 { return stem + "ía" }
if slot == 1 { return stem + "ías" }
if slot == 2 { return stem + "ía" }
if slot == 3 { return stem + "íamos" }
if slot == 4 { return stem + "íais" }
return stem + "ían"
}
// Full conjugation entry point
//
// es_conjugate: conjugate a Spanish verb.
//
// verb: Spanish infinitive (e.g. "hablar", "ser", "tener")
// tense: "present" | "past" | "future" | "imperfect"
// (note: "past" maps to the preterite/indefinite past)
// person: "first" | "second" | "third"
// number: "singular" | "plural"
fn es_conjugate(verb: String, tense: String, person: String, number: String) -> String {
let slot: Int = es_slot(person, number)
if str_eq(tense, "present") {
let irreg: String = es_irregular_present(verb, person, number)
if !str_eq(irreg, "") {
return irreg
}
let vclass: String = es_verb_class(verb)
let stem: String = es_stem(verb)
return es_regular_present(stem, vclass, slot)
}
if str_eq(tense, "past") {
let irreg: String = es_irregular_preterite(verb, person, number)
if !str_eq(irreg, "") {
return irreg
}
let vclass: String = es_verb_class(verb)
let stem: String = es_stem(verb)
return es_regular_preterite(stem, vclass, slot)
}
if str_eq(tense, "future") {
let irreg_stem: String = es_irregular_future_stem(verb)
if !str_eq(irreg_stem, "") {
return es_regular_future(irreg_stem, slot)
}
return es_regular_future(verb, slot)
}
if str_eq(tense, "imperfect") {
let irreg: String = es_irregular_imperfect(verb, person, number)
if !str_eq(irreg, "") {
return irreg
}
let vclass: String = es_verb_class(verb)
let stem: String = es_stem(verb)
return es_regular_imperfect(stem, vclass, slot)
}
// Unknown tense: return infinitive unchanged
return verb
}
// Noun gender inference
//
// Returns "m" (masculine), "f" (feminine), or "unknown".
//
// Heuristics (not exhaustive cover most common patterns):
// ends in -o -> masculine (libro, gato, niño)
// ends in -a -> feminine (casa, mesa, niña)
// ends in -ión -> feminine (canción, nación)
// ends in -dad/-tad -> feminine (ciudad, libertad)
// ends in -umbre -> feminine (costumbre)
// ends in -sis -> feminine (crisis, tesis)
// ends in -ema/-ama -> masculine (problema, programa, tema, idioma)
// ends in -or -> masculine (color, amor, señor)
// ends in -aje -> masculine (viaje, paisaje)
// ends in -án/-ón -> masculine (avión check -ión first)
// otherwise -> unknown
fn es_gender(noun: String) -> String {
// -ión before -o so "avión" feminine (it ends -ión, not just -on)
if es_str_ends(noun, "ión") { return "f" }
if es_str_ends(noun, "dad") { return "f" }
if es_str_ends(noun, "tad") { return "f" }
if es_str_ends(noun, "umbre") { return "f" }
if es_str_ends(noun, "sis") { return "f" }
if es_str_ends(noun, "ema") { return "m" }
if es_str_ends(noun, "ama") { return "m" }
if es_str_ends(noun, "aje") { return "m" }
if es_str_ends(noun, "or") { return "m" }
if es_str_ends(noun, "o") { return "m" }
if es_str_ends(noun, "a") { return "f" }
return "unknown"
}
// Noun pluralization
//
// Rules (applied in order):
// ends in vowel (a e i o u) -> add -s
// ends in consonant -> add -es
// ends in -z -> replace -z with -ces
// ends in -s (unstressed) -> unchanged (e.g. "el lunes" -> "los lunes")
//
// Note: nouns ending in stressed vowel + s (e.g. "el autobús" "los autobuses")
// are handled by the consonant rule since -s is a consonant ending for pluralization
// purposes; but "el lunes" (days of week ending in -s) stay unchanged — this is
// an irregular class. The table below handles common invariant nouns.
fn es_invariant_plural(noun: String) -> String {
if str_eq(noun, "lunes") { return "lunes" }
if str_eq(noun, "martes") { return "martes" }
if str_eq(noun, "miércoles") { return "miércoles" }
if str_eq(noun, "jueves") { return "jueves" }
if str_eq(noun, "viernes") { return "viernes" }
if str_eq(noun, "crisis") { return "crisis" }
if str_eq(noun, "tesis") { return "tesis" }
if str_eq(noun, "análisis") { return "análisis" }
if str_eq(noun, "dosis") { return "dosis" }
if str_eq(noun, "virus") { return "virus" }
return ""
}
fn es_pluralize(noun: String) -> String {
let inv: String = es_invariant_plural(noun)
if !str_eq(inv, "") {
return inv
}
let last: String = es_str_last_char(noun)
// Ends in -z: replace with -ces
if str_eq(last, "z") {
return es_str_drop_last(noun, 1) + "ces"
}
// Ends in a vowel: add -s
if str_eq(last, "a") { return noun + "s" }
if str_eq(last, "e") { return noun + "s" }
if str_eq(last, "i") { return noun + "s" }
if str_eq(last, "o") { return noun + "s" }
if str_eq(last, "u") { return noun + "s" }
// Ends in consonant (including -s for stressed words like autobús): add -es
return noun + "es"
}
// Article agreement
//
// es_agree_article: return the correct Spanish article for a noun.
//
// noun: the noun (used for gender and number inference)
// definite: "true" for definite (el/la/los/las), "false" for indefinite (un/una/unos/unas)
// number: "singular" | "plural"
//
// Special case: feminine nouns beginning with stressed "a-" or "ha-" take
// masculine singular definite article: "el agua", "el hacha".
// This is handled by checking the noun's first character when gender is feminine.
fn es_starts_with_stressed_a(noun: String) -> Bool {
// Approximate: check if noun starts with "a" or "ha" (covers most cases)
// The accent on the first syllable is not detectable orthographically in
// general, so we apply the rule broadly for any feminine noun starting with
// "a" or "ha" in singular.
let n: Int = str_len(noun)
if n == 0 {
return false
}
let c0: String = str_slice(noun, 0, 1)
if str_eq(c0, "a") { return true }
if n >= 2 {
let c1: String = str_slice(noun, 1, 2)
if str_eq(c0, "h") {
if str_eq(c1, "a") { return true }
}
}
return false
}
fn es_agree_article(noun: String, definite: String, number: String) -> String {
let gender: String = es_gender(noun)
let is_plural: Bool = str_eq(number, "plural")
let is_def: Bool = str_eq(definite, "true")
if is_def {
if is_plural {
if str_eq(gender, "f") { return "las" }
return "los"
}
// singular
if str_eq(gender, "f") {
// el agua rule: feminine singular nouns starting with stressed "a"
if es_starts_with_stressed_a(noun) { return "el" }
return "la"
}
return "el"
}
// indefinite
if is_plural {
if str_eq(gender, "f") { return "unas" }
return "unos"
}
if str_eq(gender, "f") { return "una" }
return "un"
}