Add semantics layer bridging intent frames to grammar realization
Introduces semantics.el with SemFrame (sem_frame/sem_frame_simple/sem_frame_obj constructors), sem_to_spec to convert intent frames into realizer slot maps, and sem_realize/sem_realize_full as end-to-end frame→text entry points. Supports intents: assert, query, describe, greet. Wires generate_frame() into nlg.el and adds 4 new passing tests (sem-assert, sem-query, sem-describe, sem-greet). All 10 tests pass.
This commit is contained in:
+33
-12
@@ -1,22 +1,29 @@
|
|||||||
// nlg.el - Public NLG API.
|
// nlg.el - Public NLG API.
|
||||||
//
|
//
|
||||||
// Ties together vocabulary, morphology, grammar, and realizer into a single
|
// Ties together vocabulary, morphology, grammar, realizer, and semantics into a
|
||||||
// entry point. Callers use `generate(semantic_form_json)` to produce English.
|
// single entry point.
|
||||||
//
|
//
|
||||||
// SemanticForm JSON fields (all strings):
|
// Two entry points:
|
||||||
// intent - "assert" | "question" | "command"
|
//
|
||||||
// agent - subject (pronoun or noun phrase, optional for commands)
|
// generate(semantic_form_json) -> String
|
||||||
// predicate - verb base form
|
// Low-level JSON-based API (existing). SemanticForm JSON fields:
|
||||||
// patient - object noun phrase (optional)
|
// intent - "assert" | "question" | "command"
|
||||||
// location - prepositional phrase e.g. "in the park" (optional)
|
// agent - subject (pronoun or noun phrase, optional for commands)
|
||||||
// tense - "present" | "past" | "future" (default: "present")
|
// predicate - verb base form
|
||||||
// aspect - "simple" | "progressive" | "perfect" (default: "simple")
|
// patient - object noun phrase (optional)
|
||||||
|
// location - prepositional phrase e.g. "in the park" (optional)
|
||||||
|
// tense - "present" | "past" | "future" (default: "present")
|
||||||
|
// aspect - "simple" | "progressive" | "perfect" (default: "simple")
|
||||||
|
//
|
||||||
|
// generate_frame(frame: SemFrame) -> String
|
||||||
|
// High-level semantics API (new). frame is built with sem_frame() from
|
||||||
|
// semantics.el. Intents: "assert" | "query" | "describe" | "greet".
|
||||||
|
|
||||||
// ── Include all sub-modules (concatenated at build time via nlg.el) ────────────────────────────
|
// ── Include all sub-modules (concatenated at build time via nlg.el) ────────────────────────────
|
||||||
// In El, there is no import system for standalone programs, so the sub-modules
|
// In El, there is no import system for standalone programs, so the sub-modules
|
||||||
// are inlined above this file in the build. When tests include nlg.el, they
|
// are inlined above this file in the build. When tests include nlg.el, they
|
||||||
// first include vocabulary.el, morphology.el, grammar.el, realizer.el,
|
// first include vocabulary.el, morphology.el, grammar.el, realizer.el,
|
||||||
// then this file.
|
// semantics.el, then this file.
|
||||||
|
|
||||||
// ── JSON helpers ──────────────────────────────────────────────────────────────
|
// ── JSON helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -25,7 +32,21 @@ fn sem_get(json: String, key: String) -> String {
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Public API ────────────────────────────────────────────────────────────────
|
// ── Public API: SemFrame ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Generate an English sentence from a SemFrame (built with sem_frame()).
|
||||||
|
// Uses the full semantics pipeline: sem_realize_full with the object field
|
||||||
|
// as the patient and sem_object as the predicate verb when intent is
|
||||||
|
// "assert" or "query", or the appropriate handler for "describe"/"greet".
|
||||||
|
//
|
||||||
|
// For assert/query, pass the verb separately via sem_realize_full directly
|
||||||
|
// when you need SVO (subject-verb-object) structure. This top-level entry
|
||||||
|
// point handles the common case where sem_object holds the predicate verb.
|
||||||
|
fn generate_frame(frame: [String]) -> String {
|
||||||
|
return sem_realize(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public API: JSON ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Generate an English sentence from a semantic form JSON string.
|
// Generate an English sentence from a semantic form JSON string.
|
||||||
fn generate(semantic_form_json: String) -> String {
|
fn generate(semantic_form_json: String) -> String {
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
// semantics.el - Semantic layer: SemFrame -> GramSpec (slot map for realizer).
|
||||||
|
//
|
||||||
|
// Bridges from intent/meaning representation to the grammar layer.
|
||||||
|
// A SemFrame is a slot map ([String] key-value list) with these keys:
|
||||||
|
//
|
||||||
|
// "intent" - "assert" | "query" | "describe" | "greet"
|
||||||
|
// "subject" - subject referent (pronoun or noun phrase)
|
||||||
|
// "object" - object referent (optional, "" if absent)
|
||||||
|
// "modifiers" - semicolon-separated modifier strings (e.g. "in the park;quickly")
|
||||||
|
//
|
||||||
|
// sem_to_spec converts a SemFrame into a realizer slot map ready for realize().
|
||||||
|
// sem_realize is the end-to-end shortcut: frame -> realized English text.
|
||||||
|
//
|
||||||
|
// Depends on: grammar (slots_*), realizer (realize)
|
||||||
|
|
||||||
|
// ── SemFrame constructors ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Build a SemFrame slot map with all four fields.
|
||||||
|
fn sem_frame(intent: String, subject: String, obj: String, modifiers: String) -> [String] {
|
||||||
|
let r: [String] = native_list_empty()
|
||||||
|
let r = native_list_append(r, "intent")
|
||||||
|
let r = native_list_append(r, intent)
|
||||||
|
let r = native_list_append(r, "subject")
|
||||||
|
let r = native_list_append(r, subject)
|
||||||
|
let r = native_list_append(r, "object")
|
||||||
|
let r = native_list_append(r, obj)
|
||||||
|
let r = native_list_append(r, "modifiers")
|
||||||
|
let r = native_list_append(r, modifiers)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience: frame with no object and no modifiers.
|
||||||
|
fn sem_frame_simple(intent: String, subject: String) -> [String] {
|
||||||
|
return sem_frame(intent, subject, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience: frame with object but no modifiers.
|
||||||
|
fn sem_frame_obj(intent: String, subject: String, obj: String) -> [String] {
|
||||||
|
return sem_frame(intent, subject, obj, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SemFrame field accessors ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn sem_intent(frame: [String]) -> String {
|
||||||
|
return slots_get(frame, "intent")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sem_subject(frame: [String]) -> String {
|
||||||
|
return slots_get(frame, "subject")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sem_object(frame: [String]) -> String {
|
||||||
|
return slots_get(frame, "object")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sem_modifiers(frame: [String]) -> String {
|
||||||
|
return slots_get(frame, "modifiers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Modifier helpers ──────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Modifiers are stored as a semicolon-delimited string.
|
||||||
|
// e.g. "in the park;quickly"
|
||||||
|
// sem_first_modifier extracts the first modifier (typically a location phrase).
|
||||||
|
|
||||||
|
fn sem_first_modifier(mods: String) -> String {
|
||||||
|
let n: Int = str_len(mods)
|
||||||
|
if n == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Scan to first ';'
|
||||||
|
let i: Int = 0
|
||||||
|
let running: Bool = true
|
||||||
|
while running {
|
||||||
|
if i >= n {
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
let c: String = str_slice(mods, i, i + 1)
|
||||||
|
if str_eq(c, ";") {
|
||||||
|
let running = false
|
||||||
|
} else {
|
||||||
|
let i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str_slice(mods, 0, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Intent mapping ────────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Maps semantic intent names to the slot values expected by realize():
|
||||||
|
// "assert" -> intent="assert" declarative sentence
|
||||||
|
// "query" -> intent="question" yes/no question with do-support
|
||||||
|
// "describe" -> intent="assert" declarative; object holds predicate phrase
|
||||||
|
// "greet" -> handled specially "Hello, {subject}."
|
||||||
|
|
||||||
|
fn sem_intent_to_realize(intent: String) -> String {
|
||||||
|
if str_eq(intent, "assert") { return "assert" }
|
||||||
|
if str_eq(intent, "query") { return "question" }
|
||||||
|
if str_eq(intent, "describe") { return "assert" }
|
||||||
|
if str_eq(intent, "greet") { return "greet" }
|
||||||
|
// Default: treat unknown as assertion
|
||||||
|
return "assert"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sem_to_spec: SemFrame -> realizer slot map ────────────────────────────────
|
||||||
|
//
|
||||||
|
// Returns a [String] slot map suitable for passing directly to realize().
|
||||||
|
// Default tense is "present", aspect is "simple". Callers that need
|
||||||
|
// a different tense/aspect should use slots_set on the returned map.
|
||||||
|
|
||||||
|
fn sem_to_spec(frame: [String]) -> [String] {
|
||||||
|
let intent: String = sem_intent(frame)
|
||||||
|
let subject: String = sem_subject(frame)
|
||||||
|
let obj: String = sem_object(frame)
|
||||||
|
let mods: String = sem_modifiers(frame)
|
||||||
|
|
||||||
|
let location: String = sem_first_modifier(mods)
|
||||||
|
|
||||||
|
// ── Greet: no standard grammar slot, compose directly ─────────────────
|
||||||
|
// We still return a valid spec; sem_realize handles "greet" specially.
|
||||||
|
if str_eq(intent, "greet") {
|
||||||
|
let spec: [String] = native_list_empty()
|
||||||
|
let spec = native_list_append(spec, "intent")
|
||||||
|
let spec = native_list_append(spec, "greet")
|
||||||
|
let spec = native_list_append(spec, "agent")
|
||||||
|
let spec = native_list_append(spec, subject)
|
||||||
|
let spec = native_list_append(spec, "predicate")
|
||||||
|
let spec = native_list_append(spec, "")
|
||||||
|
let spec = native_list_append(spec, "patient")
|
||||||
|
let spec = native_list_append(spec, "")
|
||||||
|
let spec = native_list_append(spec, "location")
|
||||||
|
let spec = native_list_append(spec, "")
|
||||||
|
let spec = native_list_append(spec, "tense")
|
||||||
|
let spec = native_list_append(spec, "present")
|
||||||
|
let spec = native_list_append(spec, "aspect")
|
||||||
|
let spec = native_list_append(spec, "simple")
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Describe: subject is topic, object is predicate noun/adj phrase ───
|
||||||
|
// Realized as "Subject is Object." using the copula "be".
|
||||||
|
if str_eq(intent, "describe") {
|
||||||
|
let spec: [String] = native_list_empty()
|
||||||
|
let spec = native_list_append(spec, "intent")
|
||||||
|
let spec = native_list_append(spec, "assert")
|
||||||
|
let spec = native_list_append(spec, "agent")
|
||||||
|
let spec = native_list_append(spec, subject)
|
||||||
|
let spec = native_list_append(spec, "predicate")
|
||||||
|
let spec = native_list_append(spec, "be")
|
||||||
|
let spec = native_list_append(spec, "patient")
|
||||||
|
let spec = native_list_append(spec, obj)
|
||||||
|
let spec = native_list_append(spec, "location")
|
||||||
|
let spec = native_list_append(spec, location)
|
||||||
|
let spec = native_list_append(spec, "tense")
|
||||||
|
let spec = native_list_append(spec, "present")
|
||||||
|
let spec = native_list_append(spec, "aspect")
|
||||||
|
let spec = native_list_append(spec, "simple")
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Assert / Query: standard subject-verb-object ───────────────────────
|
||||||
|
let realize_intent: String = sem_intent_to_realize(intent)
|
||||||
|
let spec: [String] = native_list_empty()
|
||||||
|
let spec = native_list_append(spec, "intent")
|
||||||
|
let spec = native_list_append(spec, realize_intent)
|
||||||
|
let spec = native_list_append(spec, "agent")
|
||||||
|
let spec = native_list_append(spec, subject)
|
||||||
|
let spec = native_list_append(spec, "predicate")
|
||||||
|
let spec = native_list_append(spec, obj)
|
||||||
|
let spec = native_list_append(spec, "patient")
|
||||||
|
let spec = native_list_append(spec, "")
|
||||||
|
let spec = native_list_append(spec, "location")
|
||||||
|
let spec = native_list_append(spec, location)
|
||||||
|
let spec = native_list_append(spec, "tense")
|
||||||
|
let spec = native_list_append(spec, "present")
|
||||||
|
let spec = native_list_append(spec, "aspect")
|
||||||
|
let spec = native_list_append(spec, "simple")
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sem_to_spec_full: SemFrame -> realizer slot map with explicit verb ────────
|
||||||
|
//
|
||||||
|
// For "assert" and "query" frames the object field carries the *noun phrase*
|
||||||
|
// patient, not the verb. Callers that have a separate verb should use this
|
||||||
|
// variant and supply it explicitly.
|
||||||
|
//
|
||||||
|
// verb - base form of the predicate verb
|
||||||
|
// tense - "present" | "past" | "future"
|
||||||
|
// aspect - "simple" | "progressive" | "perfect"
|
||||||
|
|
||||||
|
fn sem_to_spec_full(frame: [String], verb: String, tense: String, aspect: String) -> [String] {
|
||||||
|
let intent: String = sem_intent(frame)
|
||||||
|
let subject: String = sem_subject(frame)
|
||||||
|
let obj: String = sem_object(frame)
|
||||||
|
let mods: String = sem_modifiers(frame)
|
||||||
|
|
||||||
|
let location: String = sem_first_modifier(mods)
|
||||||
|
|
||||||
|
if str_eq(intent, "greet") {
|
||||||
|
return sem_to_spec(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
if str_eq(intent, "describe") {
|
||||||
|
let spec: [String] = native_list_empty()
|
||||||
|
let spec = native_list_append(spec, "intent")
|
||||||
|
let spec = native_list_append(spec, "assert")
|
||||||
|
let spec = native_list_append(spec, "agent")
|
||||||
|
let spec = native_list_append(spec, subject)
|
||||||
|
let spec = native_list_append(spec, "predicate")
|
||||||
|
let spec = native_list_append(spec, "be")
|
||||||
|
let spec = native_list_append(spec, "patient")
|
||||||
|
let spec = native_list_append(spec, obj)
|
||||||
|
let spec = native_list_append(spec, "location")
|
||||||
|
let spec = native_list_append(spec, location)
|
||||||
|
let spec = native_list_append(spec, "tense")
|
||||||
|
let spec = native_list_append(spec, tense)
|
||||||
|
let spec = native_list_append(spec, "aspect")
|
||||||
|
let spec = native_list_append(spec, aspect)
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
let realize_intent: String = sem_intent_to_realize(intent)
|
||||||
|
let spec: [String] = native_list_empty()
|
||||||
|
let spec = native_list_append(spec, "intent")
|
||||||
|
let spec = native_list_append(spec, realize_intent)
|
||||||
|
let spec = native_list_append(spec, "agent")
|
||||||
|
let spec = native_list_append(spec, subject)
|
||||||
|
let spec = native_list_append(spec, "predicate")
|
||||||
|
let spec = native_list_append(spec, verb)
|
||||||
|
let spec = native_list_append(spec, "patient")
|
||||||
|
let spec = native_list_append(spec, obj)
|
||||||
|
let spec = native_list_append(spec, "location")
|
||||||
|
let spec = native_list_append(spec, location)
|
||||||
|
let spec = native_list_append(spec, "tense")
|
||||||
|
let spec = native_list_append(spec, tense)
|
||||||
|
let spec = native_list_append(spec, "aspect")
|
||||||
|
let spec = native_list_append(spec, aspect)
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Greet realization helper ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn sem_realize_greet(subject: String) -> String {
|
||||||
|
if str_eq(subject, "") {
|
||||||
|
return "Hello."
|
||||||
|
}
|
||||||
|
return "Hello, " + subject + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sem_realize: SemFrame -> English text ─────────────────────────────────────
|
||||||
|
//
|
||||||
|
// End-to-end convenience: converts a SemFrame to a realized English string.
|
||||||
|
// For "assert" and "query" intents, the object field is treated as the
|
||||||
|
// predicate verb base form (intransitive use). For richer frames with both
|
||||||
|
// a verb and an object, use sem_realize_full.
|
||||||
|
|
||||||
|
fn sem_realize(frame: [String]) -> String {
|
||||||
|
let intent: String = sem_intent(frame)
|
||||||
|
|
||||||
|
if str_eq(intent, "greet") {
|
||||||
|
return sem_realize_greet(sem_subject(frame))
|
||||||
|
}
|
||||||
|
|
||||||
|
let spec: [String] = sem_to_spec(frame)
|
||||||
|
return realize(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sem_realize_full: SemFrame + explicit verb -> English text ────────────────
|
||||||
|
//
|
||||||
|
// Like sem_realize but accepts a separate verb, tense, and aspect.
|
||||||
|
// Use this when the SemFrame object field carries a noun phrase patient
|
||||||
|
// and you want to specify the predicate verb independently.
|
||||||
|
|
||||||
|
fn sem_realize_full(frame: [String], verb: String, tense: String, aspect: String) -> String {
|
||||||
|
let intent: String = sem_intent(frame)
|
||||||
|
|
||||||
|
if str_eq(intent, "greet") {
|
||||||
|
return sem_realize_greet(sem_subject(frame))
|
||||||
|
}
|
||||||
|
|
||||||
|
let spec: [String] = sem_to_spec_full(frame, verb, tense, aspect)
|
||||||
|
return realize(spec)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// sem-assert.el - Realize "She sleeps." via the semantics layer.
|
||||||
|
//
|
||||||
|
// SemFrame: intent="assert", subject="she", object="sleep" (verb), modifiers=""
|
||||||
|
|
||||||
|
fn run_test() -> String {
|
||||||
|
let frame: [String] = sem_frame("assert", "she", "sleep", "")
|
||||||
|
return sem_realize(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(run_test())
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// sem-describe.el - Realize "The cat is big." via the semantics layer.
|
||||||
|
//
|
||||||
|
// SemFrame: intent="describe", subject="the cat", object="big", modifiers=""
|
||||||
|
|
||||||
|
fn run_test() -> String {
|
||||||
|
let frame: [String] = sem_frame("describe", "the cat", "big", "")
|
||||||
|
return sem_realize(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(run_test())
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// sem-greet.el - Realize "Hello, Alice." via the semantics layer.
|
||||||
|
//
|
||||||
|
// SemFrame: intent="greet", subject="Alice", object="", modifiers=""
|
||||||
|
|
||||||
|
fn run_test() -> String {
|
||||||
|
let frame: [String] = sem_frame("greet", "Alice", "", "")
|
||||||
|
return sem_realize(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(run_test())
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// sem-query.el - Realize "Do you see?" via the semantics layer.
|
||||||
|
//
|
||||||
|
// SemFrame: intent="query", subject="you", object="see" (verb), modifiers=""
|
||||||
|
|
||||||
|
fn run_test() -> String {
|
||||||
|
let frame: [String] = sem_frame("query", "you", "see", "")
|
||||||
|
return sem_realize(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(run_test())
|
||||||
@@ -33,6 +33,7 @@ NLG_MODULES=(
|
|||||||
"${SRC_DIR}/vocabulary.el"
|
"${SRC_DIR}/vocabulary.el"
|
||||||
"${SRC_DIR}/grammar.el"
|
"${SRC_DIR}/grammar.el"
|
||||||
"${SRC_DIR}/realizer.el"
|
"${SRC_DIR}/realizer.el"
|
||||||
|
"${SRC_DIR}/semantics.el"
|
||||||
"${SRC_DIR}/nlg.el"
|
"${SRC_DIR}/nlg.el"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ run_nlg_case "question" examples/question.el "Do you see the d
|
|||||||
run_nlg_case "command" examples/command.el "Go home."
|
run_nlg_case "command" examples/command.el "Go home."
|
||||||
run_nlg_case "plural-noun" examples/plural-noun.el "cats|children|boxes|cities|fish|leaves"
|
run_nlg_case "plural-noun" examples/plural-noun.el "cats|children|boxes|cities|fish|leaves"
|
||||||
run_nlg_case "verb-conjugation" examples/verb-conjugation.el "ran|walked|is|am|sleeping"
|
run_nlg_case "verb-conjugation" examples/verb-conjugation.el "ran|walked|is|am|sleeping"
|
||||||
|
run_nlg_case "sem-assert" examples/sem-assert.el "She sleeps."
|
||||||
|
run_nlg_case "sem-query" examples/sem-query.el "Do you see?"
|
||||||
|
run_nlg_case "sem-describe" examples/sem-describe.el "The cat is big."
|
||||||
|
run_nlg_case "sem-greet" examples/sem-greet.el "Hello, Alice."
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "${PASS} passed, ${FAIL} failed"
|
echo "${PASS} passed, ${FAIL} failed"
|
||||||
|
|||||||
Reference in New Issue
Block a user