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:
+83
-4
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user