716 lines
25 KiB
EmacsLisp
716 lines
25 KiB
EmacsLisp
// 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 "sé" }
|
||
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 + "ió" }
|
||
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"
|
||
}
|