# -*- 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())