elp(realizer): close subordinate-clause round-trip + silent-e/doubling lemmatizer + verb-final object bug + ES/PT closed-class verb guard

- realizer now carries the subordinate clause verbatim (subord_text slot): the
  5th English acceptance sentence is byte-identical through parse->realize->reparse.
- English -ed/-ing lemmatizer restores silent-e (loved->love) and collapses
  inflectional doubling (stopped->stop), inverting en_verb_past().
- parse_spec_lang: a sentence-final main verb no longer bleeds into the object
  slot (cstart advanced to verb+1), so intransitives round-trip.
- cp_rom_is_verb rejects closed-class words (prep/det/pron/aux/neg) before the
  ending-only test, killing the 'para'/determiner misfires.

EN telephone gate 5/5 (now byte-identical 5/5), Romance gate 6/6.
This commit is contained in:
2026-08-13 15:39:49 -05:00
parent c5508372ca
commit 0ae33c0f3b
2 changed files with 100 additions and 4 deletions
+83 -4
View File
@@ -330,12 +330,71 @@ fn cp_irr2(surface: String) -> [String] {
}
// cp_reg_verb(surface) -> [lemma, tense, aspect] by suffix stripping (regular).
// English silent-e / consonant-doubling inverse (past-participle lemmatizer)
//
// Stripping "-ed" naively over-truncates two regular classes:
// silent-e verbs ("loved" -> "lov" instead of "love")
// doubled-final ("stopped" -> "stopp" instead of "stop")
// These helpers invert en_verb_past() so the round-trip closes. The rules are
// the safe, high-confidence subset; residual orthographic edge cases (e.g. bare
// "ebb"/"arc") are rare and left to the runtime vocabulary confirmer.
fn cp_e_vowel(c: String) -> Bool {
if str_eq(c, "a") { return true }
if str_eq(c, "e") { return true }
if str_eq(c, "i") { return true }
if str_eq(c, "o") { return true }
if str_eq(c, "u") { return true }
return false
}
// consonants that double under inflection (CVC rule): b d g l m n p r t
fn cp_e_doubling(c: String) -> Bool {
if str_eq(c, "b") { return true }
if str_eq(c, "d") { return true }
if str_eq(c, "g") { return true }
if str_eq(c, "l") { return true }
if str_eq(c, "m") { return true }
if str_eq(c, "n") { return true }
if str_eq(c, "p") { return true }
if str_eq(c, "r") { return true }
if str_eq(c, "t") { return true }
return false
}
// restore the base after "-ed"/"-ing" stripping produced `stem`.
fn cp_e_restore(stem: String) -> String {
let m: Int = str_len(stem)
if m < 2 { return stem }
let last: String = str_slice(stem, m - 1, m)
let prev: String = str_slice(stem, m - 2, m - 1)
// inflectional doubled consonant -> collapse (stopped->stop, planned->plan)
if str_eq(last, prev) {
if cp_e_doubling(last) { return str_slice(stem, 0, m - 1) }
}
// silent-e restoration
if str_eq(last, "v") { return stem + "e" }
if str_eq(last, "u") { return stem + "e" }
let ctx: Bool = false
if cp_e_vowel(prev) { let ctx = true }
if str_eq(prev, "l") { let ctx = true }
if str_eq(prev, "r") { let ctx = true }
if str_eq(prev, "n") { let ctx = true }
if ctx {
if str_eq(last, "c") { return stem + "e" }
if str_eq(last, "g") { return stem + "e" }
if str_eq(last, "s") { return stem + "e" }
if str_eq(last, "z") { return stem + "e" }
}
return stem
}
fn cp_reg_verb(w: String) -> [String] {
let n: Int = str_len(w)
let r: [String] = native_list_empty()
if str_ends_with(w, "ing") {
if n > 4 {
let lemma: String = str_slice(w, 0, n - 3)
let lemma: String = cp_e_restore(str_slice(w, 0, n - 3))
let r = native_list_append(r, lemma)
let r = native_list_append(r, "present")
let r = native_list_append(r, "progressive")
@@ -351,7 +410,7 @@ fn cp_reg_verb(w: String) -> [String] {
}
if str_ends_with(w, "ed") {
if n > 2 {
let lemma: String = str_slice(w, 0, n - 2)
let lemma: String = cp_e_restore(str_slice(w, 0, n - 2))
let r = native_list_append(r, lemma)
let r = native_list_append(r, "past")
let r = native_list_append(r, "simple")
@@ -617,6 +676,15 @@ fn cp_rom_aux(w: String) -> Bool {
fn cp_rom_is_verb(w: String) -> Bool {
let n: Int = str_len(w)
if n < 3 { return false }
// Closed-class words are never finite verbs reject them before the
// (ambiguous) inflectional-ending test. This kills the ending-only
// misfires on prepositions ("para", "sobre", "entre", "desde", "hasta")
// and on determiners / subject pronouns that happen to end in -a/-e/-o.
if cp_rom_prep(w) { return false }
if cp_rom_det(w) { return false }
if cp_rom_aux(w) { return false }
if cp_is_negation(w) { return false }
if !str_eq(cp_rom_pron(w), "") { 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 }
@@ -969,7 +1037,6 @@ fn parse_spec_lang(text: String, lang: String) -> [String] {
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) {
@@ -978,8 +1045,12 @@ fn parse_spec_lang(text: String, lang: String) -> [String] {
if cp_is_aux(w) {
let j = j + 1
} else {
// main verb consumed complements begin at the NEXT token.
// (Setting cstart here, not on a later iteration, is what makes
// a sentence-final verb yield no phantom object.)
let seen_v = true
let j = j + 1
let cstart = j + 1
let j = m_end
}
}
}
@@ -1044,10 +1115,17 @@ fn parse_spec_lang(text: String, lang: String) -> [String] {
}
// subordinate clause: conj + (lightweight) predicate recovery.
// subord_conj the subordinating conjunction ("because", "if", ...)
// subord_pred the recovered predicate lemma of the subordinate clause
// subord_text the SURFACE of the subordinate clause AFTER the conjunction,
// carried verbatim so realization is byte-complete (the clause
// is preserved, not dropped SACRED completeness of meaning).
let subord_conj: String = ""
let subord_pred: String = ""
let subord_text: String = ""
if m_end < n {
let subord_conj = native_list_get(toks, m_end)
let subord_text = cp_join_range(toks, m_end + 1, n)
// find the subordinate predicate: first aux/verb after the conjunction.
let s: Int = m_end + 1
let sfound: Bool = false
@@ -1095,6 +1173,7 @@ fn parse_spec_lang(text: String, lang: String) -> [String] {
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, "subord_text"); let spec = native_list_append(spec, subord_text)
let spec = native_list_append(spec, "lang"); let spec = native_list_append(spec, lang)
return spec
}
+17
View File
@@ -339,6 +339,19 @@ fn realize_lang(form: [String], profile: [String]) -> String {
let iobj: String = slots_get(form, "iobj")
let code: String = lang_get(profile, "code")
// Subordinate clause tail (SACRED completeness the clause is carried, never
// dropped): "<conj> <subordinate surface>", e.g. "because he was a monster".
let subord_conj: String = slots_get(form, "subord_conj")
let subord_text: String = slots_get(form, "subord_text")
let subord_tail: String = ""
if !str_eq(subord_conj, "") {
if !str_eq(subord_text, "") {
let subord_tail = subord_conj + " " + subord_text
} else {
let subord_tail = subord_conj
}
}
// Negative polarity: SACRED never dropped.
if str_eq(polarity, "neg") {
if str_eq(code, "en") {
@@ -359,6 +372,7 @@ fn realize_lang(form: [String], profile: [String]) -> String {
let parts = native_list_append(parts, iobj)
}
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
if !str_eq(subord_tail, "") { let parts = native_list_append(parts, subord_tail) }
let sentence: String = str_join(parts, " ")
return add_punct(capitalize_first(sentence), "assert")
}
@@ -380,6 +394,9 @@ fn realize_lang(form: [String], profile: [String]) -> String {
if !str_eq(location, "") {
let parts = native_list_append(parts, location)
}
if !str_eq(subord_tail, "") {
let parts = native_list_append(parts, subord_tail)
}
let sentence: String = str_join(parts, " ")
return add_punct(capitalize_first(sentence), "assert")
}