elp(comprehend): ES/PT Romance parser path — SACRED polarity cross-lingual
Adds a deterministic Romance front-end to comprehend.el (parse_spec_romance), dispatched from parse_spec_lang for lang es/pt. English path untouched (byte-identical, regression gate still 5/5). Pro-drop aware clause skeleton (subject | neg | verb | object | PP), cross-lingual negation lexemes already SACRED. Romance telephone gate: polarity PRESERVED 6/6 and EXTRACTED 6/6 through parse->realize->re-parse for 3 ES + 3 PT sentences. Built bounded (elc rc=0 peak 24MB, cc rc=0). Named gaps (honest): ending-only verb detection misfires on prepositions (contra) and -a/-o nouns (menina); lemma recovery keeps surface form; the non-English realizer is a generic preverbal-negator skeleton so ES/PT surfaces are not byte-parity. Full paradigm inversion + Romance lexicon deferred.
This commit is contained in:
@@ -495,9 +495,372 @@ fn cp_join_range(toks: [String], a: Int, b: Int) -> String {
|
||||
return str_join(parts, " ")
|
||||
}
|
||||
|
||||
// ── ES / PT (Romance) closed-class + light morphology ─────────────────────────
|
||||
//
|
||||
// The spec is language-neutral; only the SURFACE analysis differs. This is the
|
||||
// deterministic Romance front-end: same clause skeleton (subject | neg | verb |
|
||||
// object | PP), pro-drop aware (a Spanish/Portuguese sentence may open with the
|
||||
// verb or a negator and carry no overt subject). SACRED polarity is already
|
||||
// cross-lingual in cp_is_negation (no / não / nunca / jamás / jamais / nada /
|
||||
// nadie / nem / nenhum / ninguém), so negation survives here for free.
|
||||
|
||||
fn cp_rom_det(w: String) -> Bool {
|
||||
if str_eq(w, "el") { return true }
|
||||
if str_eq(w, "la") { return true }
|
||||
if str_eq(w, "los") { return true }
|
||||
if str_eq(w, "las") { return true }
|
||||
if str_eq(w, "un") { return true }
|
||||
if str_eq(w, "una") { return true }
|
||||
if str_eq(w, "unos") { return true }
|
||||
if str_eq(w, "unas") { return true }
|
||||
if str_eq(w, "o") { return true }
|
||||
if str_eq(w, "os") { return true }
|
||||
if str_eq(w, "um") { return true }
|
||||
if str_eq(w, "uma") { return true }
|
||||
if str_eq(w, "uns") { return true }
|
||||
if str_eq(w, "umas") { return true }
|
||||
if str_eq(w, "mi") { return true }
|
||||
if str_eq(w, "tu") { return true }
|
||||
if str_eq(w, "su") { return true }
|
||||
if str_eq(w, "meu") { return true }
|
||||
if str_eq(w, "minha"){ return true }
|
||||
if str_eq(w, "seu") { return true }
|
||||
if str_eq(w, "sua") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Romance subject pronoun -> interlingua concept ("" if not a pronoun).
|
||||
fn cp_rom_pron(w: String) -> String {
|
||||
if str_eq(w, "yo") { return "i" }
|
||||
if str_eq(w, "eu") { return "i" }
|
||||
if str_eq(w, "tú") { return "you" }
|
||||
if str_eq(w, "você") { return "you" }
|
||||
if str_eq(w, "usted") { return "you" }
|
||||
if str_eq(w, "él") { return "he" }
|
||||
if str_eq(w, "ele") { return "he" }
|
||||
if str_eq(w, "ella") { return "she" }
|
||||
if str_eq(w, "ela") { return "she" }
|
||||
if str_eq(w, "nosotros") { return "we" }
|
||||
if str_eq(w, "nós") { return "we" }
|
||||
if str_eq(w, "ellos") { return "they" }
|
||||
if str_eq(w, "ellas") { return "they" }
|
||||
if str_eq(w, "eles") { return "they" }
|
||||
if str_eq(w, "elas") { return "they" }
|
||||
return ""
|
||||
}
|
||||
|
||||
// concept -> canonical Romance subject surface for the given language.
|
||||
fn cp_rom_pron_surface(concept: String, lang: String) -> String {
|
||||
if str_eq(lang, "pt") {
|
||||
if str_eq(concept, "i") { return "eu" }
|
||||
if str_eq(concept, "you") { return "você" }
|
||||
if str_eq(concept, "he") { return "ele" }
|
||||
if str_eq(concept, "she") { return "ela" }
|
||||
if str_eq(concept, "we") { return "nós" }
|
||||
if str_eq(concept, "they") { return "eles" }
|
||||
return concept
|
||||
}
|
||||
if str_eq(concept, "i") { return "yo" }
|
||||
if str_eq(concept, "you") { return "usted" }
|
||||
if str_eq(concept, "he") { return "él" }
|
||||
if str_eq(concept, "she") { return "ella" }
|
||||
if str_eq(concept, "we") { return "nosotros" }
|
||||
if str_eq(concept, "they") { return "ellos" }
|
||||
return concept
|
||||
}
|
||||
|
||||
fn cp_rom_prep(w: String) -> Bool {
|
||||
if str_eq(w, "en") { return true }
|
||||
if str_eq(w, "em") { return true }
|
||||
if str_eq(w, "a") { return true }
|
||||
if str_eq(w, "de") { return true }
|
||||
if str_eq(w, "del") { return true }
|
||||
if str_eq(w, "do") { return true }
|
||||
if str_eq(w, "da") { return true }
|
||||
if str_eq(w, "con") { return true }
|
||||
if str_eq(w, "com") { return true }
|
||||
if str_eq(w, "por") { return true }
|
||||
if str_eq(w, "para") { return true }
|
||||
if str_eq(w, "sin") { return true }
|
||||
if str_eq(w, "sem") { return true }
|
||||
if str_eq(w, "sobre") { return true }
|
||||
if str_eq(w, "entre") { return true }
|
||||
if str_eq(w, "desde") { return true }
|
||||
if str_eq(w, "hasta") { return true }
|
||||
if str_eq(w, "até") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Romance auxiliaries / copulas (ser/estar/haber/haver/ter/ir cores).
|
||||
fn cp_rom_aux(w: String) -> Bool {
|
||||
if str_eq(w, "es") { return true }
|
||||
if str_eq(w, "está") { return true }
|
||||
if str_eq(w, "son") { return true }
|
||||
if str_eq(w, "están") { return true }
|
||||
if str_eq(w, "era") { return true }
|
||||
if str_eq(w, "eran") { return true }
|
||||
if str_eq(w, "fue") { return true }
|
||||
if str_eq(w, "ha") { return true }
|
||||
if str_eq(w, "han") { return true }
|
||||
if str_eq(w, "va") { return true }
|
||||
if str_eq(w, "van") { return true }
|
||||
if str_eq(w, "é") { return true }
|
||||
if str_eq(w, "são") { return true }
|
||||
if str_eq(w, "foi") { return true }
|
||||
if str_eq(w, "tem") { return true }
|
||||
if str_eq(w, "têm") { return true }
|
||||
if str_eq(w, "vai") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// A plausible Romance finite verb by inflectional ending (boundary detector).
|
||||
fn cp_rom_is_verb(w: String) -> Bool {
|
||||
let n: Int = str_len(w)
|
||||
if n < 3 { return false }
|
||||
if str_ends_with(w, "ar") { return true }
|
||||
if str_ends_with(w, "er") { return true }
|
||||
if str_ends_with(w, "ir") { return true }
|
||||
if str_ends_with(w, "ó") { return true }
|
||||
if str_ends_with(w, "ió") { return true }
|
||||
if str_ends_with(w, "ou") { return true }
|
||||
if str_ends_with(w, "eu") { return true }
|
||||
if str_ends_with(w, "iu") { return true }
|
||||
if str_ends_with(w, "aba"){ return true }
|
||||
if str_ends_with(w, "ía") { return true }
|
||||
if str_ends_with(w, "ava"){ return true }
|
||||
if str_ends_with(w, "an") { return true }
|
||||
if str_ends_with(w, "en") { return true }
|
||||
if str_ends_with(w, "am") { return true }
|
||||
if str_ends_with(w, "em") { return true }
|
||||
if str_ends_with(w, "a") { return true }
|
||||
if str_ends_with(w, "e") { return true }
|
||||
if str_ends_with(w, "o") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Romance verb -> [lemma, tense]. Lemma recovery is best-effort (endings only,
|
||||
// no full paradigm inversion yet); tense is read from the inflection class.
|
||||
fn cp_rom_lemma_tense(w: String) -> [String] {
|
||||
let n: Int = str_len(w)
|
||||
let tense: String = "present"
|
||||
if str_ends_with(w, "ó") { let tense = "past" }
|
||||
if str_ends_with(w, "ió") { let tense = "past" }
|
||||
if str_ends_with(w, "ou") { let tense = "past" }
|
||||
if str_ends_with(w, "eu") { let tense = "past" }
|
||||
if str_ends_with(w, "iu") { let tense = "past" }
|
||||
if str_ends_with(w, "aba") { let tense = "past" }
|
||||
if str_ends_with(w, "ía") { let tense = "past" }
|
||||
if str_ends_with(w, "ava") { let tense = "past" }
|
||||
if str_ends_with(w, "rá") { let tense = "future" }
|
||||
if str_ends_with(w, "rão") { let tense = "future" }
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, w)
|
||||
let r = native_list_append(r, tense)
|
||||
return r
|
||||
}
|
||||
|
||||
// Index where the Romance verb cluster begins (end of the subject NP), in [0,end).
|
||||
fn cp_rom_verb_start(toks: [String], end: Int) -> Int {
|
||||
if end == 0 { return 0 }
|
||||
let first: String = native_list_get(toks, 0)
|
||||
// pro-drop / verb-initial / negator-initial: no overt subject NP.
|
||||
if cp_is_negation(first) { return 0 }
|
||||
if !str_eq(cp_rom_pron(first), "") { return 1 }
|
||||
if cp_rom_is_verb(first) { return 0 }
|
||||
let have_head: Bool = false
|
||||
let i: Int = 0
|
||||
while i < end {
|
||||
let w: String = native_list_get(toks, i)
|
||||
if i > 0 {
|
||||
if cp_is_negation(w) { return i }
|
||||
if cp_rom_aux(w) { return i }
|
||||
if have_head {
|
||||
if cp_rom_is_verb(w) { return i }
|
||||
}
|
||||
}
|
||||
if !cp_rom_det(w) { let have_head = true }
|
||||
let i = i + 1
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
fn parse_spec_romance(text: String, lang: String) -> [String] {
|
||||
let toks: [String] = cp_tokenize(text)
|
||||
let n: Int = native_list_len(toks)
|
||||
|
||||
let intent: String = "assert"
|
||||
if str_ends_with(text, "?") { let intent = "question" }
|
||||
let m_end: Int = cp_subord_start(toks, n)
|
||||
|
||||
// SACRED polarity — cross-lingual negation lexemes in the main clause.
|
||||
let polarity: String = "aff"
|
||||
let neg_word: String = ""
|
||||
let pi: Int = 0
|
||||
while pi < m_end {
|
||||
let w: String = native_list_get(toks, pi)
|
||||
if cp_is_negation(w) {
|
||||
let polarity = "neg"
|
||||
if cp_is_neg_adverb(w) { let neg_word = w }
|
||||
}
|
||||
let pi = pi + 1
|
||||
}
|
||||
|
||||
// subject (pro-drop aware)
|
||||
let vstart: Int = cp_rom_verb_start(toks, m_end)
|
||||
let agent: String = ""
|
||||
let first: String = ""
|
||||
if n > 0 { let first = native_list_get(toks, 0) }
|
||||
if vstart == 0 {
|
||||
let agent = ""
|
||||
} else {
|
||||
if vstart == 1 {
|
||||
if !str_eq(cp_rom_pron(first), "") {
|
||||
let agent = cp_rom_pron_surface(cp_rom_pron(first), lang)
|
||||
} else {
|
||||
let agent = cp_join_range(toks, 0, 1)
|
||||
}
|
||||
} else {
|
||||
let agent = cp_join_range(toks, 0, vstart)
|
||||
}
|
||||
}
|
||||
|
||||
// verb cluster
|
||||
let tense: String = "present"
|
||||
let aspect: String = "simple"
|
||||
let predicate: String = ""
|
||||
let found_verb: Bool = false
|
||||
let i: Int = vstart
|
||||
while i < m_end {
|
||||
let w: String = native_list_get(toks, i)
|
||||
if found_verb {
|
||||
let i = m_end
|
||||
} else {
|
||||
if cp_is_negation(w) {
|
||||
let i = i + 1
|
||||
} else {
|
||||
if cp_rom_aux(w) {
|
||||
if str_eq(w, "era") { let tense = "past" }
|
||||
if str_eq(w, "eran") { let tense = "past" }
|
||||
if str_eq(w, "fue") { let tense = "past" }
|
||||
if str_eq(w, "foi") { let tense = "past" }
|
||||
if str_eq(w, "ha") { let aspect = "perfect" }
|
||||
if str_eq(w, "han") { let aspect = "perfect" }
|
||||
if str_eq(w, "va") { let tense = "future" }
|
||||
if str_eq(w, "van") { let tense = "future" }
|
||||
if str_eq(w, "vai") { let tense = "future" }
|
||||
// a bare copula with no following lexical verb is the predicate
|
||||
let predicate = "be"
|
||||
let i = i + 1
|
||||
} else {
|
||||
if cp_rom_is_verb(w) {
|
||||
let lt: [String] = cp_rom_lemma_tense(w)
|
||||
let predicate = native_list_get(lt, 0)
|
||||
let tense = native_list_get(lt, 1)
|
||||
let found_verb = true
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if str_eq(predicate, "be") { let found_verb = true }
|
||||
|
||||
// complements: first NP after the verb is the object; "a" -> recipient (iobj);
|
||||
// other prepositions -> location adjunct.
|
||||
let cstart: Int = vstart
|
||||
let seen_v: Bool = false
|
||||
let j: Int = vstart
|
||||
while j < m_end {
|
||||
let w: String = native_list_get(toks, j)
|
||||
if seen_v {
|
||||
let cstart = j
|
||||
let j = m_end
|
||||
} else {
|
||||
if cp_is_negation(w) { let j = j + 1 }
|
||||
else {
|
||||
if cp_rom_aux(w) { let j = j + 1 }
|
||||
else {
|
||||
if cp_rom_is_verb(w) { let seen_v = true }
|
||||
let j = j + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let patient: String = ""
|
||||
let iobj: String = ""
|
||||
let location: String = ""
|
||||
let k: Int = cstart
|
||||
while k < m_end {
|
||||
let w: String = native_list_get(toks, k)
|
||||
if cp_rom_prep(w) {
|
||||
let prep: String = w
|
||||
let a: Int = k + 1
|
||||
let b: Int = a
|
||||
let run3: Bool = true
|
||||
while run3 {
|
||||
if b >= m_end { let run3 = false }
|
||||
else {
|
||||
if cp_rom_prep(native_list_get(toks, b)) { let run3 = false }
|
||||
else { let b = b + 1 }
|
||||
}
|
||||
}
|
||||
let np: String = cp_join_range(toks, a, b)
|
||||
if str_eq(prep, "a") {
|
||||
let iobj = np
|
||||
} else {
|
||||
if str_eq(location, "") { let location = prep + " " + np }
|
||||
}
|
||||
let k = b
|
||||
} else {
|
||||
let a: Int = k
|
||||
let b: Int = a
|
||||
let run4: Bool = true
|
||||
while run4 {
|
||||
if b >= m_end { let run4 = false }
|
||||
else {
|
||||
if cp_rom_prep(native_list_get(toks, b)) { let run4 = false }
|
||||
else { let b = b + 1 }
|
||||
}
|
||||
}
|
||||
if str_eq(patient, "") { let patient = cp_join_range(toks, a, b) }
|
||||
let k = b
|
||||
}
|
||||
}
|
||||
|
||||
// subordinate clause conj (predicate recovery is left to a later pass)
|
||||
let subord_conj: String = ""
|
||||
let subord_pred: String = ""
|
||||
if m_end < n { let subord_conj = native_list_get(toks, m_end) }
|
||||
|
||||
if str_eq(agent, "") {
|
||||
if found_verb {
|
||||
if str_eq(intent, "assert") { let intent = "command" }
|
||||
}
|
||||
}
|
||||
|
||||
let spec: [String] = native_list_empty()
|
||||
let spec = native_list_append(spec, "intent"); let spec = native_list_append(spec, intent)
|
||||
let spec = native_list_append(spec, "agent"); let spec = native_list_append(spec, agent)
|
||||
let spec = native_list_append(spec, "predicate"); let spec = native_list_append(spec, predicate)
|
||||
let spec = native_list_append(spec, "patient"); let spec = native_list_append(spec, patient)
|
||||
let spec = native_list_append(spec, "iobj"); let spec = native_list_append(spec, iobj)
|
||||
let spec = native_list_append(spec, "location"); let spec = native_list_append(spec, location)
|
||||
let spec = native_list_append(spec, "tense"); let spec = native_list_append(spec, tense)
|
||||
let spec = native_list_append(spec, "aspect"); let spec = native_list_append(spec, aspect)
|
||||
let spec = native_list_append(spec, "polarity"); let spec = native_list_append(spec, polarity)
|
||||
let spec = native_list_append(spec, "neg_word"); let spec = native_list_append(spec, neg_word)
|
||||
let spec = native_list_append(spec, "subord_conj"); let spec = native_list_append(spec, subord_conj)
|
||||
let spec = native_list_append(spec, "subord_pred"); let spec = native_list_append(spec, subord_pred)
|
||||
let spec = native_list_append(spec, "lang"); let spec = native_list_append(spec, lang)
|
||||
return spec
|
||||
}
|
||||
|
||||
// ── the parser ────────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_spec_lang(text: String, lang: String) -> [String] {
|
||||
if str_eq(lang, "es") { return parse_spec_romance(text, lang) }
|
||||
if str_eq(lang, "pt") { return parse_spec_romance(text, lang) }
|
||||
let toks: [String] = cp_tokenize(text)
|
||||
let n: Int = native_list_len(toks)
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// comprehend_romance_gate.el - ES / PT native telephone test (SACRED polarity).
|
||||
//
|
||||
// The spec is language-neutral. This gate proves the Romance front-end extracts
|
||||
// SACRED polarity correctly and that negation survives parse -> realize ->
|
||||
// re-parse for Spanish and Portuguese (byte-parity of the surface is NOT expected
|
||||
// yet — the non-English realizer path is a generic preverbal-negator skeleton).
|
||||
|
||||
fn rg_line(text: String, lang: String, expected_pol: String) -> String {
|
||||
let spec: [String] = parse_spec_lang(text, lang)
|
||||
let pol_in: String = slots_get(spec, "polarity")
|
||||
let surf: String = realize(spec)
|
||||
let spec2: [String] = parse_spec_lang(surf, lang)
|
||||
let pol_out: String = slots_get(spec2, "polarity")
|
||||
let status: String = "LOST"
|
||||
if str_eq(pol_in, pol_out) { let status = "PRESERVED" }
|
||||
let okexp: String = "MISMATCH"
|
||||
if str_eq(pol_in, expected_pol) { let okexp = "ok" }
|
||||
let out: String = "IN[" + lang + "]: " + text + "\n"
|
||||
let out = out + " spec: pol=" + pol_in + " pred=" + slots_get(spec, "predicate")
|
||||
let out = out + " agent=" + slots_get(spec, "agent")
|
||||
let out = out + " pat=" + slots_get(spec, "patient")
|
||||
let out = out + " iobj=" + slots_get(spec, "iobj")
|
||||
let out = out + " loc=" + slots_get(spec, "location")
|
||||
let out = out + " tense=" + slots_get(spec, "tense") + "\n"
|
||||
let out = out + " realized: " + surf + "\n"
|
||||
let out = out + " reparse: pol=" + pol_out + " [" + status + "] expected=" + expected_pol + " (" + okexp + ")\n"
|
||||
return out
|
||||
}
|
||||
|
||||
fn rg_pres(text: String, lang: String) -> Int {
|
||||
let spec: [String] = parse_spec_lang(text, lang)
|
||||
let surf: String = realize(spec)
|
||||
let spec2: [String] = parse_spec_lang(surf, lang)
|
||||
if str_eq(slots_get(spec, "polarity"), slots_get(spec2, "polarity")) { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
fn rg_corr(text: String, lang: String, expected_pol: String) -> Int {
|
||||
let spec: [String] = parse_spec_lang(text, lang)
|
||||
if str_eq(slots_get(spec, "polarity"), expected_pol) { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
fn run_romance_gate() -> String {
|
||||
let e1: String = "El niño no comió el pescado."
|
||||
let e2: String = "Yo nunca luché contra el océano."
|
||||
let e3: String = "El profesor lee el libro."
|
||||
let p1: String = "O professor não leu o livro."
|
||||
let p2: String = "Eu nunca lutei contra o oceano."
|
||||
let p3: String = "A menina comeu o peixe."
|
||||
|
||||
let rep: String = "==== ELP Romance telephone test (ES / PT) ====\n"
|
||||
let rep = rep + rg_line(e1, "es", "neg")
|
||||
let rep = rep + rg_line(e2, "es", "neg")
|
||||
let rep = rep + rg_line(e3, "es", "aff")
|
||||
let rep = rep + rg_line(p1, "pt", "neg")
|
||||
let rep = rep + rg_line(p2, "pt", "neg")
|
||||
let rep = rep + rg_line(p3, "pt", "aff")
|
||||
|
||||
let pres: Int = 0
|
||||
if rg_pres(e1, "es") == 1 { let pres = pres + 1 }
|
||||
if rg_pres(e2, "es") == 1 { let pres = pres + 1 }
|
||||
if rg_pres(e3, "es") == 1 { let pres = pres + 1 }
|
||||
if rg_pres(p1, "pt") == 1 { let pres = pres + 1 }
|
||||
if rg_pres(p2, "pt") == 1 { let pres = pres + 1 }
|
||||
if rg_pres(p3, "pt") == 1 { let pres = pres + 1 }
|
||||
let corr: Int = 0
|
||||
if rg_corr(e1, "es", "neg") == 1 { let corr = corr + 1 }
|
||||
if rg_corr(e2, "es", "neg") == 1 { let corr = corr + 1 }
|
||||
if rg_corr(e3, "es", "aff") == 1 { let corr = corr + 1 }
|
||||
if rg_corr(p1, "pt", "neg") == 1 { let corr = corr + 1 }
|
||||
if rg_corr(p2, "pt", "neg") == 1 { let corr = corr + 1 }
|
||||
if rg_corr(p3, "pt", "aff") == 1 { let corr = corr + 1 }
|
||||
|
||||
let rep = rep + "-----------------------------------------------------------------\n"
|
||||
let rep = rep + "polarity PRESERVED through round-trip: " + int_to_str(pres) + "/6\n"
|
||||
let rep = rep + "polarity EXTRACTED correctly: " + int_to_str(corr) + "/6\n"
|
||||
if pres == 6 {
|
||||
if corr == 6 { let rep = rep + "ROMANCE GATE: PASS\n" }
|
||||
else { let rep = rep + "ROMANCE GATE: FAIL (extraction)\n" }
|
||||
} else {
|
||||
let rep = rep + "ROMANCE GATE: FAIL (round-trip)\n"
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
println(run_romance_gate())
|
||||
Reference in New Issue
Block a user