Files
el/elp/tests/gen_parity_es.py
T
claude c62ea3383c stage(elp-es): emit vocabulary-es.el + lang_profile_es.el; extend morphology-es.el; parity harness
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%.
2026-08-13 09:49:20 -05:00

101 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
"""gen_parity_es.py — emit an El parity program that checks the .el Spanish
morphology against the validated Python (UniMorph-backed) realizer.
Python is the ORACLE. For every lemma in the held-out inventory we embed the
Python-produced form, call the corresponding .el function, and the El program
prints PASS/FAIL per category. Aggregation is done in bash (grep -c), so no
El-side mutable counters are needed.
Categories:
Vpres verb present-ind-3sg es_conjugate(v,present,third,singular)
Vpret verb preterite-3sg es_conjugate(v,past,third,singular)
Vpart past participle es_participle(v)
Npl noun plural es_pluralize(n)
Gheur noun gender HEURISTIC es_gender(n) [exposes the bug]
Aheur def article via heuristic es_agree_article(n,true,sg) [inherits the bug]
Avocab def article via REAL g es_article_for_gender(realg,...) [the fix]
Ampl adj masc-plural es_inflect_adj(a,m,plural)
Afsg adj fem-singular es_inflect_adj(a,f,singular)
Ctr contraction del/al es_contract(prep, np)
"""
import morphology_es_full as M
import realizer_es as R
from gen_elp_es import _collect_from_specs, _CORE_VERBS, _CORE_NOUNS, _CORE_ADJS
def _esc(s):
return str(s).replace('\\', '\\\\').replace('"', '\\"')
def _check(cat, lemma, el_call, expected):
return (f' es_check("{cat}", "{_esc(lemma)}", {el_call}, "{_esc(expected)}")')
def main(out_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 = []
lines.append("// gen'd parity checks — Python oracle embedded, El functions called.")
lines.append("fn es_check(cat: String, lemma: String, got: String, exp: String) {")
lines.append(' if str_eq(got, exp) {')
lines.append(' println("PASS " + cat)')
lines.append(' } else {')
lines.append(' println("FAIL " + cat + " " + lemma + " got=" + got + " exp=" + exp)')
lines.append(' }')
lines.append("}")
lines.append("")
lines.append("fn es_parity() {")
# verbs
for v in verbs:
p0 = M.conjugate(v, "ind", "present", "third", "singular")[0]
p1 = M.conjugate(v, "ind", "preterite", "third", "singular")[0]
pp = M.participle(v)[0]
lines.append(_check("Vpres", v, f'es_conjugate("{_esc(v)}", "present", "third", "singular")', p0))
lines.append(_check("Vpret", v, f'es_conjugate("{_esc(v)}", "past", "third", "singular")', p1))
lines.append(_check("Vpart", v, f'es_participle("{_esc(v)}")', pp))
# nouns
for n in nouns:
pl = M.inflect_noun(n, "plural")[0]
g = M.noun_gender(n) # REAL lexicon gender
art = R._article(g, "singular", "def", n) # oracle article from real gender
lines.append(_check("Npl", n, f'es_pluralize("{_esc(n)}")', pl))
lines.append(_check("Gheur", n, f'es_gender("{_esc(n)}")', g))
lines.append(_check("Aheur", n, f'es_agree_article("{_esc(n)}", "true", "singular")', art))
lines.append(_check("Avocab", n, f'es_article_for_gender("{_esc(g)}", "{_esc(n)}", "true", "singular")', art))
# adjectives
for a in adjs:
mpl = M.inflect_adj(a, "m", "plural")[0]
fsg = M.inflect_adj(a, "f", "singular")[0]
lines.append(_check("Ampl", a, f'es_inflect_adj("{_esc(a)}", "m", "plural")', mpl))
lines.append(_check("Afsg", a, f'es_inflect_adj("{_esc(a)}", "f", "singular")', fsg))
# contractions (mandatory)
ctr_cases = [("de", "el día"), ("a", "el hombre"), ("de", "el mundo"),
("a", "el país"), ("en", "el mar"), ("de", "el año"),
("a", "la casa"), ("de", "la ciudad")]
for prep, np in ctr_cases:
exp = R._contract(prep, np)
lines.append(_check("Ctr", prep + "+" + np, f'es_contract("{_esc(prep)}", "{_esc(np)}")', exp))
lines.append("}")
lines.append("")
lines.append("fn main() {")
lines.append(" es_parity()")
lines.append("}")
with open(out_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines) + "\n")
print(f"wrote {out_path} ({len(verbs)} verbs, {len(nouns)} nouns, {len(adjs)} adjs)")
if __name__ == "__main__":
import sys
main(sys.argv[1] if len(sys.argv) > 1 else "parity_es_checks.el")