Rename: nlg → elp (Engram Language Protocol)

This commit is contained in:
Will Anderson
2026-05-02 22:15:25 -05:00
parent cbb27b8d87
commit 34725a3988
125 changed files with 129214 additions and 780 deletions
+193 -144
View File
@@ -1,33 +1,32 @@
// realizer.el - Syntactic realizer: semantic form -> English text.
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
//
// Takes a SemanticForm (represented as a [String] slot map) and produces
// a well-formed English sentence, handling:
// - verb conjugation (tense, aspect, person/number agreement)
// - do-support for questions
// - progressive and perfect aspect periphrasis
// - determiner-noun agreement (a/an)
// - capitalization and final punctuation
// The realizer is now language-agnostic. It reads the "lang" field from the
// GramSpec to resolve a language profile, then dispatches word order, question
// formation, and morphology through the engine functions in grammar.el and
// morphology.el.
//
// Depends on: morphology (verb_form, agree_determiner)
// grammar (generate_tree, render_tree, slots_*, build_np, build_pp, etc.)
// Semantic form slot keys
// English remains the default (backward compatible) when no "lang" key is set.
//
// intent - "assert" | "question" | "command"
// agent - subject referent string (pronoun or "det noun" phrase)
// predicate - verb base form
// patient - object referent string (optional)
// location - prepositional phrase string, e.g. "in the park" (optional)
// tense - "present" | "past" | "future"
// aspect - "simple" | "progressive" | "perfect"
// Realization pipeline per call:
// 1. Extract lang code -> resolve profile
// 2. Extract agent, predicate, patient, location, tense, aspect, intent
// 3. Compute person/number from agent (English heuristic; other languages TBD)
// 4. Build VP: morph_conjugate with profile -> get verb and auxiliary surface
// 5. Choose question strategy from gram_question_strategy(profile)
// 6. Order constituents via gram_order_constituents(subj, verb, obj, profile)
// 7. Capitalize and terminate
//
// Additional computed slots used internally:
// agent_person - "first" | "second" | "third"
// agent_number - "singular" | "plural"
// verb_surf - conjugated verb surface form
// aux_surf - conjugated auxiliary surface form
// Depends on: morphology (morph_conjugate, agree_determiner)
// grammar (gram_order_constituents, gram_question_strategy,
// gram_build_vp, build_np, build_pp, slots_get)
// language-profile (lang_from_code, lang_get, ...)
// Agent agreement analysis
//
// Person and number are inferred from English pronouns. For other languages
// the grammatical person/number should come from the Engram vocabulary node
// for the subject; here we use a heuristic that is correct for English and
// passable for languages where the same pronoun strings are used.
fn agent_person(agent: String) -> String {
if str_eq(agent, "I") { return "first" }
@@ -51,8 +50,6 @@ fn agent_number(agent: String) -> String {
if str_eq(agent, "us") { return "plural" }
if str_eq(agent, "they") { return "plural" }
if str_eq(agent, "them") { return "plural" }
// For noun phrases: check for plural determiner or known plural nouns
// Default to singular for simple noun phrases
return "singular"
}
@@ -64,75 +61,174 @@ fn realize_np(referent: String, number: String) -> String {
// VP realization
//
// Returns the surface verb string (possibly with auxiliary) for a VP.
// aspect: "simple" | "progressive" | "perfect"
// tense: "present" | "past" | "future"
// person: "first" | "second" | "third"
// number: "singular" | "plural"
// Returns [main_verb_surface, aux_surface_or_empty]
// Returns [main_verb_surface, aux_surface_or_empty].
// Delegates conjugation to morph_conjugate with the language profile.
fn realize_vp(base_verb: String, tense: String, aspect: String, person: String, number: String) -> [String] {
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
let empty_aux: String = ""
// future: will + base
if str_eq(tense, "future") {
// Future: modal "will" + base (English) or language-specific future marker.
// For isolating/agglutinative languages the future marker is also the
// base form (morph_conjugate returns base); the surface "will" only appears
// for English because morph_conjugate("be", "future", ..., en_profile) = "will be".
let code: String = lang_get(profile, "code")
if str_eq(code, "en") {
let result: [String] = native_list_empty()
let result = native_list_append(result, base_verb)
let result = native_list_append(result, "will")
return result
}
// Other languages: conjugate normally (engine returns base form for
// languages without loaded Engram suffix data).
let surf: String = morph_conjugate(base_verb, tense, person, number, profile)
let result: [String] = native_list_empty()
let result = native_list_append(result, base_verb)
let result = native_list_append(result, "will")
let result = native_list_append(result, surf)
let result = native_list_append(result, empty_aux)
return result
}
// progressive: be + gerund
if str_eq(aspect, "progressive") {
let gerund: String = verb_form(base_verb, "progressive", person, number)
// be auxiliary conjugated for tense
let be_aux: String = verb_form("be", tense, person, number)
let gerund: String = morph_conjugate(base_verb, "progressive", person, number, profile)
let be_aux: String = morph_conjugate("be", tense, person, number, profile)
let result: [String] = native_list_empty()
let result = native_list_append(result, gerund)
let result = native_list_append(result, be_aux)
return result
}
// perfect: have + past participle
if str_eq(aspect, "perfect") {
let pp: String = verb_form(base_verb, "perfect", person, number)
// have auxiliary conjugated for tense+agreement
let have_form: String = verb_form("have", tense, person, number)
let result: [String] = native_list_empty()
let pp: String = morph_conjugate(base_verb, "perfect", person, number, profile)
let have_form: String = morph_conjugate("have", tense, person, number, profile)
let result: [String] = native_list_empty()
let result = native_list_append(result, pp)
let result = native_list_append(result, have_form)
return result
}
// simple
let surf: String = verb_form(base_verb, tense, person, number)
let surf: String = morph_conjugate(base_verb, tense, person, number, profile)
let result: [String] = native_list_empty()
let result = native_list_append(result, surf)
let result = native_list_append(result, empty_aux)
return result
}
// Do-support for questions
// Question formation
//
// "Do you see the dog?" uses do-support for non-be verbs in simple aspect.
// Returns [do_aux_surface, bare_verb_base]
// Strategy is resolved from gram_question_strategy(profile):
//
// "do-support" (en) - insert conjugated "do" before subject; verb stays base.
// "particle" (ja, hi, fi) - statement order + sentence-final question particle.
// "intonation" (zh, es, ar, ru, sw) - statement order + "?" punctuation only.
// "inversion" (fr, de) - subject-verb inversion.
fn realize_do_support(base_verb: String, tense: String, person: String, number: String) -> [String] {
// For "be" verbs: invert directly, no do-support
if str_eq(base_verb, "be") {
let be_form: String = verb_form("be", tense, person, number)
let result: [String] = native_list_empty()
let result = native_list_append(result, be_form)
let result = native_list_append(result, "")
return result
// realize_question_lang: build the question surface string for any language.
// Returns the complete surface string (without final punctuation).
fn realize_question_lang(predicate: String, tense: String, aspect: String, person: String, number: String, agent: String, patient: String, location: String, profile: [String]) -> String {
let strategy: String = gram_question_strategy(profile)
let code: String = lang_get(profile, "code")
// do-support (English)
if str_eq(strategy, "do-support") {
if str_eq(aspect, "progressive") {
let vp_pair: [String] = realize_vp_lang(predicate, tense, "progressive", person, number, profile)
let gerund: String = native_list_get(vp_pair, 0)
let be_aux: String = native_list_get(vp_pair, 1)
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, be_aux)
let parts = native_list_append(parts, agent)
let parts = native_list_append(parts, gerund)
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
return str_join(parts, " ")
}
if str_eq(aspect, "perfect") {
let vp_pair: [String] = realize_vp_lang(predicate, tense, "perfect", person, number, profile)
let pp: String = native_list_get(vp_pair, 0)
let have_aux: String = native_list_get(vp_pair, 1)
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, have_aux)
let parts = native_list_append(parts, agent)
let parts = native_list_append(parts, pp)
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
return str_join(parts, " ")
}
// Simple: do-support
if str_eq(predicate, "be") {
let be_form: String = morph_conjugate("be", tense, person, number, profile)
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, be_form)
let parts = native_list_append(parts, agent)
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
return str_join(parts, " ")
}
let do_form: String = morph_conjugate("do", tense, person, number, profile)
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, do_form)
let parts = native_list_append(parts, agent)
let parts = native_list_append(parts, predicate)
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
return str_join(parts, " ")
}
// For modal/have-perfect: invert the auxiliary directly
// For simple tense: do-support
let do_form: String = verb_form("do", tense, person, number)
let result: [String] = native_list_empty()
let result = native_list_append(result, do_form)
let result = native_list_append(result, base_verb)
return result
// particle (ja, hi, fi)
// Build in statement order, then append the question particle.
if str_eq(strategy, "particle") {
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
let verb_s: String = native_list_get(vp_pair, 0)
let aux_s: String = native_list_get(vp_pair, 1)
let vp_str: String = gram_build_vp(verb_s, aux_s, profile)
let core: String = gram_order_constituents(agent, vp_str, patient, profile)
let loc_part: String = ""
if !str_eq(location, "") {
let loc_part = core + " " + location
} else {
let loc_part = core
}
// Language-specific question particles
if str_eq(code, "ja") { return loc_part + "" }
if str_eq(code, "hi") { return loc_part + " क्या" }
if str_eq(code, "fi") { return loc_part + "-ko" }
return loc_part + "?"
}
// inversion (fr, de)
if str_eq(strategy, "inversion") {
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
let verb_s: String = native_list_get(vp_pair, 0)
let aux_s: String = native_list_get(vp_pair, 1)
// Inversion: Verb-Subject-Object order
let parts: [String] = native_list_empty()
if !str_eq(aux_s, "") {
let parts = native_list_append(parts, aux_s)
} else {
let parts = native_list_append(parts, verb_s)
}
let parts = native_list_append(parts, agent)
if !str_eq(aux_s, "") {
let parts = native_list_append(parts, verb_s)
}
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
return str_join(parts, " ")
}
// intonation (zh, es, ar, ru, sw) statement order, "?" added by caller
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
let verb_s: String = native_list_get(vp_pair, 0)
let aux_s: String = native_list_get(vp_pair, 1)
let vp_str: String = gram_build_vp(verb_s, aux_s, profile)
let core: String = gram_order_constituents(agent, vp_str, patient, profile)
if !str_eq(location, "") {
return core + " " + location
}
return core
}
// Capitalization and punctuation
@@ -143,39 +239,30 @@ fn capitalize_first(s: String) -> String {
return s
}
let first: String = str_slice(s, 0, 1)
let rest: String = str_slice(s, 1, n)
let rest: String = str_slice(s, 1, n)
return str_to_upper(first) + rest
}
fn add_punct(s: String, intent: String) -> String {
if str_eq(intent, "question") {
return s + "?"
}
if str_eq(intent, "command") {
return s + "."
}
if str_eq(intent, "question") { return s + "?" }
return s + "."
}
// Main realization
// Main realization entry point
fn realize(form: [String]) -> String {
let intent: String = slots_get(form, "intent")
let agent: String = slots_get(form, "agent")
fn realize_lang(form: [String], profile: [String]) -> String {
let intent: String = slots_get(form, "intent")
let agent: String = slots_get(form, "agent")
let predicate: String = slots_get(form, "predicate")
let patient: String = slots_get(form, "patient")
let location: String = slots_get(form, "location")
let patient: String = slots_get(form, "patient")
let location: String = slots_get(form, "location")
let tense_raw: String = slots_get(form, "tense")
let aspect_raw: String = slots_get(form, "aspect")
let aspect_raw: String= slots_get(form, "aspect")
let tense: String = tense_raw
if str_eq(tense, "") {
let tense = "present"
}
let tense: String = tense_raw
if str_eq(tense, "") { let tense = "present" }
let aspect: String = aspect_raw
if str_eq(aspect, "") {
let aspect = "simple"
}
if str_eq(aspect, "") { let aspect = "simple" }
let person: String = agent_person(agent)
let number: String = agent_number(agent)
@@ -184,78 +271,40 @@ fn realize(form: [String]) -> String {
if str_eq(intent, "command") {
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, predicate)
if !str_eq(patient, "") {
let parts = native_list_append(parts, patient)
}
if !str_eq(location, "") {
let parts = native_list_append(parts, location)
}
if !str_eq(patient, "") { let parts = native_list_append(parts, patient) }
if !str_eq(location, "") { let parts = native_list_append(parts, location) }
let sentence: String = str_join(parts, " ")
return add_punct(capitalize_first(sentence), "command")
}
// Question (yes/no)
// Question
if str_eq(intent, "question") {
let do_pair: [String] = realize_do_support(predicate, tense, person, number)
let aux_surf: String = native_list_get(do_pair, 0)
let verb_bare: String = native_list_get(do_pair, 1)
// For progressive/perfect questions, use the aspect auxiliary instead
let use_verb: String = verb_bare
let use_aux: String = aux_surf
if str_eq(aspect, "progressive") {
let vp_pair: [String] = realize_vp(predicate, tense, "progressive", person, number)
let gerund: String = native_list_get(vp_pair, 0)
let be_aux: String = native_list_get(vp_pair, 1)
let use_aux = be_aux
let use_verb = gerund
}
if str_eq(aspect, "perfect") {
let vp_pair: [String] = realize_vp(predicate, tense, "perfect", person, number)
let pp: String = native_list_get(vp_pair, 0)
let have_aux: String = native_list_get(vp_pair, 1)
let use_aux = have_aux
let use_verb = pp
}
// Build: Aux Agent Verb [Patient] [Location] ?
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, use_aux)
let parts = native_list_append(parts, agent)
if str_eq(use_verb, "") {
// be inversion: Aux already is the be form, no separate verb
} else {
let parts = native_list_append(parts, use_verb)
}
if !str_eq(patient, "") {
let parts = native_list_append(parts, patient)
}
if !str_eq(location, "") {
let parts = native_list_append(parts, location)
}
let sentence: String = str_join(parts, " ")
return add_punct(capitalize_first(sentence), "question")
let surface: String = realize_question_lang(predicate, tense, aspect, person, number, agent, patient, location, profile)
return add_punct(capitalize_first(surface), "question")
}
// Assertion (declarative)
let vp_pair: [String] = realize_vp(predicate, tense, aspect, person, number)
let vp_pair: [String] = realize_vp_lang(predicate, tense, aspect, person, number, profile)
let verb_surf: String = native_list_get(vp_pair, 0)
let aux_surf: String = native_list_get(vp_pair, 1)
let aux_surf: String = native_list_get(vp_pair, 1)
let vp_str: String = gram_build_vp(verb_surf, aux_surf, profile)
let core: String = gram_order_constituents(agent, vp_str, patient, profile)
let parts: [String] = native_list_empty()
let parts = native_list_append(parts, agent)
if !str_eq(aux_surf, "") {
let parts = native_list_append(parts, aux_surf)
}
let parts = native_list_append(parts, verb_surf)
if !str_eq(patient, "") {
let parts = native_list_append(parts, patient)
}
let parts = native_list_append(parts, core)
if !str_eq(location, "") {
let parts = native_list_append(parts, location)
}
let sentence: String = str_join(parts, " ")
return add_punct(capitalize_first(sentence), "assert")
}
// realize: backward-compatible English entry point (original signature).
fn realize(form: [String]) -> String {
let lang_code: String = slots_get(form, "lang")
if str_eq(lang_code, "") {
return realize_lang(form, lang_default())
}
return realize_lang(form, lang_from_code(lang_code))
}