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.
|
||||
//
|
||||
// Ties together vocabulary, morphology, grammar, and realizer into a single
|
||||
// entry point. Callers use `generate(semantic_form_json)` to produce English.
|
||||
// Ties together vocabulary, morphology, grammar, realizer, and semantics into a
|
||||
// single entry point.
|
||||
//
|
||||
// SemanticForm JSON fields (all strings):
|
||||
// intent - "assert" | "question" | "command"
|
||||
// agent - subject (pronoun or noun phrase, optional for commands)
|
||||
// predicate - verb base form
|
||||
// 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")
|
||||
// Two entry points:
|
||||
//
|
||||
// generate(semantic_form_json) -> String
|
||||
// Low-level JSON-based API (existing). SemanticForm JSON fields:
|
||||
// intent - "assert" | "question" | "command"
|
||||
// agent - subject (pronoun or noun phrase, optional for commands)
|
||||
// predicate - verb base form
|
||||
// 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) ────────────────────────────
|
||||
// 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
|
||||
// first include vocabulary.el, morphology.el, grammar.el, realizer.el,
|
||||
// then this file.
|
||||
// semantics.el, then this file.
|
||||
|
||||
// ── JSON helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -25,7 +32,21 @@ fn sem_get(json: String, key: String) -> String {
|
||||
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.
|
||||
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}/grammar.el"
|
||||
"${SRC_DIR}/realizer.el"
|
||||
"${SRC_DIR}/semantics.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 "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 "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 "${PASS} passed, ${FAIL} failed"
|
||||
|
||||
Reference in New Issue
Block a user