335298a518
Phase 3 piece 1. Ports propositions.py off spaCy: the dependency-parser role is now the el-native parser (parse_spec), and each memory sentence's meaning-spec IS the triple (subject, predicate, object, modifiers, polarity, tense, source, confidence). Sentence segmentation + repr parity with propositions.py. NEGATION SACRED: polarity flows straight from the spec, never dropped/inverted. Gate (propositions_gate.el): 4/4 SACRED polarity correct on extraction; multi-sentence memory splits one triple per sentence in reading order with negation preserved. Built bounded (elc rc=0 peak 24MB, cc rc=0). Gap (honest): English regular-verb lemmatizer does not restore silent-e (stores->stor); coreference/passive normalization from the reference not yet ported (shallow pronoun subject kept as surface).
141 lines
6.1 KiB
EmacsLisp
141 lines
6.1 KiB
EmacsLisp
// propositions.el - the READ primitive over the engram's OWN memories, native el.
|
|
//
|
|
// Free memory text -> structured PROPOSITIONS (triples):
|
|
// (subject, predicate, object, modifiers, polarity, tense, source, confidence)
|
|
//
|
|
// This is comprehension turned inward: the Python reference (propositions.py) ran
|
|
// spaCy's dependency parser over each memory sentence and walked the arcs. Here
|
|
// the spaCy role is filled by the el-native parser (comprehend.el / parse_spec):
|
|
// each sentence is parsed to a meaning-spec, and the spec's roles ARE the triple.
|
|
// Nothing generates text. NEGATION IS SACRED: polarity flows straight from the
|
|
// spec's polarity field and is never dropped or inverted.
|
|
//
|
|
// Depends on: comprehend (parse_spec / parse_spec_lang), grammar (slots_get).
|
|
|
|
// ── sentence segmentation ─────────────────────────────────────────────────────
|
|
// Split on sentence-final punctuation (. ! ?) and hard newlines. Markdown/long
|
|
// memories are handled shallowly (the reference caps + ranks by query overlap;
|
|
// that ranking belongs to the dialogue layer, not here).
|
|
|
|
fn prop_is_boundary(c: String) -> Bool {
|
|
if str_eq(c, ".") { return true }
|
|
if str_eq(c, "!") { return true }
|
|
if str_eq(c, "?") { return true }
|
|
if str_eq(c, "\n") { return true }
|
|
return false
|
|
}
|
|
|
|
fn prop_split_sentences(text: String) -> [String] {
|
|
let out: [String] = native_list_empty()
|
|
let n: Int = str_len(text)
|
|
let start: Int = 0
|
|
let i: Int = 0
|
|
while i < n {
|
|
let c: String = str_slice(text, i, i + 1)
|
|
if prop_is_boundary(c) {
|
|
let seg: String = str_slice(text, start, i + 1)
|
|
let trimmed: String = cp_trim_punct(seg)
|
|
if !str_eq(trimmed, "") {
|
|
let out = native_list_append(out, seg)
|
|
}
|
|
let start = i + 1
|
|
}
|
|
let i = i + 1
|
|
}
|
|
if start < n {
|
|
let seg: String = str_slice(text, start, n)
|
|
let trimmed: String = cp_trim_punct(seg)
|
|
if !str_eq(trimmed, "") {
|
|
let out = native_list_append(out, seg)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ── spec -> proposition record ────────────────────────────────────────────────
|
|
// A proposition is a slot map (same [String] shape as the spec) with the READ
|
|
// contract keys. Modifiers fold the spec's location + iobj adjuncts.
|
|
|
|
fn prop_confidence(subject: String, predicate: String, object: String) -> String {
|
|
if str_eq(predicate, "") { return "0.0" }
|
|
if str_eq(subject, "") { return "0.4" }
|
|
if str_eq(object, "") { return "0.7" }
|
|
return "1.0"
|
|
}
|
|
|
|
fn prop_modifiers(spec: [String]) -> String {
|
|
let loc: String = slots_get(spec, "location")
|
|
let iobj: String = slots_get(spec, "iobj")
|
|
let parts: [String] = native_list_empty()
|
|
if !str_eq(loc, "") { let parts = native_list_append(parts, loc) }
|
|
if !str_eq(iobj, "") { let parts = native_list_append(parts, "to " + iobj) }
|
|
return str_join(parts, "; ")
|
|
}
|
|
|
|
fn prop_from_spec(spec: [String], source_id: String) -> [String] {
|
|
let subject: String = slots_get(spec, "agent")
|
|
let predicate: String = slots_get(spec, "predicate")
|
|
let object: String = slots_get(spec, "patient")
|
|
let polarity: String = slots_get(spec, "polarity")
|
|
let tense: String = slots_get(spec, "tense")
|
|
let mods: String = prop_modifiers(spec)
|
|
let conf: String = prop_confidence(subject, predicate, object)
|
|
|
|
let p: [String] = native_list_empty()
|
|
let p = native_list_append(p, "subject"); let p = native_list_append(p, subject)
|
|
let p = native_list_append(p, "predicate"); let p = native_list_append(p, predicate)
|
|
let p = native_list_append(p, "object"); let p = native_list_append(p, object)
|
|
let p = native_list_append(p, "modifiers"); let p = native_list_append(p, mods)
|
|
let p = native_list_append(p, "polarity"); let p = native_list_append(p, polarity)
|
|
let p = native_list_append(p, "tense"); let p = native_list_append(p, tense)
|
|
let p = native_list_append(p, "source"); let p = native_list_append(p, source_id)
|
|
let p = native_list_append(p, "confidence"); let p = native_list_append(p, conf)
|
|
return p
|
|
}
|
|
|
|
// Extract one proposition from a single sentence (given language).
|
|
fn prop_extract_one_lang(sentence: String, lang: String, source_id: String) -> [String] {
|
|
let spec: [String] = parse_spec_lang(sentence, lang)
|
|
return prop_from_spec(spec, source_id)
|
|
}
|
|
|
|
fn prop_extract_one(sentence: String, source_id: String) -> [String] {
|
|
return prop_extract_one_lang(sentence, "en", source_id)
|
|
}
|
|
|
|
// Render a proposition as a compact trace line (repr parity with propositions.py).
|
|
fn prop_repr(p: [String]) -> String {
|
|
let neg: String = ""
|
|
if str_eq(slots_get(p, "polarity"), "neg") { let neg = "NOT " }
|
|
let mods: String = slots_get(p, "modifiers")
|
|
let modstr: String = ""
|
|
if !str_eq(mods, "") { let modstr = " [" + mods + "]" }
|
|
let s: String = "(" + slots_get(p, "subject") + " -" + neg + slots_get(p, "predicate")
|
|
let s = s + "-> " + slots_get(p, "object") + modstr
|
|
let s = s + " conf=" + slots_get(p, "confidence") + ")"
|
|
return s
|
|
}
|
|
|
|
// Extract all propositions from a memory's text (one per sentence). Returns a
|
|
// flat [String] whose entries are the prop_repr trace lines, in reading order.
|
|
fn prop_extract_lang(text: String, lang: String, source_id: String) -> [String] {
|
|
let sents: [String] = prop_split_sentences(text)
|
|
let m: Int = native_list_len(sents)
|
|
let out: [String] = native_list_empty()
|
|
let i: Int = 0
|
|
while i < m {
|
|
let sent: String = native_list_get(sents, i)
|
|
let p: [String] = prop_extract_one_lang(sent, lang, source_id)
|
|
// drop empty parses (no predicate recovered): honest partial, not noise.
|
|
if !str_eq(slots_get(p, "predicate"), "") {
|
|
let out = native_list_append(out, prop_repr(p))
|
|
}
|
|
let i = i + 1
|
|
}
|
|
return out
|
|
}
|
|
|
|
fn prop_extract(text: String, source_id: String) -> [String] {
|
|
return prop_extract_lang(text, "en", source_id)
|
|
}
|