c62ea3383c
Port the validated sandbox Spanish realizer into the ELP .el runtime (STAGE only). - vocabulary-es.el: 342 entries generated from UniMorph via morphology_es_full (real forms; nouns carry REAL per-lemma lexicon gender in form2 — kills the el-mano/la-dia masculine-default class). - lang_profile_es.el: Spanish typology flags incl. mandatory contractions + govt. - morphology-es.el: add es_participle, es_inflect_adj, es_contract, es_article_for_gender; accent-correct plural/preterite/participle. - parity harness: 90.1% morphology parity vs Python oracle; gender heuristic 73.6% vs vocab-real 100%; contraction 100%.
263 lines
14 KiB
Python
263 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""gen_elp_es.py — emit the ELP (.el) port artifacts for Spanish.
|
|
|
|
Mirrors gen_elp_en.py. Produces:
|
|
vocabulary-es.el real generated vocabulary in the established schema
|
|
[lemma, pos, form0, form1, form2, en_translation, semantic_hint]
|
|
(same schema as vocabulary-got.el / vocabulary-en.el;
|
|
UniMorph spa lineage).
|
|
Verbs : form0=present-ind-3sg form1=preterite-3sg form2=past-participle
|
|
Nouns : form0=singular form1=plural form2=REAL gender (m/f, lexicon)
|
|
Adjs : form0=masc-sg form1=fem-sg form2=masc-pl
|
|
lang_profile_es.el the Spanish profile with the flags the realizer keys on.
|
|
|
|
Every form is generated by morphology_es_full (real UniMorph lexicon, not
|
|
hand-typed), so the .el vocabulary is honest and reproduces the forms the
|
|
realizer used. CRITICAL (coordinator quality bar): noun gender in form2 is the
|
|
REAL per-lemma lexicon gender (N;FEM/MASC), NOT an ending heuristic — this is
|
|
what kills the 'el mano / la día' masculine-default error class.
|
|
"""
|
|
import morphology_es_full as M
|
|
from test_set_es import TESTS
|
|
from held_out_es import HELD
|
|
|
|
# ── core closed class + common content lemmas so the vocab is usable beyond the
|
|
# validated sentences ──────────────────────────────────────────────────────
|
|
_CORE_VERBS = ["ser", "estar", "haber", "tener", "hacer", "ir", "ver", "dar",
|
|
"saber", "poder", "querer", "venir", "decir", "poner", "salir",
|
|
"hablar", "comer", "vivir", "trabajar", "estudiar", "llegar",
|
|
"pasar", "deber", "parecer", "quedar", "creer", "dejar", "llevar",
|
|
"encontrar", "llamar", "pensar", "volver", "conocer", "sentir",
|
|
"contar", "empezar", "buscar", "esperar", "existir", "entrar",
|
|
"escribir", "perder", "producir", "recordar", "morir", "nacer",
|
|
"abrir", "escapar", "soñar", "amar", "caer", "leer", "oír"]
|
|
_CORE_NOUNS = ["tiempo", "persona", "año", "día", "mano", "mundo", "vida",
|
|
"hombre", "mujer", "parte", "casa", "país", "problema", "programa",
|
|
"tema", "mapa", "agua", "foto", "moto", "ciudad", "libertad",
|
|
"canción", "nación", "flor", "color", "amor", "señor", "viaje",
|
|
"paisaje", "gato", "perro", "libro", "mesa", "silla", "noche",
|
|
"luz", "voz", "pez", "raíz", "crisis", "sol", "luna", "mar",
|
|
"corazón", "flor", "árbol", "camino", "puerta", "ventana"]
|
|
_CORE_ADJS = ["bueno", "malo", "nuevo", "viejo", "grande", "pequeño", "alto",
|
|
"bajo", "largo", "corto", "feliz", "triste", "fácil", "difícil",
|
|
"rápido", "lento", "hermoso", "económico", "político", "social",
|
|
"azul", "rojo", "verde", "blanco", "negro", "español", "francés",
|
|
"inglés", "importante", "posible", "necesario", "trabajador"]
|
|
|
|
# closed-class function words. Contractions (del/al) and the government notes
|
|
# are the coordinator's quality bar (mandatory contraction; verb-prep govt).
|
|
_FUNCTION = [
|
|
# articles (gender/number agreement is in morphology; these are citation)
|
|
("el", "det", "el", "los", "m", "the", "definite article m.sg"),
|
|
("la", "det", "la", "las", "f", "the", "definite article f.sg"),
|
|
("un", "det", "un", "unos","m", "a", "indefinite article m.sg"),
|
|
("una", "det", "una", "unas","f", "a", "indefinite article f.sg"),
|
|
# MANDATORY CONTRACTIONS (prep + el) — del / al
|
|
("del", "contraction", "del", "", "", "of the", "de + el (mandatory contraction)"),
|
|
("al", "contraction", "al", "", "", "to the", "a + el (mandatory contraction)"),
|
|
# demonstratives
|
|
("este", "dem", "este", "estos", "m", "this", "proximal dem m"),
|
|
("esta", "dem", "esta", "estas", "f", "this", "proximal dem f"),
|
|
# negation (SACRED — polarity never dropped)
|
|
("no", "neg", "no", "", "", "not/no", "sentential negator (preverbal)"),
|
|
("ninguno", "det", "ningún", "ninguna", "", "none", "negative determiner (apocope ningún m.sg)"),
|
|
# conjunctions
|
|
("y", "conj", "y", "e", "", "and", "coordinator (e before i-/hi-)"),
|
|
("o", "conj", "o", "u", "", "or", "coordinator (u before o-/ho-)"),
|
|
("pero", "conj", "pero", "", "", "but", "adversative coordinator"),
|
|
("que", "conj", "que", "", "", "that", "complementizer / relative"),
|
|
("si", "conj", "si", "", "", "if", "conditional subordinator"),
|
|
("porque", "conj", "porque", "", "", "because", "causal subordinator"),
|
|
("cuando", "conj", "cuando", "", "", "when", "temporal subordinator"),
|
|
# prepositions (government: verbs select these; contraction with el applies to a/de)
|
|
("a", "prep", "a", "", "", "to", "dir-obj (personal a) / dative / allative; a+el=al"),
|
|
("de", "prep", "de", "", "", "of/from", "genitive/ablative government; de+el=del"),
|
|
("en", "prep", "en", "", "", "in/on", "locative"),
|
|
("con", "prep", "con", "", "", "with", "comitative"),
|
|
("por", "prep", "por", "", "", "by/for", "passive agent / cause"),
|
|
("para", "prep", "para", "", "", "for", "purpose/benefactive"),
|
|
("contra", "prep", "contra", "", "", "against", "adversative government (protestar contra)"),
|
|
("sin", "prep", "sin", "", "", "without", "privative"),
|
|
# subject pronouns
|
|
("yo", "pron", "yo", "me", "mi", "I", "1sg subj/obj/poss"),
|
|
("tú", "pron", "tú", "te", "tu", "you", "2sg informal"),
|
|
("usted", "pron", "usted", "lo", "su", "you", "2sg formal (3sg agreement)"),
|
|
("él", "pron", "él", "lo", "su", "he", "3sg m subj/DO-clitic/poss"),
|
|
("ella", "pron", "ella", "la", "su", "she", "3sg f subj/DO-clitic/poss"),
|
|
("nosotros", "pron", "nosotros", "nos", "nuestro", "we", "1pl"),
|
|
("vosotros", "pron", "vosotros", "os", "vuestro", "you", "2pl informal"),
|
|
("ellos", "pron", "ellos", "los", "su", "they", "3pl m"),
|
|
("ellas", "pron", "ellas", "las", "su", "they", "3pl f"),
|
|
# indirect-object clitics
|
|
("le", "clitic", "le", "les", "", "to-him/her", "dative clitic 3sg/3pl (->se before lo/la)"),
|
|
("se", "clitic", "se", "se", "", "himself/-self", "reflexive / spurious-se (le+lo->se lo)"),
|
|
]
|
|
|
|
|
|
def _walk_collect(spec, verbs, nouns, adjs):
|
|
"""Recursively collect verb/noun/adj lemmas from a semantic spec."""
|
|
if isinstance(spec, dict):
|
|
if spec.get("pred"):
|
|
verbs.add(spec["pred"])
|
|
if spec.get("noun"):
|
|
nouns.add(spec["noun"])
|
|
if spec.get("adj"):
|
|
adjs.add(spec["adj"])
|
|
if spec.get("superlative"):
|
|
adjs.add(spec["superlative"])
|
|
if spec.get("from_adj"):
|
|
adjs.add(spec["from_adj"])
|
|
# adjs: [{"lemma":..,"pos":..}] | ["lemma", ..]
|
|
for a in spec.get("adjs", []) or []:
|
|
adjs.add(a["lemma"] if isinstance(a, dict) else a)
|
|
for a in spec.get("adj_coord", []) or []:
|
|
adjs.add(a["lemma"] if isinstance(a, dict) else a)
|
|
if isinstance(spec.get("pcomp"), dict):
|
|
pc = spec["pcomp"]
|
|
if pc.get("adj"):
|
|
adjs.add(pc["adj"])
|
|
for a in pc.get("adj_coord", []) or []:
|
|
adjs.add(a["lemma"] if isinstance(a, dict) else a)
|
|
for v in spec.values():
|
|
_walk_collect(v, verbs, nouns, adjs)
|
|
elif isinstance(spec, list):
|
|
for it in spec:
|
|
_walk_collect(it, verbs, nouns, adjs)
|
|
|
|
|
|
def _collect_from_specs():
|
|
verbs, nouns, adjs = set(), set(), set()
|
|
for t in TESTS + HELD:
|
|
_walk_collect(t["spec"], verbs, nouns, adjs)
|
|
return verbs, nouns, adjs
|
|
|
|
|
|
def _esc(s):
|
|
return str(s).replace('"', '\\"')
|
|
|
|
|
|
def _row(fields):
|
|
return " (" + " ".join(f'"{_esc(f)}"' for f in fields) + ")"
|
|
|
|
|
|
def emit_vocabulary(path):
|
|
v_specs, n_specs, a_specs = _collect_from_specs()
|
|
verbs = sorted(set(_CORE_VERBS) | v_specs)
|
|
nouns = sorted(set(_CORE_NOUNS) | n_specs)
|
|
adjs = sorted(set(_CORE_ADJS) | a_specs)
|
|
|
|
lines = [
|
|
";;; vocabulary-es.el — Spanish vocabulary for ELP surface realization.",
|
|
";;; Schema: (lemma pos form0 form1 form2 en_translation semantic_hint)",
|
|
";;; Source: UniMorph Spanish (github.com/unimorph/spa, CC-BY-SA 3.0),",
|
|
";;; generated by gen_elp_es.py via morphology_es_full (real forms).",
|
|
";;; Verbs: form0=present-ind-3sg form1=preterite-3sg form2=past-participle",
|
|
";;; Nouns: form0=singular form1=plural form2=REAL gender (m/f, from lexicon —",
|
|
";;; NOT an ending heuristic; this is what kills 'el mano'/'la día' errors)",
|
|
";;; Adjs : form0=masc-sg form1=fem-sg form2=masc-pl",
|
|
"",
|
|
"(vocabulary-es",
|
|
"",
|
|
" ;; -- function / closed class (incl. mandatory contractions del/al) --------",
|
|
]
|
|
for f in _FUNCTION:
|
|
lines.append(_row(f))
|
|
|
|
lines.append("")
|
|
lines.append(" ;; -- verbs (form0=pres-3sg form1=pret-3sg form2=past-participle) -----------")
|
|
for lem in verbs:
|
|
f0, c0 = M.conjugate(lem, "ind", "present", "third", "singular")
|
|
f1, c1 = M.conjugate(lem, "ind", "preterite", "third", "singular")
|
|
pp, cp = M.participle(lem)
|
|
vclass = lem[-2:] if lem[-2:] in ("ar", "er", "ir") else "ar"
|
|
irr = "irregular" if (c0 == "lexicon" and pp in M._IRREG_PART.values()) or \
|
|
lem in ("ser", "estar", "ir", "haber", "tener", "hacer", "ver",
|
|
"dar", "saber", "poder", "querer", "venir", "decir",
|
|
"poner", "salir") else "regular"
|
|
lines.append(_row([lem, "verb", f0, f1, pp, lem, vclass + "/" + irr]))
|
|
|
|
lines.append("")
|
|
lines.append(" ;; -- nouns (form0=sg form1=pl form2=REAL gender m/f) ----------------------")
|
|
for lem in nouns:
|
|
sg, _ = M.inflect_noun(lem, "singular")
|
|
pl, _ = M.inflect_noun(lem, "plural")
|
|
g = M.noun_gender(lem)
|
|
# honesty flag: did gender come from the lexicon, or a heuristic fallback?
|
|
src = "lexicon" if (lem in M._NOUNS and M._NOUNS[lem].get("g")) else "heuristic"
|
|
lines.append(_row([lem, "noun", sg, pl, g, lem, "gender:" + src]))
|
|
|
|
lines.append("")
|
|
lines.append(" ;; -- adjectives (form0=masc-sg form1=fem-sg form2=masc-pl) ----------------")
|
|
for lem in adjs:
|
|
m_sg, _ = M.inflect_adj(lem, "m", "singular")
|
|
f_sg, _ = M.inflect_adj(lem, "f", "singular")
|
|
m_pl, _ = M.inflect_adj(lem, "m", "plural")
|
|
src = "lexicon" if lem in M._ADJS else "rule"
|
|
lines.append(_row([lem, "adj", m_sg, f_sg, m_pl, lem, src]))
|
|
|
|
lines.append("")
|
|
lines.append(")")
|
|
with open(path, "w", encoding="utf-8") as fh:
|
|
fh.write("\n".join(lines) + "\n")
|
|
return len(_FUNCTION) + len(verbs) + len(nouns) + len(adjs), len(verbs), len(nouns), len(adjs)
|
|
|
|
|
|
LANG_PROFILE = ''';;; lang_profile_es.el — Spanish language profile for ELP.
|
|
;;; Keys the realizer's construction switches. Mirrors lang_profile_en / _pt.
|
|
|
|
(lang_profile_es
|
|
(language "Spanish")
|
|
(iso639 "es")
|
|
(family "Romance")
|
|
|
|
;; -- core typology flags -------------------------------------------------
|
|
(pro-drop yes) ; subjects routinely dropped; agreement carries person
|
|
(obligatory-subject no)
|
|
(grammatical-gender yes) ; m/f on every noun; article+adjective AGREE
|
|
(gender-source lexicon); REAL per-noun gender from UniMorph — NOT a heuristic
|
|
(do-support no)
|
|
(subject-aux-inversion no) ; questions by intonation/punctuation, not inversion
|
|
(question-strategy intonation)
|
|
(article-selection "el/la/los/las un/una/unos/unas")
|
|
(stressed-a-rule yes) ; fem sg noun in stressed a-/ha- takes el/un (el agua)
|
|
(adjective-position postnominal) ; default post; a few prenominal + apocope
|
|
(adjective-agreement "gender+number")
|
|
(question-punct inverted) ; opening ¿ ¡ required
|
|
|
|
;; -- MANDATORY CONTRACTIONS (coordinator quality bar) --------------------
|
|
(contractions ((de el "del") (a el "al")))
|
|
(contraction-mandatory yes) ; 'de el'/'a el' MUST surface as del/al
|
|
|
|
;; -- verb / aspect system ------------------------------------------------
|
|
(verb-classes (ar er ir))
|
|
(tenses (present preterite imperfect future conditional))
|
|
(moods (ind sbjv imp))
|
|
(finite-agreement "person+number (6 slots)")
|
|
(perfect-aux "haber") ; haber + past participle (invariant -o)
|
|
(progressive-aux "estar") ; estar + gerund
|
|
(passive-aux "ser") ; ser + participle (agrees) + por-agent
|
|
(copula-split "ser/estar") ; permanent vs stage-level
|
|
(future "infinitive + é/ás/á/emos/éis/án")
|
|
|
|
;; -- clitics / government ------------------------------------------------
|
|
(object-clitics yes) ; me te lo la le nos os los las; proclisis/enclisis
|
|
(clitic-order "se II I III (le+lo -> se lo)")
|
|
(enclisis "imperative/infinitive/gerund + accent repair (dá+me+lo->dámelo)")
|
|
(verb-prep-government yes) ; verbs select prep (protestar+contra, escapar+de)
|
|
|
|
;; -- SACRED safety bar (shared with en/pt) -------------------------------
|
|
(negation-faithful yes)) ; polarity never dropped/inverted; unplaceable -> FLAG
|
|
'''
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
voc_path = sys.argv[1] if len(sys.argv) > 1 else "vocabulary-es.el"
|
|
lp_path = sys.argv[2] if len(sys.argv) > 2 else "lang_profile_es.el"
|
|
total, nv, nn, na = emit_vocabulary(voc_path)
|
|
with open(lp_path, "w", encoding="utf-8") as fh:
|
|
fh.write(LANG_PROFILE)
|
|
print(f"wrote {voc_path} ({total} entries: {len(_FUNCTION)} fn, {nv} verbs, {nn} nouns, {na} adjs)")
|
|
print(f"wrote {lp_path}")
|
|
print("lexicon:", M.lexicon_stats())
|