Add native El NLG system: morphology, vocabulary, grammar, realizer
Implements a complete natural language generation stack in El: - morphology.el: English pluralization, verb conjugation (40+ irregulars), determiner agreement - vocabulary.el: inline seed lexicon (~100 entries: pronouns, nouns, verbs, adjectives, etc.) - grammar.el: CFG rules (S/NP/VP/PP), slot-map driven tree generator, s-expression renderer - realizer.el: semantic form -> English text with tense/aspect/agreement, do-support for questions - nlg.el: JSON-driven public API tying all modules together - tests/run.sh: acceptance corpus runner (6 tests, all passing)
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
// realizer.el - Syntactic realizer: semantic form -> English 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
|
||||
//
|
||||
// Depends on: morphology (verb_form, agree_determiner)
|
||||
// grammar (generate_tree, render_tree, slots_*, build_np, build_pp, etc.)
|
||||
|
||||
// ── Semantic form slot keys ───────────────────────────────────────────────────
|
||||
//
|
||||
// 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"
|
||||
//
|
||||
// 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
|
||||
|
||||
// ── Agent agreement analysis ──────────────────────────────────────────────────
|
||||
|
||||
fn agent_person(agent: String) -> String {
|
||||
if str_eq(agent, "I") { return "first" }
|
||||
if str_eq(agent, "me") { return "first" }
|
||||
if str_eq(agent, "we") { return "first" }
|
||||
if str_eq(agent, "us") { return "first" }
|
||||
if str_eq(agent, "you") { return "second" }
|
||||
return "third"
|
||||
}
|
||||
|
||||
fn agent_number(agent: String) -> String {
|
||||
if str_eq(agent, "I") { return "singular" }
|
||||
if str_eq(agent, "me") { return "singular" }
|
||||
if str_eq(agent, "he") { return "singular" }
|
||||
if str_eq(agent, "him") { return "singular" }
|
||||
if str_eq(agent, "she") { return "singular" }
|
||||
if str_eq(agent, "her") { return "singular" }
|
||||
if str_eq(agent, "it") { return "singular" }
|
||||
if str_eq(agent, "you") { return "singular" }
|
||||
if str_eq(agent, "we") { return "plural" }
|
||||
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"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── 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]
|
||||
|
||||
fn realize_vp(base_verb: String, tense: String, aspect: String, person: String, number: String) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
// ── future: will + base ────────────────────────────────────────────────
|
||||
if str_eq(tense, "future") {
|
||||
let result: [String] = native_list_empty()
|
||||
let result = native_list_append(result, base_verb)
|
||||
let result = native_list_append(result, "will")
|
||||
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 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 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 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 ──────────────────────────────────────────────────
|
||||
//
|
||||
// "Do you see the dog?" uses do-support for non-be verbs in simple aspect.
|
||||
// Returns [do_aux_surface, bare_verb_base]
|
||||
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// ── Capitalization and punctuation ────────────────────────────────────────────
|
||||
|
||||
fn capitalize_first(s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
if n == 0 {
|
||||
return s
|
||||
}
|
||||
let first: String = str_slice(s, 0, 1)
|
||||
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 + "."
|
||||
}
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization ──────────────────────────────────────────────────────────
|
||||
|
||||
fn realize(form: [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 tense_raw: String = slots_get(form, "tense")
|
||||
let aspect_raw: String = slots_get(form, "aspect")
|
||||
|
||||
let tense: String = tense_raw
|
||||
if str_eq(tense, "") {
|
||||
let tense = "present"
|
||||
}
|
||||
let aspect: String = aspect_raw
|
||||
if str_eq(aspect, "") {
|
||||
let aspect = "simple"
|
||||
}
|
||||
|
||||
let person: String = agent_person(agent)
|
||||
let number: String = agent_number(agent)
|
||||
|
||||
// ── Command (imperative) ──────────────────────────────────────────────────
|
||||
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)
|
||||
}
|
||||
let sentence: String = str_join(parts, " ")
|
||||
return add_punct(capitalize_first(sentence), "command")
|
||||
}
|
||||
|
||||
// ── Question (yes/no) ─────────────────────────────────────────────────────
|
||||
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")
|
||||
}
|
||||
|
||||
// ── Assertion (declarative) ───────────────────────────────────────────────
|
||||
let vp_pair: [String] = realize_vp(predicate, tense, aspect, person, number)
|
||||
let verb_surf: String = native_list_get(vp_pair, 0)
|
||||
let aux_surf: String = native_list_get(vp_pair, 1)
|
||||
|
||||
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)
|
||||
}
|
||||
if !str_eq(location, "") {
|
||||
let parts = native_list_append(parts, location)
|
||||
}
|
||||
let sentence: String = str_join(parts, " ")
|
||||
return add_punct(capitalize_first(sentence), "assert")
|
||||
}
|
||||
Reference in New Issue
Block a user