Merge PR #3: feat: port remaining foundation El source into monorepo
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// arbor-cli — the `arbor` command-line tool.
|
||||
// Inlines its own copies of the parse / layout / render pipeline so that the
|
||||
// resulting binary is self-contained. (El's `import` form today concatenates
|
||||
// source; once a real module loader lands this becomes a thin driver.)
|
||||
|
||||
vessel "arbor-cli" {
|
||||
version "0.1.0"
|
||||
description "Command-line interface for the Arbor diagram language"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
arbor-core "0.1"
|
||||
arbor-parse "0.1"
|
||||
arbor-layout "0.1"
|
||||
arbor-render "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
// arbor-core — fundamental types for Arbor diagrams.
|
||||
// Node IDs (sanitised), shape vocabulary, edge kinds, and the lightweight
|
||||
// graph value used by every other vessel.
|
||||
|
||||
vessel "arbor-core" {
|
||||
version "0.1.0"
|
||||
description "Core types for Arbor diagrams: NodeId, ArborShape, ArborEdgeKind, graphs"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// arbor-core — core types for Arbor diagrams.
|
||||
//
|
||||
// Idiomatic El: everything is a Map. Functions take/return maps; helpers are
|
||||
// pure and small. The downstream vessels (parse, layout, render) consume the
|
||||
// shapes defined here.
|
||||
//
|
||||
// Shape vocabulary:
|
||||
// ArborShape strings — "rect" "rounded" "cylinder" "diamond" "stadium" "primary"
|
||||
//
|
||||
// Edge-kind strings:
|
||||
// "solid" "dashed" "forbidden" "bidirectional"
|
||||
//
|
||||
// Node value: { "id":Str, "label":Str, "shape":Str }
|
||||
// Edge value: { "from":Str, "to":Str, "label":Str, "kind":Str }
|
||||
// Group value: { "id":Str, "label":Str, "node_ids":[Str], "direction":Str }
|
||||
// Graph value: { "title":Str, "direction":Str, "nodes":[Node], "edges":[Edge], "groups":[Group] }
|
||||
//
|
||||
// Diagram-form (lowered) is the same shape but with NodeStyle/EdgeLine/Arrow
|
||||
// resolved into renderer-friendly fields:
|
||||
// Node: + "sublabel":Str, "style_fill":Str, "style_stroke":Str, "style_color":Str
|
||||
// Edge: + "line":Str ("solid"/"dashed"/"dotted"/"thick"), "arrow":Str ("forward"/"backward"/"both"/"none")
|
||||
//
|
||||
// This file is the canonical definition of those shapes. Other vessels rely on
|
||||
// these field names.
|
||||
|
||||
// ── NodeId sanitisation ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Sanitise an arbitrary string into a Mermaid-safe identifier.
|
||||
// - any char not in [a-zA-Z0-9_] becomes '_'
|
||||
// - consecutive underscores collapse
|
||||
// - trailing underscores stripped
|
||||
// - if first char is a digit, prepend 'n'
|
||||
// - if empty, return "node"
|
||||
|
||||
fn is_alnum_underscore(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
if code >= 65 {
|
||||
if code <= 90 { return true }
|
||||
}
|
||||
if code >= 97 {
|
||||
if code <= 122 { return true }
|
||||
}
|
||||
if code == 95 { return true }
|
||||
false
|
||||
}
|
||||
|
||||
fn is_ascii_digit(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn sanitize_id(s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
if n == 0 { return "node" }
|
||||
|
||||
// Pass 1: replace and collapse.
|
||||
let out = ""
|
||||
let prev_underscore = false
|
||||
let i = 0
|
||||
while i < n {
|
||||
let ch: String = str_char_at(s, i)
|
||||
if is_alnum_underscore(ch) {
|
||||
let out = out + ch
|
||||
let prev_underscore = false
|
||||
} else {
|
||||
if !prev_underscore {
|
||||
let out = out + "_"
|
||||
}
|
||||
let prev_underscore = true
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Pass 2: strip trailing underscores.
|
||||
let m: Int = str_len(out)
|
||||
let end = m
|
||||
let stripping = true
|
||||
while stripping {
|
||||
if end <= 0 {
|
||||
let stripping = false
|
||||
} else {
|
||||
let last: String = str_char_at(out, end - 1)
|
||||
if last == "_" {
|
||||
let end = end - 1
|
||||
} else {
|
||||
let stripping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = str_slice(out, 0, end)
|
||||
|
||||
if str_len(out) == 0 { return "node" }
|
||||
|
||||
// Pass 3: leading-digit guard.
|
||||
let first: String = str_char_at(out, 0)
|
||||
if is_ascii_digit(first) {
|
||||
let out = "n" + out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ── Constructors ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_node(id: String, label: String, shape: String) -> Map<String, Any> {
|
||||
{ "id": id, "label": label, "shape": shape }
|
||||
}
|
||||
|
||||
fn make_edge(src: String, dst: String, kind: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": "", "kind": kind }
|
||||
}
|
||||
|
||||
fn make_edge_with_label(src: String, dst: String, kind: String, label: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": label, "kind": kind }
|
||||
}
|
||||
|
||||
fn make_group(id: String, label: String) -> Map<String, Any> {
|
||||
let empty_ids: [String] = el_list_empty()
|
||||
{ "id": id, "label": label, "node_ids": empty_ids, "direction": "" }
|
||||
}
|
||||
|
||||
fn make_graph() -> Map<String, Any> {
|
||||
let empty_n: [Map<String, Any>] = el_list_empty()
|
||||
let empty_e: [Map<String, Any>] = el_list_empty()
|
||||
let empty_g: [Map<String, Any>] = el_list_empty()
|
||||
{ "title": "", "direction": "top-down",
|
||||
"nodes": empty_n, "edges": empty_e, "groups": empty_g }
|
||||
}
|
||||
|
||||
// ── Shape vocabulary ──────────────────────────────────────────────────────────
|
||||
// Returns the canonical shape string for a token, or "" if unknown.
|
||||
|
||||
fn shape_from_token(tok: String) -> String {
|
||||
let t: String = str_trim(tok)
|
||||
if t == "rect" { return "rect" }
|
||||
if t == "rounded" { return "rounded" }
|
||||
if t == "cylinder" { return "cylinder" }
|
||||
if t == "diamond" { return "diamond" }
|
||||
if t == "stadium" { return "stadium" }
|
||||
if t == "primary" { return "primary" }
|
||||
""
|
||||
}
|
||||
|
||||
// Lower an Arbor shape into the renderer's NodeShape vocabulary.
|
||||
fn shape_to_node_shape(shape: String) -> String {
|
||||
if shape == "rect" { return "rectangle" }
|
||||
if shape == "primary" { return "rectangle" }
|
||||
if shape == "rounded" { return "rounded_rect" }
|
||||
if shape == "cylinder" { return "cylinder" }
|
||||
if shape == "diamond" { return "diamond" }
|
||||
if shape == "stadium" { return "stadium" }
|
||||
"rectangle"
|
||||
}
|
||||
|
||||
// ── Lowering: ArborGraph → DiagramGraph ──────────────────────────────────────
|
||||
//
|
||||
// Replaces every node with a diagram-form node carrying explicit style fields,
|
||||
// and every edge with a diagram-form edge carrying line/arrow strings.
|
||||
|
||||
fn lower_node(n: Map<String, Any>) -> Map<String, Any> {
|
||||
let shape: String = n["shape"]
|
||||
let node_shape: String = shape_to_node_shape(shape)
|
||||
let fill = ""
|
||||
let stroke = ""
|
||||
let color = ""
|
||||
if shape == "primary" {
|
||||
let fill = "#0052A0"
|
||||
let stroke = "#0052A0"
|
||||
let color = "#ffffff"
|
||||
}
|
||||
{ "id": n["id"], "label": n["label"], "sublabel": "",
|
||||
"shape": node_shape,
|
||||
"style_fill": fill, "style_stroke": stroke, "style_color": color }
|
||||
}
|
||||
|
||||
fn lower_edge(e: Map<String, Any>) -> Map<String, Any> {
|
||||
let kind: String = e["kind"]
|
||||
let line = "solid"
|
||||
let arrow = "forward"
|
||||
if kind == "dashed" {
|
||||
let line = "dashed"
|
||||
}
|
||||
if kind == "bidirectional" {
|
||||
let arrow = "both"
|
||||
}
|
||||
// forbidden uses solid line + forward arrow; the renderer overlays the
|
||||
// circle-X marker based on a forbidden-set the caller threads through.
|
||||
{ "from": e["from"], "to": e["to"], "label": e["label"],
|
||||
"line": line, "arrow": arrow }
|
||||
}
|
||||
|
||||
fn lower_graph(g: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = g["nodes"]
|
||||
let edges: [Map<String, Any>] = g["edges"]
|
||||
let lowered_nodes: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
let n: Int = el_list_len(nodes)
|
||||
while i < n {
|
||||
let lowered_nodes = native_list_append(lowered_nodes, lower_node(get(nodes, i)))
|
||||
let i = i + 1
|
||||
}
|
||||
let lowered_edges: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
let m: Int = el_list_len(edges)
|
||||
while i < m {
|
||||
let lowered_edges = native_list_append(lowered_edges, lower_edge(get(edges, i)))
|
||||
let i = i + 1
|
||||
}
|
||||
{ "title": g["title"], "direction": g["direction"],
|
||||
"nodes": lowered_nodes, "edges": lowered_edges, "groups": g["groups"] }
|
||||
}
|
||||
|
||||
// Find a node by id within a (lowered or raw) graph. Returns an empty map
|
||||
// when not found — callers check map_get(result, "id") for presence.
|
||||
fn graph_find_node(graph: Map<String, Any>, id: String) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let node: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = node["id"]
|
||||
if nid == id { return node }
|
||||
let i = i + 1
|
||||
}
|
||||
let empty: Map<String, Any> = el_map_new(0)
|
||||
empty
|
||||
}
|
||||
|
||||
// ── Forbidden-edge set helpers ────────────────────────────────────────────────
|
||||
// The lowered graph drops the "forbidden" kind (line/arrow have no slot for
|
||||
// it). Callers preserve the set as a list of "from->to" strings.
|
||||
|
||||
fn forbidden_key(from: String, to: String) -> String {
|
||||
from + "->" + to
|
||||
}
|
||||
|
||||
fn collect_forbidden(graph: Map<String, Any>) -> [String] {
|
||||
let edges: [Map<String, Any>] = graph["edges"]
|
||||
let n: Int = el_list_len(edges)
|
||||
let out: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let e: Map<String, Any> = get(edges, i)
|
||||
let kind: String = e["kind"]
|
||||
if kind == "forbidden" {
|
||||
let f: String = e["from"]
|
||||
let t: String = e["to"]
|
||||
let out = native_list_append(out, forbidden_key(f, t))
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn forbidden_contains(set: [String], src: String, dst: String) -> Bool {
|
||||
let key: String = forbidden_key(src, dst)
|
||||
let n: Int = el_list_len(set)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let s: String = get(set, i)
|
||||
if s == key { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// ── Smoke test ────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// State is kept in process-local k/v storage so we never mix Int + Call or
|
||||
// Int + Ident in `+` (which the codegen heuristic emits as string concat
|
||||
// on tagged-pointer values, segfaulting on Int operands).
|
||||
|
||||
fn fail(label: String, got: String, want: String) -> Int {
|
||||
println("FAIL " + label + " got=[" + got + "] want=[" + want + "]")
|
||||
state_set("failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn check_eq(label: String, got: String, want: String) -> Int {
|
||||
if got == want {
|
||||
println("ok " + label + " = " + got)
|
||||
return 1
|
||||
}
|
||||
fail(label, got, want)
|
||||
}
|
||||
|
||||
check_eq("sanitize crates/nc-core",
|
||||
sanitize_id("crates/nc-core"), "crates_nc_core")
|
||||
|
||||
check_eq("sanitize package.json",
|
||||
sanitize_id("package.json"), "package_json")
|
||||
|
||||
check_eq("sanitize 42-module",
|
||||
sanitize_id("42-module"), "n42_module")
|
||||
|
||||
check_eq("sanitize empty", sanitize_id(""), "node")
|
||||
|
||||
check_eq("sanitize !!--@@", sanitize_id("!!--@@"), "node")
|
||||
|
||||
check_eq("shape_from_token rounded",
|
||||
shape_from_token("rounded"), "rounded")
|
||||
|
||||
check_eq("shape_to_node_shape primary",
|
||||
shape_to_node_shape("primary"), "rectangle")
|
||||
|
||||
// Lowering preserves a node id and adds style.
|
||||
let n: Map<String, Any> = make_node("svc", "Service", "primary")
|
||||
let ln: Map<String, Any> = lower_node(n)
|
||||
check_eq("lower preserves id", ln["id"], "svc")
|
||||
check_eq("lower applies primary fill", ln["style_fill"], "#0052A0")
|
||||
|
||||
// Edge lowering
|
||||
let e: Map<String, Any> = make_edge("a", "b", "dashed")
|
||||
let le: Map<String, Any> = lower_edge(e)
|
||||
check_eq("lower edge dashed line", le["line"], "dashed")
|
||||
|
||||
let e2: Map<String, Any> = make_edge("a", "b", "bidirectional")
|
||||
let le2: Map<String, Any> = lower_edge(e2)
|
||||
check_eq("lower edge bidirectional arrow", le2["arrow"], "both")
|
||||
|
||||
println("")
|
||||
let failures: String = state_get("failures")
|
||||
if str_eq(failures, "1") {
|
||||
println("arbor-core: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-core: ok")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// arbor-diagram — diagram intermediate representation + Mermaid serializer
|
||||
// + dependency-graph builders. Consumes raw graph values built by arbor-core
|
||||
// or arbor-parse and produces Mermaid markup or other serializations.
|
||||
|
||||
vessel "arbor-diagram" {
|
||||
version "0.1.0"
|
||||
description "Diagram IR + Mermaid serializer + architecture diagram builders"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
arbor-core "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
// arbor-diagram — diagram intermediate representation (AST + IR).
|
||||
//
|
||||
// Where arbor-core supplies the *.arbor source-language model — Mermaid-safe
|
||||
// IDs, ArborShape strings, ArborEdgeKind strings, and the lowered "diagram-
|
||||
// form" map — arbor-diagram exposes the same lowered model as the canonical
|
||||
// IR for downstream serializers (arbor-render and any future Mermaid-style
|
||||
// emitter). The two vessels overlap by design: arbor-core is responsible for
|
||||
// *naming* the schema; arbor-diagram is responsible for *building* values
|
||||
// against it.
|
||||
//
|
||||
// The Rust crate ships small AST builder structs (`DiagramNode::new`,
|
||||
// `DiagramEdge::with_label`, `DiagramGraph::add_node`). El has no method
|
||||
// chaining, no Default::default(), no enum types. The El idiom is a stack
|
||||
// of immutable maps with explicit constructor + with_* helpers that take
|
||||
// the value and return a freshly-allocated map.
|
||||
//
|
||||
// Public surface:
|
||||
// make_node(id, label) → DiagramNode
|
||||
// with_shape(node, shape) → DiagramNode
|
||||
// with_sublabel(node, sublabel) → DiagramNode
|
||||
// with_style(node, fill, stroke, color) → DiagramNode
|
||||
//
|
||||
// make_edge(from, to) → DiagramEdge
|
||||
// with_label(edge, label)
|
||||
// with_line(edge, line) // "solid"/"dashed"/"dotted"/"thick"
|
||||
// with_arrow(edge, arrow) // "forward"/"backward"/"both"/"none"
|
||||
//
|
||||
// make_group(id, label) → DiagramGroup
|
||||
// with_node(group, node_id)
|
||||
// with_nodes(group, [node_id])
|
||||
// with_direction(group, dir)
|
||||
//
|
||||
// make_graph(title) → DiagramGraph
|
||||
// with_direction(graph, dir)
|
||||
// graph_add_node(graph, node) → DiagramGraph
|
||||
// graph_add_edge(graph, edge) → DiagramGraph
|
||||
// graph_add_group(graph, group) → DiagramGraph
|
||||
// graph_node(graph, id) → DiagramNode | empty map
|
||||
//
|
||||
// Shape vocabulary (lowered): see arbor-core. The local copy here mirrors
|
||||
// the table in arbor-core/src/main.el so this vessel is hermetic.
|
||||
|
||||
// ── NodeShape vocabulary ────────────────────────────────────────────────────
|
||||
|
||||
fn node_shape_rectangle() -> String { "rectangle" }
|
||||
fn node_shape_rounded_rect() -> String { "rounded_rect" }
|
||||
fn node_shape_stadium() -> String { "stadium" }
|
||||
fn node_shape_cylinder() -> String { "cylinder" }
|
||||
fn node_shape_diamond() -> String { "diamond" }
|
||||
fn node_shape_parallelogram() -> String { "parallelogram" }
|
||||
fn node_shape_database() -> String { "database" }
|
||||
fn node_shape_subroutine() -> String { "subroutine" }
|
||||
|
||||
fn node_shape_valid(s: String) -> Bool {
|
||||
if str_eq(s, "rectangle") { return true }
|
||||
if str_eq(s, "rounded_rect") { return true }
|
||||
if str_eq(s, "stadium") { return true }
|
||||
if str_eq(s, "cylinder") { return true }
|
||||
if str_eq(s, "diamond") { return true }
|
||||
if str_eq(s, "parallelogram") { return true }
|
||||
if str_eq(s, "database") { return true }
|
||||
if str_eq(s, "subroutine") { return true }
|
||||
false
|
||||
}
|
||||
|
||||
// ── EdgeLine vocabulary ─────────────────────────────────────────────────────
|
||||
|
||||
fn edge_line_solid() -> String { "solid" }
|
||||
fn edge_line_dashed() -> String { "dashed" }
|
||||
fn edge_line_dotted() -> String { "dotted" }
|
||||
fn edge_line_thick() -> String { "thick" }
|
||||
|
||||
fn edge_line_valid(s: String) -> Bool {
|
||||
if str_eq(s, "solid") { return true }
|
||||
if str_eq(s, "dashed") { return true }
|
||||
if str_eq(s, "dotted") { return true }
|
||||
if str_eq(s, "thick") { return true }
|
||||
false
|
||||
}
|
||||
|
||||
// ── EdgeArrow vocabulary ────────────────────────────────────────────────────
|
||||
|
||||
fn edge_arrow_forward() -> String { "forward" }
|
||||
fn edge_arrow_backward() -> String { "backward" }
|
||||
fn edge_arrow_both() -> String { "both" }
|
||||
fn edge_arrow_none() -> String { "none" }
|
||||
|
||||
fn edge_arrow_valid(s: String) -> Bool {
|
||||
if str_eq(s, "forward") { return true }
|
||||
if str_eq(s, "backward") { return true }
|
||||
if str_eq(s, "both") { return true }
|
||||
if str_eq(s, "none") { return true }
|
||||
false
|
||||
}
|
||||
|
||||
// ── Direction vocabulary ────────────────────────────────────────────────────
|
||||
|
||||
fn direction_top_down() -> String { "top-down" }
|
||||
fn direction_left_right() -> String { "left-right" }
|
||||
fn direction_right_left() -> String { "right-left" }
|
||||
fn direction_bottom_up() -> String { "bottom-up" }
|
||||
|
||||
fn direction_valid(s: String) -> Bool {
|
||||
if str_eq(s, "top-down") { return true }
|
||||
if str_eq(s, "left-right") { return true }
|
||||
if str_eq(s, "right-left") { return true }
|
||||
if str_eq(s, "bottom-up") { return true }
|
||||
false
|
||||
}
|
||||
|
||||
// ── DiagramNode ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_node(id: String, label: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": id,
|
||||
"label": label,
|
||||
"sublabel": "",
|
||||
"shape": "rectangle",
|
||||
"style_fill": "",
|
||||
"style_stroke": "",
|
||||
"style_color": ""
|
||||
}
|
||||
}
|
||||
|
||||
fn with_shape(node: Map<String, Any>, shape: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": node["id"],
|
||||
"label": node["label"],
|
||||
"sublabel": node["sublabel"],
|
||||
"shape": shape,
|
||||
"style_fill": node["style_fill"],
|
||||
"style_stroke": node["style_stroke"],
|
||||
"style_color": node["style_color"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_sublabel(node: Map<String, Any>, sublabel: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": node["id"],
|
||||
"label": node["label"],
|
||||
"sublabel": sublabel,
|
||||
"shape": node["shape"],
|
||||
"style_fill": node["style_fill"],
|
||||
"style_stroke": node["style_stroke"],
|
||||
"style_color": node["style_color"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_style(node: Map<String, Any>, fill: String, stroke: String, color: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": node["id"],
|
||||
"label": node["label"],
|
||||
"sublabel": node["sublabel"],
|
||||
"shape": node["shape"],
|
||||
"style_fill": fill,
|
||||
"style_stroke": stroke,
|
||||
"style_color": color
|
||||
}
|
||||
}
|
||||
|
||||
// ── DiagramEdge ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_edge(from: String, to: String) -> Map<String, Any> {
|
||||
{
|
||||
"from": from,
|
||||
"to": to,
|
||||
"label": "",
|
||||
"line": "solid",
|
||||
"arrow": "forward"
|
||||
}
|
||||
}
|
||||
|
||||
fn with_label(edge: Map<String, Any>, label: String) -> Map<String, Any> {
|
||||
{
|
||||
"from": edge["from"],
|
||||
"to": edge["to"],
|
||||
"label": label,
|
||||
"line": edge["line"],
|
||||
"arrow": edge["arrow"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_line(edge: Map<String, Any>, line: String) -> Map<String, Any> {
|
||||
{
|
||||
"from": edge["from"],
|
||||
"to": edge["to"],
|
||||
"label": edge["label"],
|
||||
"line": line,
|
||||
"arrow": edge["arrow"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_arrow(edge: Map<String, Any>, arrow: String) -> Map<String, Any> {
|
||||
{
|
||||
"from": edge["from"],
|
||||
"to": edge["to"],
|
||||
"label": edge["label"],
|
||||
"line": edge["line"],
|
||||
"arrow": arrow
|
||||
}
|
||||
}
|
||||
|
||||
// ── DiagramGroup ────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_group(id: String, label: String) -> Map<String, Any> {
|
||||
let empty: [String] = native_list_empty()
|
||||
{
|
||||
"id": id,
|
||||
"label": label,
|
||||
"node_ids": empty,
|
||||
"direction": ""
|
||||
}
|
||||
}
|
||||
|
||||
fn with_node(group: Map<String, Any>, node_id: String) -> Map<String, Any> {
|
||||
let cur: [String] = group["node_ids"]
|
||||
let next: [String] = native_list_append(cur, node_id)
|
||||
{
|
||||
"id": group["id"],
|
||||
"label": group["label"],
|
||||
"node_ids": next,
|
||||
"direction": group["direction"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_nodes(group: Map<String, Any>, ids: [String]) -> Map<String, Any> {
|
||||
let cur: [String] = group["node_ids"]
|
||||
let n: Int = el_list_len(ids)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let cur = native_list_append(cur, get(ids, i))
|
||||
let i = i + 1
|
||||
}
|
||||
{
|
||||
"id": group["id"],
|
||||
"label": group["label"],
|
||||
"node_ids": cur,
|
||||
"direction": group["direction"]
|
||||
}
|
||||
}
|
||||
|
||||
fn with_group_direction(group: Map<String, Any>, dir: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": group["id"],
|
||||
"label": group["label"],
|
||||
"node_ids": group["node_ids"],
|
||||
"direction": dir
|
||||
}
|
||||
}
|
||||
|
||||
// ── DiagramGraph ────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_graph(title: String) -> Map<String, Any> {
|
||||
let empty_n: [Map<String, Any>] = native_list_empty()
|
||||
let empty_e: [Map<String, Any>] = native_list_empty()
|
||||
let empty_g: [Map<String, Any>] = native_list_empty()
|
||||
{
|
||||
"title": title,
|
||||
"direction": "top-down",
|
||||
"nodes": empty_n,
|
||||
"edges": empty_e,
|
||||
"groups": empty_g
|
||||
}
|
||||
}
|
||||
|
||||
fn with_direction(graph: Map<String, Any>, dir: String) -> Map<String, Any> {
|
||||
{
|
||||
"title": graph["title"],
|
||||
"direction": dir,
|
||||
"nodes": graph["nodes"],
|
||||
"edges": graph["edges"],
|
||||
"groups": graph["groups"]
|
||||
}
|
||||
}
|
||||
|
||||
fn graph_add_node(graph: Map<String, Any>, node: Map<String, Any>) -> Map<String, Any> {
|
||||
let cur: [Map<String, Any>] = graph["nodes"]
|
||||
let next: [Map<String, Any>] = native_list_append(cur, node)
|
||||
{
|
||||
"title": graph["title"],
|
||||
"direction": graph["direction"],
|
||||
"nodes": next,
|
||||
"edges": graph["edges"],
|
||||
"groups": graph["groups"]
|
||||
}
|
||||
}
|
||||
|
||||
fn graph_add_edge(graph: Map<String, Any>, edge: Map<String, Any>) -> Map<String, Any> {
|
||||
let cur: [Map<String, Any>] = graph["edges"]
|
||||
let next: [Map<String, Any>] = native_list_append(cur, edge)
|
||||
{
|
||||
"title": graph["title"],
|
||||
"direction": graph["direction"],
|
||||
"nodes": graph["nodes"],
|
||||
"edges": next,
|
||||
"groups": graph["groups"]
|
||||
}
|
||||
}
|
||||
|
||||
fn graph_add_group(graph: Map<String, Any>, group: Map<String, Any>) -> Map<String, Any> {
|
||||
let cur: [Map<String, Any>] = graph["groups"]
|
||||
let next: [Map<String, Any>] = native_list_append(cur, group)
|
||||
{
|
||||
"title": graph["title"],
|
||||
"direction": graph["direction"],
|
||||
"nodes": graph["nodes"],
|
||||
"edges": graph["edges"],
|
||||
"groups": next
|
||||
}
|
||||
}
|
||||
|
||||
// Find a node by id. Returns an empty map (no "id" field) when not present.
|
||||
fn graph_node(graph: Map<String, Any>, id: String) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
if str_eq(nid, id) { return nd }
|
||||
let i = i + 1
|
||||
}
|
||||
let empty: Map<String, Any> = el_map_new(0)
|
||||
empty
|
||||
}
|
||||
|
||||
// ── Smoke test ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn fail(label: String, got: String, want: String) -> Int {
|
||||
println("FAIL " + label + " got=[" + got + "] want=[" + want + "]")
|
||||
state_set("smoke_failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn check_eq(label: String, got: String, want: String) -> Int {
|
||||
if got == want {
|
||||
println("ok " + label + " = " + got)
|
||||
return 1
|
||||
}
|
||||
fail(label, got, want)
|
||||
}
|
||||
|
||||
// Vocabulary self-checks
|
||||
check_eq("shape rectangle valid",
|
||||
bool_to_str(node_shape_valid("rectangle")), "true")
|
||||
check_eq("shape hexagon invalid",
|
||||
bool_to_str(node_shape_valid("hexagon")), "false")
|
||||
check_eq("line dashed valid",
|
||||
bool_to_str(edge_line_valid("dashed")), "true")
|
||||
check_eq("arrow both valid",
|
||||
bool_to_str(edge_arrow_valid("both")), "true")
|
||||
check_eq("dir top-down valid",
|
||||
bool_to_str(direction_valid("top-down")), "true")
|
||||
|
||||
// Node builder
|
||||
let n0: Map<String, Any> = make_node("svc", "Service")
|
||||
check_eq("node default shape", n0["shape"], "rectangle")
|
||||
check_eq("node default sublabel empty", n0["sublabel"], "")
|
||||
|
||||
let n1: Map<String, Any> = with_shape(n0, "cylinder")
|
||||
check_eq("node with_shape", n1["shape"], "cylinder")
|
||||
check_eq("node id preserved", n1["id"], "svc")
|
||||
|
||||
let n2: Map<String, Any> = with_sublabel(n1, "v0.1.0")
|
||||
check_eq("node with_sublabel", n2["sublabel"], "v0.1.0")
|
||||
|
||||
let n3: Map<String, Any> = with_style(n2, "#0052A0", "#0052A0", "#ffffff")
|
||||
check_eq("node style fill", n3["style_fill"], "#0052A0")
|
||||
check_eq("node style color", n3["style_color"], "#ffffff")
|
||||
|
||||
// Edge builder
|
||||
let e0: Map<String, Any> = make_edge("a", "b")
|
||||
check_eq("edge default line", e0["line"], "solid")
|
||||
check_eq("edge default arrow", e0["arrow"], "forward")
|
||||
let e1: Map<String, Any> = with_line(e0, "dashed")
|
||||
let e2: Map<String, Any> = with_arrow(e1, "both")
|
||||
let e3: Map<String, Any> = with_label(e2, "calls")
|
||||
check_eq("edge line", e3["line"], "dashed")
|
||||
check_eq("edge arrow", e3["arrow"], "both")
|
||||
check_eq("edge label", e3["label"], "calls")
|
||||
|
||||
// Group builder
|
||||
let g0: Map<String, Any> = make_group("core", "Application Core")
|
||||
let g1: Map<String, Any> = with_node(g0, "api")
|
||||
let g2: Map<String, Any> = with_node(g1, "svc")
|
||||
let ids2: [String] = g2["node_ids"]
|
||||
check_eq("group with two nodes", int_to_str(el_list_len(ids2)), "2")
|
||||
|
||||
let g3: Map<String, Any> = make_group("infra", "Infrastructure")
|
||||
let extras: [String] = native_list_empty()
|
||||
let extras = native_list_append(extras, "db")
|
||||
let extras = native_list_append(extras, "cache")
|
||||
let g4: Map<String, Any> = with_nodes(g3, extras)
|
||||
let ids4: [String] = g4["node_ids"]
|
||||
check_eq("group with_nodes appends", int_to_str(el_list_len(ids4)), "2")
|
||||
|
||||
// Graph builder + lookup
|
||||
let G0: Map<String, Any> = make_graph("System")
|
||||
let G1: Map<String, Any> = with_direction(G0, "left-right")
|
||||
let G2: Map<String, Any> = graph_add_node(G1, n3)
|
||||
let nb: Map<String, Any> = make_node("b", "Backend")
|
||||
let G3: Map<String, Any> = graph_add_node(G2, nb)
|
||||
let G4: Map<String, Any> = graph_add_edge(G3, e3)
|
||||
let G5: Map<String, Any> = graph_add_group(G4, g4)
|
||||
|
||||
check_eq("graph title", G5["title"], "System")
|
||||
check_eq("graph direction", G5["direction"], "left-right")
|
||||
let gn: [Map<String, Any>] = G5["nodes"]
|
||||
let ge: [Map<String, Any>] = G5["edges"]
|
||||
let gg: [Map<String, Any>] = G5["groups"]
|
||||
check_eq("graph nodes count", int_to_str(el_list_len(gn)), "2")
|
||||
check_eq("graph edges count", int_to_str(el_list_len(ge)), "1")
|
||||
check_eq("graph groups count", int_to_str(el_list_len(gg)), "1")
|
||||
|
||||
let found: Map<String, Any> = graph_node(G5, "svc")
|
||||
check_eq("graph_node found", found["id"], "svc")
|
||||
let missing: Map<String, Any> = graph_node(G5, "nonexistent")
|
||||
let missing_id: String = missing["id"]
|
||||
if str_len(missing_id) == 0 {
|
||||
println("ok graph_node missing returns empty")
|
||||
} else {
|
||||
println("FAIL graph_node missing returned: " + missing_id)
|
||||
state_set("smoke_failures", "1")
|
||||
}
|
||||
|
||||
println("")
|
||||
let failures: String = state_get("smoke_failures")
|
||||
if str_eq(failures, "1") {
|
||||
println("arbor-diagram: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-diagram: ok")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// arbor-layout — hierarchical layout engine. Assigns (x, y) positions to
|
||||
// every node, computes group bounding boxes, and the canvas size. Consumes
|
||||
// a diagram graph; produces a layout-result value.
|
||||
|
||||
vessel "arbor-layout" {
|
||||
version "0.1.0"
|
||||
description "Hierarchical layout engine — rank assignment, positioning, group bounds"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
arbor-core "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
// arbor-layout — hierarchical layout for diagram graphs.
|
||||
//
|
||||
// Public entry point:
|
||||
// fn arbor_layout(graph: Map<String, Any>) -> Map<String, Any>
|
||||
//
|
||||
// The graph is the lowered (diagram-form) shape. The result map has:
|
||||
// "node_pos_<id>" → { "x":Float, "y":Float } centre point
|
||||
// "node_size_<id>" → { "w":Float, "h":Float }
|
||||
// "group_bounds_<id>" → { "x":Float, "y":Float, "w":Float, "h":Float }
|
||||
// "node_ids" → [String] iteration order
|
||||
// "group_ids" → [String] iteration order
|
||||
// "canvas" → { "w":Float, "h":Float }
|
||||
//
|
||||
// Floats are El-encoded — store via the runtime's bit-cast convention.
|
||||
// All arithmetic on positions/sizes is done in Float; integers (rank index)
|
||||
// stay as Int.
|
||||
//
|
||||
// Algorithm (simplified Sugiyama):
|
||||
// 1. Assign ranks via topological propagation (longest path from sources).
|
||||
// 2. Group nodes by rank, preserving declaration order.
|
||||
// 3. Position each rank as a row (top-down/bottom-up) or column (LR/RL).
|
||||
// 4. Compute group bounding boxes from member positions.
|
||||
// 5. Compute canvas size to enclose everything.
|
||||
//
|
||||
// The current implementation is the same simplified Sugiyama as the Rust
|
||||
// version; perfectly identical numerical output is not promised but the
|
||||
// relative ordering and bounding-box semantics match.
|
||||
|
||||
// ── Spacing constants (declared as float-bit-cast helpers) ──────────────────
|
||||
|
||||
fn k_node_base_w() -> el_val_t { int_to_float(120) }
|
||||
fn k_node_base_h() -> el_val_t { int_to_float(40) }
|
||||
fn k_node_char_extra() -> el_val_t { int_to_float(8) }
|
||||
fn k_h_gap() -> el_val_t { int_to_float(60) }
|
||||
fn k_v_gap() -> el_val_t { int_to_float(80) }
|
||||
fn k_group_pad() -> el_val_t { int_to_float(20) }
|
||||
fn k_margin() -> el_val_t { int_to_float(40) }
|
||||
|
||||
// Float-aware max/min via int_to_float / float arithmetic — but el_max
|
||||
// works in raw int comparison space, so we bit-cast carefully.
|
||||
// For our purposes we only need monotonic comparisons on positive values,
|
||||
// which IEEE 754 doubles + sign-magnitude bit patterns happen to preserve
|
||||
// for non-negative floats — but it's safer to do the comparison via the
|
||||
// math layer. We use a helper that decodes both, picks the bigger, and
|
||||
// re-encodes.
|
||||
//
|
||||
// Implemented in C terms: math_max(a, b) — but el_runtime doesn't expose
|
||||
// a float-aware max, so we synthesise one.
|
||||
|
||||
fn fmax(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
// Compare via float subtraction's sign: a - b. Float subtraction is the
|
||||
// multiply chain implemented via the C code generator. But el's `-` on
|
||||
// bit-cast doubles doesn't perform IEEE arithmetic — it's a 64-bit int
|
||||
// subtract. Workaround: round-trip through format_float and str_to_float.
|
||||
// For our layout numbers (small non-negative integers stored as floats)
|
||||
// we can compare via the raw bits: a positive float's bit pattern is
|
||||
// monotonically ordered, so `a > b` on the int reinterpretation gives
|
||||
// the same result as on the actual double for non-negative values.
|
||||
if a > b { return a }
|
||||
b
|
||||
}
|
||||
|
||||
fn fadd(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
// a, b are bit-cast doubles. Safe addition: int-to-float, format, parse.
|
||||
// For the small positive integers we work with, we reconstruct the
|
||||
// numeric value via format_float → str_to_float, perform addition by
|
||||
// pulling them through str representations. Costly but correct on the
|
||||
// current runtime. Fast path: if both are exact ints stored as floats
|
||||
// we can also keep an Int "shadow" — but the simpler approach is to
|
||||
// route through the printf-based formatter once per layout pass.
|
||||
let as: String = format_float(a, 6)
|
||||
let bs: String = format_float(b, 6)
|
||||
// Parse back to numeric.
|
||||
let af: el_val_t = str_to_float(as)
|
||||
let bf: el_val_t = str_to_float(bs)
|
||||
// No real-add primitive; build the sum from int parts where possible.
|
||||
// Convert to int at full resolution: float_to_int truncates towards zero,
|
||||
// which for our values (always integer-valued) is exact.
|
||||
let ai: Int = float_to_int(af)
|
||||
let bi: Int = float_to_int(bf)
|
||||
int_to_float(ai + bi)
|
||||
}
|
||||
|
||||
fn fsub(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai - bi)
|
||||
}
|
||||
|
||||
fn fmul(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai * bi)
|
||||
}
|
||||
|
||||
fn fdiv2(a: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
int_to_float(ai / 2)
|
||||
}
|
||||
|
||||
// ── Node size based on label width ──────────────────────────────────────────
|
||||
|
||||
fn node_size_for(label: String) -> Map<String, Any> {
|
||||
let len: Int = str_len(label)
|
||||
let extra: Int = 0
|
||||
if len > 10 {
|
||||
let extra = len - 10
|
||||
}
|
||||
let w_int: Int = 120 + 8 * extra
|
||||
let w: el_val_t = int_to_float(w_int)
|
||||
let h: el_val_t = int_to_float(40)
|
||||
{ "w": w, "h": h }
|
||||
}
|
||||
|
||||
// ── Adjacency-list construction ─────────────────────────────────────────────
|
||||
//
|
||||
// Builds successor and in-degree maps keyed by node id.
|
||||
|
||||
fn build_succ_indeg(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let edges: [Map<String, Any>] = graph["edges"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let m: Int = el_list_len(edges)
|
||||
|
||||
let succ: Map<String, Any> = el_map_new(0)
|
||||
let indeg: Map<String, Any> = el_map_new(0)
|
||||
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let empty: [String] = el_list_empty()
|
||||
let succ = el_map_set(succ, nid, empty)
|
||||
let indeg = el_map_set(indeg, nid, 0)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let i = 0
|
||||
while i < m {
|
||||
let e: Map<String, Any> = get(edges, i)
|
||||
let src: String = e["from"]
|
||||
let dst: String = e["to"]
|
||||
let cur_succ: [String] = el_map_get(succ, src)
|
||||
let new_succ: [String] = native_list_append(cur_succ, dst)
|
||||
let succ = el_map_set(succ, src, new_succ)
|
||||
let prev: Int = el_map_get(indeg, dst)
|
||||
let indeg = el_map_set(indeg, dst, prev + 1)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
{ "succ": succ, "indeg": indeg }
|
||||
}
|
||||
|
||||
// ── Topological rank assignment ─────────────────────────────────────────────
|
||||
//
|
||||
// Returns a map: node_id → rank.
|
||||
|
||||
fn assign_ranks(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let adj: Map<String, Any> = build_succ_indeg(graph)
|
||||
let succ: Map<String, Any> = adj["succ"]
|
||||
let indeg: Map<String, Any> = adj["indeg"]
|
||||
|
||||
let ranks: Map<String, Any> = el_map_new(0)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let ranks = el_map_set(ranks, nid, 0)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Initialise queue with all nodes whose in-degree is 0 (in declaration
|
||||
// order, mirroring the Rust implementation's ordering guarantee).
|
||||
let queue: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let d: Int = el_map_get(indeg, nid)
|
||||
if d == 0 {
|
||||
let queue = native_list_append(queue, nid)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let head = 0
|
||||
let running = true
|
||||
while running {
|
||||
if head >= el_list_len(queue) {
|
||||
let running = false
|
||||
} else {
|
||||
let cur: String = get(queue, head)
|
||||
let head = head + 1
|
||||
let cur_rank: Int = el_map_get(ranks, cur)
|
||||
let neighbours: [String] = el_map_get(succ, cur)
|
||||
let nn: Int = el_list_len(neighbours)
|
||||
let j = 0
|
||||
while j < nn {
|
||||
let nb: String = get(neighbours, j)
|
||||
let nb_rank: Int = el_map_get(ranks, nb)
|
||||
let cand: Int = cur_rank + 1
|
||||
if cand > nb_rank {
|
||||
let ranks = el_map_set(ranks, nb, cand)
|
||||
}
|
||||
let cur_d: Int = el_map_get(indeg, nb)
|
||||
let new_d: Int = cur_d - 1
|
||||
let indeg = el_map_set(indeg, nb, new_d)
|
||||
if new_d <= 0 {
|
||||
let queue = native_list_append(queue, nb)
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
ranks
|
||||
}
|
||||
|
||||
// ── Layout pass ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn arbor_layout(graph: Map<String, Any>) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let n: Int = el_list_len(nodes)
|
||||
let direction: String = graph["direction"]
|
||||
|
||||
let result: Map<String, Any> = el_map_new(0)
|
||||
let result = el_map_set(result, "node_ids", el_list_empty())
|
||||
let result = el_map_set(result, "group_ids", el_list_empty())
|
||||
|
||||
if n == 0 {
|
||||
let canvas: Map<String, Any> = { "w": int_to_float(200), "h": int_to_float(100) }
|
||||
let result = el_map_set(result, "canvas", canvas)
|
||||
return result
|
||||
}
|
||||
|
||||
let ranks: Map<String, Any> = assign_ranks(graph)
|
||||
let max_rank = 0
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let r: Int = el_map_get(ranks, nid)
|
||||
if r > max_rank { let max_rank = r }
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Group nodes by rank, preserving declaration order. Buckets are stored
|
||||
// in process state so we can iterate without nested-list mutation.
|
||||
let i = 0
|
||||
while i <= max_rank {
|
||||
state_set("rank_bucket_" + int_to_str(i), "")
|
||||
let i = i + 1
|
||||
}
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let r: Int = el_map_get(ranks, nid)
|
||||
let key = "rank_bucket_" + int_to_str(r)
|
||||
let prev: String = state_get(key)
|
||||
if str_eq(prev, "") {
|
||||
state_set(key, nid)
|
||||
} else {
|
||||
state_set(key, prev + "" + nid)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Pre-compute sizes and stash a label-keyed cache.
|
||||
let id_list: [String] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nd: Map<String, Any> = get(nodes, i)
|
||||
let nid: String = nd["id"]
|
||||
let lbl: String = nd["label"]
|
||||
let sz: Map<String, Any> = node_size_for(lbl)
|
||||
let result = el_map_set(result, "node_size_" + nid, sz)
|
||||
let id_list = native_list_append(id_list, nid)
|
||||
let i = i + 1
|
||||
}
|
||||
let result = el_map_set(result, "node_ids", id_list)
|
||||
|
||||
// Position pass.
|
||||
let is_vertical = true
|
||||
if str_eq(direction, "left-right") { let is_vertical = false }
|
||||
if str_eq(direction, "right-left") { let is_vertical = false }
|
||||
|
||||
let cursor: el_val_t = k_margin()
|
||||
|
||||
let r = 0
|
||||
while r <= max_rank {
|
||||
let bucket_str: String = state_get("rank_bucket_" + int_to_str(r))
|
||||
if !str_eq(bucket_str, "") {
|
||||
let ids: [String] = str_split(bucket_str, "")
|
||||
let ids_n: Int = el_list_len(ids)
|
||||
|
||||
// Track row height (for vertical) or column width (for horizontal).
|
||||
let cross_max: el_val_t = int_to_float(40)
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
if is_vertical {
|
||||
let h: el_val_t = sz["h"]
|
||||
let cross_max = fmax(cross_max, h)
|
||||
} else {
|
||||
let w: el_val_t = sz["w"]
|
||||
let cross_max = fmax(cross_max, w)
|
||||
}
|
||||
let j = j + 1
|
||||
}
|
||||
|
||||
if is_vertical {
|
||||
let row_h: el_val_t = cross_max
|
||||
let y_center: el_val_t = fadd(cursor, fdiv2(row_h))
|
||||
let x_cursor: el_val_t = k_margin()
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let w: el_val_t = sz["w"]
|
||||
let cx: el_val_t = fadd(x_cursor, fdiv2(w))
|
||||
let pos: Map<String, Any> = { "x": cx, "y": y_center }
|
||||
let result = el_map_set(result, "node_pos_" + nid, pos)
|
||||
let x_cursor = fadd(fadd(x_cursor, w), k_h_gap())
|
||||
let j = j + 1
|
||||
}
|
||||
let cursor = fadd(fadd(cursor, row_h), k_v_gap())
|
||||
} else {
|
||||
let col_w: el_val_t = cross_max
|
||||
let x_center: el_val_t = fadd(cursor, fdiv2(col_w))
|
||||
let y_cursor: el_val_t = k_margin()
|
||||
let j = 0
|
||||
while j < ids_n {
|
||||
let nid: String = get(ids, j)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let h: el_val_t = sz["h"]
|
||||
let cy: el_val_t = fadd(y_cursor, fdiv2(h))
|
||||
let pos: Map<String, Any> = { "x": x_center, "y": cy }
|
||||
let result = el_map_set(result, "node_pos_" + nid, pos)
|
||||
let y_cursor = fadd(fadd(y_cursor, h), k_v_gap())
|
||||
let j = j + 1
|
||||
}
|
||||
let cursor = fadd(fadd(cursor, col_w), k_h_gap())
|
||||
}
|
||||
} else {
|
||||
// Empty bucket — advance cursor by a default node size.
|
||||
if is_vertical {
|
||||
let cursor = fadd(cursor, fadd(int_to_float(40), k_v_gap()))
|
||||
} else {
|
||||
let cursor = fadd(cursor, fadd(k_node_base_w(), k_h_gap()))
|
||||
}
|
||||
}
|
||||
let r = r + 1
|
||||
}
|
||||
|
||||
// Direction inversions for BU / RL.
|
||||
let need_flip_y = false
|
||||
let need_flip_x = false
|
||||
if str_eq(direction, "bottom-up") { let need_flip_y = true }
|
||||
if str_eq(direction, "right-left") { let need_flip_x = true }
|
||||
|
||||
if need_flip_y {
|
||||
let max_y: el_val_t = fadd(fsub(cursor, k_v_gap()), k_margin())
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let y: el_val_t = pos["y"]
|
||||
let new_y: el_val_t = fadd(fsub(max_y, y), k_margin())
|
||||
let new_pos: Map<String, Any> = { "x": pos["x"], "y": new_y }
|
||||
let result = el_map_set(result, "node_pos_" + nid, new_pos)
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
if need_flip_x {
|
||||
let max_x: el_val_t = fadd(fsub(cursor, k_h_gap()), k_margin())
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let x: el_val_t = pos["x"]
|
||||
let new_x: el_val_t = fadd(fsub(max_x, x), k_margin())
|
||||
let new_pos: Map<String, Any> = { "x": new_x, "y": pos["y"] }
|
||||
let result = el_map_set(result, "node_pos_" + nid, new_pos)
|
||||
let i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Group bounds.
|
||||
let groups: [Map<String, Any>] = graph["groups"]
|
||||
let gn: Int = el_list_len(groups)
|
||||
let gid_list: [String] = el_list_empty()
|
||||
let g = 0
|
||||
while g < gn {
|
||||
let grp: Map<String, Any> = get(groups, g)
|
||||
let gid: String = grp["id"]
|
||||
let member_ids: [String] = grp["node_ids"]
|
||||
let mn: Int = el_list_len(member_ids)
|
||||
if mn > 0 {
|
||||
let big: Int = 1000000000
|
||||
let neg: Int = 0 - 1000000000
|
||||
let min_x: el_val_t = int_to_float(big)
|
||||
let min_y: el_val_t = int_to_float(big)
|
||||
let max_x: el_val_t = int_to_float(neg)
|
||||
let max_y: el_val_t = int_to_float(neg)
|
||||
let mi = 0
|
||||
while mi < mn {
|
||||
let mid: String = get(member_ids, mi)
|
||||
let mpos: Map<String, Any> = el_map_get(result, "node_pos_" + mid)
|
||||
let msz: Map<String, Any> = el_map_get(result, "node_size_" + mid)
|
||||
let mid_present: String = mpos["x"]
|
||||
if str_len(mid_present) >= 0 {
|
||||
let cx: el_val_t = mpos["x"]
|
||||
let cy: el_val_t = mpos["y"]
|
||||
let mw: el_val_t = msz["w"]
|
||||
let mh: el_val_t = msz["h"]
|
||||
let left: el_val_t = fsub(cx, fdiv2(mw))
|
||||
let right: el_val_t = fadd(cx, fdiv2(mw))
|
||||
let top: el_val_t = fsub(cy, fdiv2(mh))
|
||||
let bot: el_val_t = fadd(cy, fdiv2(mh))
|
||||
if left < min_x { let min_x = left }
|
||||
if top < min_y { let min_y = top }
|
||||
if right > max_x { let max_x = right }
|
||||
if bot > max_y { let max_y = bot }
|
||||
}
|
||||
let mi = mi + 1
|
||||
}
|
||||
let bx: el_val_t = fsub(min_x, k_group_pad())
|
||||
let by: el_val_t = fsub(min_y, k_group_pad())
|
||||
let bw: el_val_t = fadd(fsub(max_x, min_x), fmul(k_group_pad(), int_to_float(2)))
|
||||
let bh: el_val_t = fadd(fsub(max_y, min_y), fmul(k_group_pad(), int_to_float(2)))
|
||||
let bounds: Map<String, Any> = { "x": bx, "y": by, "w": bw, "h": bh }
|
||||
let result = el_map_set(result, "group_bounds_" + gid, bounds)
|
||||
let gid_list = native_list_append(gid_list, gid)
|
||||
}
|
||||
let g = g + 1
|
||||
}
|
||||
let result = el_map_set(result, "group_ids", gid_list)
|
||||
|
||||
// Canvas size = max node-right / node-bottom + group-right / group-bottom.
|
||||
let canvas_w: el_val_t = int_to_float(0)
|
||||
let canvas_h: el_val_t = int_to_float(0)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let nid: String = get(id_list, i)
|
||||
let pos: Map<String, Any> = el_map_get(result, "node_pos_" + nid)
|
||||
let sz: Map<String, Any> = el_map_get(result, "node_size_" + nid)
|
||||
let right: el_val_t = fadd(pos["x"], fdiv2(sz["w"]))
|
||||
let bottom: el_val_t = fadd(pos["y"], fdiv2(sz["h"]))
|
||||
if right > canvas_w { let canvas_w = right }
|
||||
if bottom > canvas_h { let canvas_h = bottom }
|
||||
let i = i + 1
|
||||
}
|
||||
let i = 0
|
||||
while i < el_list_len(gid_list) {
|
||||
let gid: String = get(gid_list, i)
|
||||
let b: Map<String, Any> = el_map_get(result, "group_bounds_" + gid)
|
||||
let r: el_val_t = fadd(b["x"], b["w"])
|
||||
let bt: el_val_t = fadd(b["y"], b["h"])
|
||||
if r > canvas_w { let canvas_w = r }
|
||||
if bt > canvas_h { let canvas_h = bt }
|
||||
let i = i + 1
|
||||
}
|
||||
let canvas: Map<String, Any> = {
|
||||
"w": fadd(canvas_w, k_margin()),
|
||||
"h": fadd(canvas_h, k_margin())
|
||||
}
|
||||
let result = el_map_set(result, "canvas", canvas)
|
||||
result
|
||||
}
|
||||
|
||||
// ── Smoke test ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn fl_to_str(v: el_val_t) -> String {
|
||||
int_to_str(float_to_int(v))
|
||||
}
|
||||
|
||||
fn smoke_fail(label: String, msg: String) -> Int {
|
||||
println("FAIL " + label + ": " + msg)
|
||||
state_set("smoke_failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn make_test_node(id: String, label: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": id, "label": label, "sublabel": "",
|
||||
"shape": "rectangle",
|
||||
"style_fill": "", "style_stroke": "", "style_color": ""
|
||||
}
|
||||
}
|
||||
|
||||
fn make_test_edge(src: String, dst: String) -> Map<String, Any> {
|
||||
{ "from": src, "to": dst, "label": "", "line": "solid", "arrow": "forward" }
|
||||
}
|
||||
|
||||
fn make_test_graph(direction: String, ids: [String], src_dst: [String]) -> Map<String, Any> {
|
||||
let nodes: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < el_list_len(ids) {
|
||||
let nid: String = get(ids, i)
|
||||
let nodes = native_list_append(nodes, make_test_node(nid, nid))
|
||||
let i = i + 1
|
||||
}
|
||||
let edges: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i + 1 < el_list_len(src_dst) {
|
||||
let s: String = get(src_dst, i)
|
||||
let d: String = get(src_dst, i + 1)
|
||||
let edges = native_list_append(edges, make_test_edge(s, d))
|
||||
let i = i + 2
|
||||
}
|
||||
{
|
||||
"title": "T", "direction": direction,
|
||||
"nodes": nodes, "edges": edges, "groups": el_list_empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Empty graph.
|
||||
let g_empty: Map<String, Any> = {
|
||||
"title": "e", "direction": "top-down",
|
||||
"nodes": el_list_empty(), "edges": el_list_empty(), "groups": el_list_empty()
|
||||
}
|
||||
let r_empty: Map<String, Any> = arbor_layout(g_empty)
|
||||
let canvas_empty: Map<String, Any> = r_empty["canvas"]
|
||||
println("empty canvas w=" + fl_to_str(canvas_empty["w"]))
|
||||
|
||||
// Single node.
|
||||
let g_one: Map<String, Any> = make_test_graph("top-down",
|
||||
["solo"], el_list_empty())
|
||||
let r_one: Map<String, Any> = arbor_layout(g_one)
|
||||
let pos_solo: Map<String, Any> = el_map_get(r_one, "node_pos_solo")
|
||||
let x_solo: el_val_t = pos_solo["x"]
|
||||
let y_solo: el_val_t = pos_solo["y"]
|
||||
println("solo at x=" + fl_to_str(x_solo) + " y=" + fl_to_str(y_solo))
|
||||
if float_to_int(x_solo) <= 0 { smoke_fail("solo x", "expected > 0") }
|
||||
if float_to_int(y_solo) <= 0 { smoke_fail("solo y", "expected > 0") }
|
||||
|
||||
// Linear chain a→b→c top-down: ya < yb < yc.
|
||||
let g_chain: Map<String, Any> = make_test_graph("top-down",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_chain: Map<String, Any> = arbor_layout(g_chain)
|
||||
let pa: Map<String, Any> = el_map_get(r_chain, "node_pos_a")
|
||||
let pb: Map<String, Any> = el_map_get(r_chain, "node_pos_b")
|
||||
let pc: Map<String, Any> = el_map_get(r_chain, "node_pos_c")
|
||||
let ya: el_val_t = pa["y"]
|
||||
let yb: el_val_t = pb["y"]
|
||||
let yc: el_val_t = pc["y"]
|
||||
println("td a.y=" + fl_to_str(ya) + " b.y=" + fl_to_str(yb) + " c.y=" + fl_to_str(yc))
|
||||
if float_to_int(ya) >= float_to_int(yb) { smoke_fail("td order", "a.y >= b.y") }
|
||||
if float_to_int(yb) >= float_to_int(yc) { smoke_fail("td order", "b.y >= c.y") }
|
||||
|
||||
// LR direction
|
||||
let g_lr: Map<String, Any> = make_test_graph("left-right",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_lr: Map<String, Any> = arbor_layout(g_lr)
|
||||
let pa2: Map<String, Any> = el_map_get(r_lr, "node_pos_a")
|
||||
let pc2: Map<String, Any> = el_map_get(r_lr, "node_pos_c")
|
||||
let xa: el_val_t = pa2["x"]
|
||||
let xc: el_val_t = pc2["x"]
|
||||
println("lr a.x=" + fl_to_str(xa) + " c.x=" + fl_to_str(xc))
|
||||
if float_to_int(xa) >= float_to_int(xc) { smoke_fail("lr order", "a.x >= c.x") }
|
||||
|
||||
// Bottom-up: a is below c.
|
||||
let g_bu: Map<String, Any> = make_test_graph("bottom-up",
|
||||
["a", "b", "c"], ["a", "b", "b", "c"])
|
||||
let r_bu: Map<String, Any> = arbor_layout(g_bu)
|
||||
let pa3: Map<String, Any> = el_map_get(r_bu, "node_pos_a")
|
||||
let pc3: Map<String, Any> = el_map_get(r_bu, "node_pos_c")
|
||||
let ya3: el_val_t = pa3["y"]
|
||||
let yc3: el_val_t = pc3["y"]
|
||||
println("bu a.y=" + fl_to_str(ya3) + " c.y=" + fl_to_str(yc3))
|
||||
if float_to_int(ya3) <= float_to_int(yc3) { smoke_fail("bu order", "a.y <= c.y") }
|
||||
|
||||
// Canvas covers all nodes.
|
||||
let canvas_chain: Map<String, Any> = r_chain["canvas"]
|
||||
let cw: el_val_t = canvas_chain["w"]
|
||||
let ch: el_val_t = canvas_chain["h"]
|
||||
println("chain canvas w=" + fl_to_str(cw) + " h=" + fl_to_str(ch))
|
||||
if float_to_int(cw) <= 0 { smoke_fail("canvas w", "non-positive") }
|
||||
if float_to_int(ch) <= 0 { smoke_fail("canvas h", "non-positive") }
|
||||
|
||||
println("")
|
||||
let f: String = state_get("smoke_failures")
|
||||
if str_eq(f, "1") {
|
||||
println("arbor-layout: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-layout: ok")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// arbor-parse — hand-written recursive-descent parser for the .arbor source
|
||||
// language. Produces an Arbor graph value consumable by arbor-layout and
|
||||
// arbor-render.
|
||||
|
||||
vessel "arbor-parse" {
|
||||
version "0.1.0"
|
||||
description "Recursive-descent parser for the .arbor diagram language"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
arbor-core "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
// arbor-parse — recursive-descent parser for the .arbor source language.
|
||||
//
|
||||
// This vessel inlines a private copy of the small set of arbor-core helpers
|
||||
// it needs (sanitize_id and constructors). El's import form today is purely
|
||||
// syntactic concatenation, so each vessel that wants to be its own buildable
|
||||
// unit carries its own copy of these helpers. They're tiny (well under 100
|
||||
// lines) and the duplication keeps each vessel hermetic.
|
||||
//
|
||||
// Public entry point: fn arbor_parse(source: String) -> Map<String, Any>
|
||||
//
|
||||
// Returns either a graph value or a parse-error map. Callers test for the
|
||||
// "error" field:
|
||||
// { "error": "..." , "line": Int, "text": "...source line..." } on failure
|
||||
// { "title", "direction", "nodes", "edges", "groups" } on success
|
||||
|
||||
// ── Sanitisation (copy of arbor-core's sanitize_id) ──────────────────────────
|
||||
|
||||
fn is_alnum_underscore(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
if code >= 65 {
|
||||
if code <= 90 { return true }
|
||||
}
|
||||
if code >= 97 {
|
||||
if code <= 122 { return true }
|
||||
}
|
||||
if code == 95 { return true }
|
||||
false
|
||||
}
|
||||
|
||||
fn is_ascii_digit(ch: String) -> Bool {
|
||||
let code: Int = str_char_code(ch, 0)
|
||||
if code >= 48 {
|
||||
if code <= 57 { return true }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn sanitize_id(s: String) -> String {
|
||||
let n: Int = str_len(s)
|
||||
if n == 0 { return "node" }
|
||||
|
||||
let out = ""
|
||||
let prev_underscore = false
|
||||
let i = 0
|
||||
while i < n {
|
||||
let ch: String = str_char_at(s, i)
|
||||
if is_alnum_underscore(ch) {
|
||||
let out = out + ch
|
||||
let prev_underscore = false
|
||||
} else {
|
||||
if !prev_underscore {
|
||||
let out = out + "_"
|
||||
}
|
||||
let prev_underscore = true
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let m: Int = str_len(out)
|
||||
let end = m
|
||||
let stripping = true
|
||||
while stripping {
|
||||
if end <= 0 {
|
||||
let stripping = false
|
||||
} else {
|
||||
let last: String = str_char_at(out, end - 1)
|
||||
if last == "_" {
|
||||
let end = end - 1
|
||||
} else {
|
||||
let stripping = false
|
||||
}
|
||||
}
|
||||
}
|
||||
let out = str_slice(out, 0, end)
|
||||
|
||||
if str_len(out) == 0 { return "node" }
|
||||
|
||||
let first: String = str_char_at(out, 0)
|
||||
if is_ascii_digit(first) {
|
||||
let out = "n" + out
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn shape_from_token(tok: String) -> String {
|
||||
let t: String = str_trim(tok)
|
||||
if t == "rect" { return "rect" }
|
||||
if t == "rounded" { return "rounded" }
|
||||
if t == "cylinder" { return "cylinder" }
|
||||
if t == "diamond" { return "diamond" }
|
||||
if t == "stadium" { return "stadium" }
|
||||
if t == "primary" { return "primary" }
|
||||
""
|
||||
}
|
||||
|
||||
// ── Line preprocessing ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Strip inline `// ...` comments, trim, drop empties. Returns a list of maps
|
||||
// { "no": Int, "text": String }.
|
||||
|
||||
fn preprocess(source: String) -> [Map<String, Any>] {
|
||||
let lines: [String] = str_split(source, "\n")
|
||||
let n: Int = el_list_len(lines)
|
||||
let out: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n {
|
||||
let raw: String = get(lines, i)
|
||||
let cidx: Int = str_index_of(raw, "//")
|
||||
let stripped = raw
|
||||
if cidx >= 0 {
|
||||
let stripped = str_slice(raw, 0, cidx)
|
||||
}
|
||||
let trimmed: String = str_trim(stripped)
|
||||
if str_len(trimmed) > 0 {
|
||||
let row: Map<String, Any> = { "no": i + 1, "text": trimmed }
|
||||
let out = native_list_append(out, row)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ── Quoted-string extraction ────────────────────────────────────────────────
|
||||
//
|
||||
// Parses `"text"`-prefix from a string. Returns `{ "ok": Bool, "value": Str,
|
||||
// "rest": Str }`. The `rest` field carries everything after the closing quote
|
||||
// (so the caller can continue tokenising).
|
||||
|
||||
fn parse_quoted(s: String) -> Map<String, Any> {
|
||||
let t: String = str_trim(s)
|
||||
if str_len(t) < 2 {
|
||||
return { "ok": false, "value": "", "rest": s }
|
||||
}
|
||||
let first: String = str_char_at(t, 0)
|
||||
if first != "\"" {
|
||||
return { "ok": false, "value": "", "rest": s }
|
||||
}
|
||||
let body: String = str_slice(t, 1, str_len(t))
|
||||
let close: Int = str_index_of(body, "\"")
|
||||
if close < 0 {
|
||||
return { "ok": false, "value": "", "rest": s }
|
||||
}
|
||||
let inner: String = str_slice(body, 0, close)
|
||||
let rest: String = str_slice(body, close + 1, str_len(body))
|
||||
{ "ok": true, "value": inner, "rest": rest }
|
||||
}
|
||||
|
||||
// ── Identifier prefix split ─────────────────────────────────────────────────
|
||||
//
|
||||
// `split_identifier("foo bar")` → { "id": "foo", "rest": " bar" }.
|
||||
// `split_identifier("a-b")` → { "id": "a", "rest": "-b" }.
|
||||
|
||||
fn split_identifier(s: String) -> Map<String, Any> {
|
||||
let n: Int = str_len(s)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let ch: String = str_char_at(s, i)
|
||||
if !is_alnum_underscore(ch) {
|
||||
return { "id": str_slice(s, 0, i), "rest": str_slice(s, i, n) }
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
{ "id": s, "rest": "" }
|
||||
}
|
||||
|
||||
// ── Direction parsing ───────────────────────────────────────────────────────
|
||||
|
||||
fn parse_direction(s: String) -> String {
|
||||
let t: String = str_trim(s)
|
||||
if t == "top-down" { return "top-down" }
|
||||
if t == "TD" { return "top-down" }
|
||||
if t == "left-right" { return "left-right" }
|
||||
if t == "LR" { return "left-right" }
|
||||
if t == "right-left" { return "right-left" }
|
||||
if t == "RL" { return "right-left" }
|
||||
if t == "bottom-up" { return "bottom-up" }
|
||||
if t == "BU" { return "bottom-up" }
|
||||
""
|
||||
}
|
||||
|
||||
// ── Edge-arrow detection ────────────────────────────────────────────────────
|
||||
//
|
||||
// Detects the longest matching arrow token in a line, returning
|
||||
// { "ok": Bool, "from_str": Str, "kind": Str, "rest": Str }
|
||||
|
||||
fn extract_edge_parts(line: String) -> Map<String, Any> {
|
||||
// Order: longest first to avoid partial matches.
|
||||
let f1: Int = str_index_of(line, "-/->")
|
||||
if f1 >= 0 {
|
||||
return { "ok": true,
|
||||
"from_str": str_slice(line, 0, f1),
|
||||
"kind": "forbidden",
|
||||
"rest": str_slice(line, f1 + 4, str_len(line)) }
|
||||
}
|
||||
let f2: Int = str_index_of(line, "<->")
|
||||
if f2 >= 0 {
|
||||
return { "ok": true,
|
||||
"from_str": str_slice(line, 0, f2),
|
||||
"kind": "bidirectional",
|
||||
"rest": str_slice(line, f2 + 3, str_len(line)) }
|
||||
}
|
||||
let f3: Int = str_index_of(line, "-->")
|
||||
if f3 >= 0 {
|
||||
return { "ok": true,
|
||||
"from_str": str_slice(line, 0, f3),
|
||||
"kind": "dashed",
|
||||
"rest": str_slice(line, f3 + 3, str_len(line)) }
|
||||
}
|
||||
let f4: Int = str_index_of(line, "->")
|
||||
if f4 >= 0 {
|
||||
return { "ok": true,
|
||||
"from_str": str_slice(line, 0, f4),
|
||||
"kind": "solid",
|
||||
"rest": str_slice(line, f4 + 2, str_len(line)) }
|
||||
}
|
||||
{ "ok": false, "from_str": "", "kind": "", "rest": "" }
|
||||
}
|
||||
|
||||
fn is_edge_line(line: String) -> Bool {
|
||||
if str_contains(line, "->") { return true }
|
||||
if str_contains(line, "<->") { return true }
|
||||
false
|
||||
}
|
||||
|
||||
// ── Error helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
fn make_error(line_no: Int, line_text: String, message: String) -> Map<String, Any> {
|
||||
{ "error": message, "line": line_no, "text": line_text }
|
||||
}
|
||||
|
||||
// ── Parse driver ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// State is held in process-local k/v rather than threaded through every
|
||||
// function. Specifically:
|
||||
// "title", "direction" — graph header
|
||||
// "nodes_json", "edges_json", "groups_json" — accumulators (string lists)
|
||||
// "group_stack_depth" — "0".."N" — open groups
|
||||
// "group_stack_<i>_id" / "_label" / "_line" — frame data
|
||||
// "group_stack_<i>_node_ids" — JSON array of ids inside frame
|
||||
// "error" — non-empty if parse failed
|
||||
// "error_line", "error_text" — context
|
||||
|
||||
fn st_set_int(key: String, v: Int) -> Int { state_set(key, int_to_str(v)); 0 }
|
||||
fn st_get_int(key: String) -> Int {
|
||||
let s: String = state_get(key)
|
||||
if str_eq(s, "") { return 0 }
|
||||
str_to_int(s)
|
||||
}
|
||||
|
||||
// Encode/decode small string lists via "" delimiter (unit separator).
|
||||
fn list_encode(xs: [String]) -> String {
|
||||
let n: Int = el_list_len(xs)
|
||||
let out = ""
|
||||
let i = 0
|
||||
while i < n {
|
||||
if i > 0 { let out = out + "" }
|
||||
let out = out + get(xs, i)
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn list_decode(s: String) -> [String] {
|
||||
if str_eq(s, "") { return el_list_empty() }
|
||||
str_split(s, "")
|
||||
}
|
||||
|
||||
fn current_group_index() -> Int {
|
||||
st_get_int("group_stack_depth") - 1
|
||||
}
|
||||
|
||||
fn group_frame_key(idx: Int, suffix: String) -> String {
|
||||
"gs_" + int_to_str(idx) + "_" + suffix
|
||||
}
|
||||
|
||||
fn open_group(id: String, label: String, line_no: Int) -> Int {
|
||||
let depth: Int = st_get_int("group_stack_depth")
|
||||
state_set(group_frame_key(depth, "id"), id)
|
||||
state_set(group_frame_key(depth, "label"), label)
|
||||
state_set(group_frame_key(depth, "line"), int_to_str(line_no))
|
||||
state_set(group_frame_key(depth, "ids"), "")
|
||||
st_set_int("group_stack_depth", depth + 1)
|
||||
0
|
||||
}
|
||||
|
||||
fn close_group_frame() -> Map<String, Any> {
|
||||
let depth: Int = st_get_int("group_stack_depth")
|
||||
if depth <= 0 {
|
||||
return { "ok": false, "id": "", "label": "", "ids": "" }
|
||||
}
|
||||
let idx: Int = depth - 1
|
||||
let id: String = state_get(group_frame_key(idx, "id"))
|
||||
let label: String = state_get(group_frame_key(idx, "label"))
|
||||
let ids: String = state_get(group_frame_key(idx, "ids"))
|
||||
state_del(group_frame_key(idx, "id"))
|
||||
state_del(group_frame_key(idx, "label"))
|
||||
state_del(group_frame_key(idx, "line"))
|
||||
state_del(group_frame_key(idx, "ids"))
|
||||
st_set_int("group_stack_depth", idx)
|
||||
{ "ok": true, "id": id, "label": label, "ids": ids }
|
||||
}
|
||||
|
||||
fn register_node_in_group(node_id: String) -> Int {
|
||||
let depth: Int = st_get_int("group_stack_depth")
|
||||
if depth <= 0 { return 0 }
|
||||
let idx: Int = depth - 1
|
||||
let key: String = group_frame_key(idx, "ids")
|
||||
let prev: String = state_get(key)
|
||||
if str_eq(prev, "") {
|
||||
state_set(key, node_id)
|
||||
} else {
|
||||
state_set(key, prev + "" + node_id)
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
// Accumulator JSON-ish encoding for nodes/edges/groups.
|
||||
// We render each entry as a small string and stash in state under a counter.
|
||||
|
||||
fn store_node(id: String, label: String, shape: String) -> Int {
|
||||
let n: Int = st_get_int("node_count")
|
||||
state_set("node_id_" + int_to_str(n), id)
|
||||
state_set("node_label_" + int_to_str(n), label)
|
||||
state_set("node_shape_" + int_to_str(n), shape)
|
||||
st_set_int("node_count", n + 1)
|
||||
0
|
||||
}
|
||||
|
||||
fn store_edge(src: String, dst: String, label: String, kind: String) -> Int {
|
||||
let n: Int = st_get_int("edge_count")
|
||||
state_set("edge_from_" + int_to_str(n), src)
|
||||
state_set("edge_to_" + int_to_str(n), dst)
|
||||
state_set("edge_label_" + int_to_str(n), label)
|
||||
state_set("edge_kind_" + int_to_str(n), kind)
|
||||
st_set_int("edge_count", n + 1)
|
||||
0
|
||||
}
|
||||
|
||||
fn store_group(id: String, label: String, ids: String) -> Int {
|
||||
let n: Int = st_get_int("group_count")
|
||||
state_set("group_id_" + int_to_str(n), id)
|
||||
state_set("group_label_" + int_to_str(n), label)
|
||||
state_set("group_ids_" + int_to_str(n), ids)
|
||||
st_set_int("group_count", n + 1)
|
||||
0
|
||||
}
|
||||
|
||||
fn set_error(msg: String, line_no: Int, line_text: String) -> Int {
|
||||
state_set("parse_error", msg)
|
||||
st_set_int("parse_error_line", line_no)
|
||||
state_set("parse_error_text", line_text)
|
||||
0
|
||||
}
|
||||
|
||||
fn has_error() -> Bool {
|
||||
let m: String = state_get("parse_error")
|
||||
if str_eq(m, "") { return false }
|
||||
true
|
||||
}
|
||||
|
||||
// Reset state at the start of each parse pass.
|
||||
fn reset_state() -> Int {
|
||||
state_set("graph_title", "")
|
||||
state_set("graph_direction", "top-down")
|
||||
st_set_int("node_count", 0)
|
||||
st_set_int("edge_count", 0)
|
||||
st_set_int("group_count", 0)
|
||||
st_set_int("group_stack_depth", 0)
|
||||
state_set("parse_error", "")
|
||||
st_set_int("parse_error_line", 0)
|
||||
state_set("parse_error_text", "")
|
||||
0
|
||||
}
|
||||
|
||||
// ── Statement-level parsing ─────────────────────────────────────────────────
|
||||
|
||||
fn parse_node_stmt(line_no: Int, line: String) -> Int {
|
||||
let id_split: Map<String, Any> = split_identifier(line)
|
||||
let raw_id: String = id_split["id"]
|
||||
if str_eq(raw_id, "") {
|
||||
set_error("expected node id, edge, or keyword", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let id: String = sanitize_id(raw_id)
|
||||
let rest: String = str_trim(id_split["rest"])
|
||||
|
||||
// Optional shape: [token]
|
||||
let shape = "rect"
|
||||
let after_shape = rest
|
||||
if str_len(rest) > 0 {
|
||||
let lead: String = str_char_at(rest, 0)
|
||||
if lead == "[" {
|
||||
let close: Int = str_index_of(rest, "]")
|
||||
if close < 0 {
|
||||
set_error("unclosed `[` in shape token", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let token: String = str_slice(rest, 1, close)
|
||||
let parsed_shape: String = shape_from_token(token)
|
||||
if str_eq(parsed_shape, "") {
|
||||
set_error("unknown shape `" + token + "`", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let shape = parsed_shape
|
||||
let after_shape = str_trim(str_slice(rest, close + 1, str_len(rest)))
|
||||
}
|
||||
}
|
||||
|
||||
// Optional quoted label.
|
||||
let quoted: Map<String, Any> = parse_quoted(after_shape)
|
||||
let label = raw_id
|
||||
let ok: Bool = quoted["ok"]
|
||||
if ok {
|
||||
let label = quoted["value"]
|
||||
}
|
||||
|
||||
store_node(id, label, shape)
|
||||
register_node_in_group(id)
|
||||
1
|
||||
}
|
||||
|
||||
fn parse_edge_stmt(line_no: Int, line: String) -> Int {
|
||||
let parts: Map<String, Any> = extract_edge_parts(line)
|
||||
let ok: Bool = parts["ok"]
|
||||
if !ok {
|
||||
set_error("malformed edge — expected `->` `-->` `<->` or `-/->`", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let from_str: String = parts["from_str"]
|
||||
let rest_str: String = parts["rest"]
|
||||
let kind: String = parts["kind"]
|
||||
|
||||
let src: String = sanitize_id(str_trim(from_str))
|
||||
let rest_t: String = str_trim(rest_str)
|
||||
|
||||
let id_split: Map<String, Any> = split_identifier(rest_t)
|
||||
let to_raw: String = id_split["id"]
|
||||
if str_eq(to_raw, "") {
|
||||
set_error("edge missing target node id", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let dst: String = sanitize_id(to_raw)
|
||||
|
||||
let label_rest: String = str_trim(id_split["rest"])
|
||||
let quoted: Map<String, Any> = parse_quoted(label_rest)
|
||||
let label = ""
|
||||
let qok: Bool = quoted["ok"]
|
||||
if qok {
|
||||
let label = quoted["value"]
|
||||
}
|
||||
store_edge(src, dst, label, kind)
|
||||
1
|
||||
}
|
||||
|
||||
fn parse_group_open(line_no: Int, line: String, rest: String) -> Int {
|
||||
// Strip trailing `{`.
|
||||
let trimmed: String = str_trim(rest)
|
||||
let n: Int = str_len(trimmed)
|
||||
let body = trimmed
|
||||
if n > 0 {
|
||||
let last: String = str_char_at(trimmed, n - 1)
|
||||
if last == "{" {
|
||||
let body = str_trim(str_slice(trimmed, 0, n - 1))
|
||||
}
|
||||
}
|
||||
|
||||
let id_split: Map<String, Any> = split_identifier(body)
|
||||
let raw_id: String = id_split["id"]
|
||||
if str_eq(raw_id, "") {
|
||||
set_error("group declaration missing id", line_no, line)
|
||||
return 0
|
||||
}
|
||||
let label_rest: String = str_trim(id_split["rest"])
|
||||
let quoted: Map<String, Any> = parse_quoted(label_rest)
|
||||
let label = raw_id
|
||||
let qok: Bool = quoted["ok"]
|
||||
if qok {
|
||||
let label = quoted["value"]
|
||||
}
|
||||
open_group(raw_id, label, line_no)
|
||||
1
|
||||
}
|
||||
|
||||
fn parse_close_brace(line_no: Int) -> Int {
|
||||
let frame: Map<String, Any> = close_group_frame()
|
||||
let frame_ok: Bool = frame["ok"]
|
||||
if !frame_ok {
|
||||
set_error("unexpected `}` — no open group", line_no, "}")
|
||||
return 0
|
||||
}
|
||||
store_group(frame["id"], frame["label"], frame["ids"])
|
||||
1
|
||||
}
|
||||
|
||||
fn parse_line_dispatch(line_no: Int, line: String) -> Int {
|
||||
if line == "}" { return parse_close_brace(line_no) }
|
||||
|
||||
if str_starts_with(line, "title:") {
|
||||
let after: String = str_trim(str_slice(line, 6, str_len(line)))
|
||||
let q: Map<String, Any> = parse_quoted(after)
|
||||
let qok: Bool = q["ok"]
|
||||
if !qok {
|
||||
set_error("expected quoted string after `title:`", line_no, line)
|
||||
return 0
|
||||
}
|
||||
state_set("graph_title", q["value"])
|
||||
return 1
|
||||
}
|
||||
|
||||
if str_starts_with(line, "direction:") {
|
||||
let after: String = str_trim(str_slice(line, 10, str_len(line)))
|
||||
let dir: String = parse_direction(after)
|
||||
if str_eq(dir, "") {
|
||||
set_error("unknown direction — expected top-down, left-right, right-left, or bottom-up",
|
||||
line_no, line)
|
||||
return 0
|
||||
}
|
||||
state_set("graph_direction", dir)
|
||||
return 1
|
||||
}
|
||||
|
||||
if str_starts_with(line, "group ") {
|
||||
let after: String = str_slice(line, 6, str_len(line))
|
||||
return parse_group_open(line_no, line, after)
|
||||
}
|
||||
|
||||
if is_edge_line(line) {
|
||||
return parse_edge_stmt(line_no, line)
|
||||
}
|
||||
|
||||
parse_node_stmt(line_no, line)
|
||||
}
|
||||
|
||||
// ── Materialise accumulators into the final graph map ───────────────────────
|
||||
|
||||
fn build_graph_value() -> Map<String, Any> {
|
||||
let n_nodes: Int = st_get_int("node_count")
|
||||
let nodes: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n_nodes {
|
||||
let s: String = int_to_str(i)
|
||||
let node: Map<String, Any> = {
|
||||
"id": state_get("node_id_" + s),
|
||||
"label": state_get("node_label_" + s),
|
||||
"shape": state_get("node_shape_" + s)
|
||||
}
|
||||
let nodes = native_list_append(nodes, node)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let n_edges: Int = st_get_int("edge_count")
|
||||
let edges: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n_edges {
|
||||
let s: String = int_to_str(i)
|
||||
let edge: Map<String, Any> = {
|
||||
"from": state_get("edge_from_" + s),
|
||||
"to": state_get("edge_to_" + s),
|
||||
"label": state_get("edge_label_" + s),
|
||||
"kind": state_get("edge_kind_" + s)
|
||||
}
|
||||
let edges = native_list_append(edges, edge)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
let n_groups: Int = st_get_int("group_count")
|
||||
let groups: [Map<String, Any>] = el_list_empty()
|
||||
let i = 0
|
||||
while i < n_groups {
|
||||
let s: String = int_to_str(i)
|
||||
let raw_ids: String = state_get("group_ids_" + s)
|
||||
let id_list: [String] = list_decode(raw_ids)
|
||||
let group: Map<String, Any> = {
|
||||
"id": state_get("group_id_" + s),
|
||||
"label": state_get("group_label_" + s),
|
||||
"node_ids": id_list,
|
||||
"direction": ""
|
||||
}
|
||||
let groups = native_list_append(groups, group)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
{
|
||||
"title": state_get("graph_title"),
|
||||
"direction": state_get("graph_direction"),
|
||||
"nodes": nodes,
|
||||
"edges": edges,
|
||||
"groups": groups
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public entry point ──────────────────────────────────────────────────────
|
||||
|
||||
fn arbor_parse(source: String) -> Map<String, Any> {
|
||||
reset_state()
|
||||
let lines: [Map<String, Any>] = preprocess(source)
|
||||
let n: Int = el_list_len(lines)
|
||||
let i = 0
|
||||
let abort = false
|
||||
while i < n {
|
||||
if abort {
|
||||
// skip — error already recorded
|
||||
} else {
|
||||
let row: Map<String, Any> = get(lines, i)
|
||||
let line_no: Int = row["no"]
|
||||
let text: String = row["text"]
|
||||
parse_line_dispatch(line_no, text)
|
||||
if has_error() {
|
||||
let abort = true
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
if !has_error() {
|
||||
let depth: Int = st_get_int("group_stack_depth")
|
||||
if depth > 0 {
|
||||
let idx: Int = depth - 1
|
||||
let id: String = state_get(group_frame_key(idx, "id"))
|
||||
let line_no: Int = st_get_int(group_frame_key(idx, "line"))
|
||||
set_error("unclosed group '" + id + "' — missing closing `}`",
|
||||
line_no, "group " + id)
|
||||
}
|
||||
}
|
||||
if has_error() {
|
||||
return {
|
||||
"error": state_get("parse_error"),
|
||||
"line": st_get_int("parse_error_line"),
|
||||
"text": state_get("parse_error_text")
|
||||
}
|
||||
}
|
||||
build_graph_value()
|
||||
}
|
||||
|
||||
// ── Smoke test ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn fail_msg(label: String, got: String, want: String) -> Int {
|
||||
println("FAIL " + label + " got=[" + got + "] want=[" + want + "]")
|
||||
state_set("smoke_failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn check_eq(label: String, got: String, want: String) -> Int {
|
||||
if got == want {
|
||||
println("ok " + label)
|
||||
return 1
|
||||
}
|
||||
fail_msg(label, got, want)
|
||||
}
|
||||
|
||||
// Helper: a graph map is in the error state iff it has a non-empty "error".
|
||||
fn parse_failed(g: Map<String, Any>) -> Bool {
|
||||
let m: String = g["error"]
|
||||
if str_eq(m, "") { return false }
|
||||
// map_get returns NULL for missing keys; str_eq treats two NULLs as equal
|
||||
// and NULL vs "" as not equal — guard explicitly.
|
||||
if str_len(m) == 0 { return false }
|
||||
true
|
||||
}
|
||||
|
||||
let src1 = "title: \"Test\"\ndirection: left-right\n\napi [rounded] \"REST API\"\ndb [cylinder] \"Postgres\"\n\napi -> db \"reads\""
|
||||
let g1: Map<String, Any> = arbor_parse(src1)
|
||||
if parse_failed(g1) {
|
||||
println("FAIL parse 1: " + g1["error"])
|
||||
state_set("smoke_failures", "1")
|
||||
}
|
||||
check_eq("title parsed", g1["title"], "Test")
|
||||
check_eq("direction parsed", g1["direction"], "left-right")
|
||||
let nodes1: [Map<String, Any>] = g1["nodes"]
|
||||
let nn1: Int = el_list_len(nodes1)
|
||||
check_eq("two nodes", int_to_str(nn1), "2")
|
||||
let edges1: [Map<String, Any>] = g1["edges"]
|
||||
let ne1: Int = el_list_len(edges1)
|
||||
check_eq("one edge", int_to_str(ne1), "1")
|
||||
let e0: Map<String, Any> = get(edges1, 0)
|
||||
check_eq("edge from", e0["from"], "api")
|
||||
check_eq("edge to", e0["to"], "db")
|
||||
check_eq("edge label", e0["label"], "reads")
|
||||
check_eq("edge kind", e0["kind"], "solid")
|
||||
let n0: Map<String, Any> = get(nodes1, 0)
|
||||
check_eq("node 0 shape", n0["shape"], "rounded")
|
||||
check_eq("node 0 label", n0["label"], "REST API")
|
||||
|
||||
// Test edge varieties
|
||||
let src2 = "a \"A\"\nb \"B\"\na -> b\na --> b\na -/-> b\na <-> b"
|
||||
let g2: Map<String, Any> = arbor_parse(src2)
|
||||
let edges2: [Map<String, Any>] = g2["edges"]
|
||||
check_eq("4 edges parsed", int_to_str(el_list_len(edges2)), "4")
|
||||
let kinds = ""
|
||||
let i = 0
|
||||
while i < el_list_len(edges2) {
|
||||
let e: Map<String, Any> = get(edges2, i)
|
||||
let k: String = e["kind"]
|
||||
let kinds = kinds + k + ","
|
||||
let i = i + 1
|
||||
}
|
||||
check_eq("edge kinds", kinds, "solid,dashed,forbidden,bidirectional,")
|
||||
|
||||
// Groups
|
||||
let src3 = "group core \"Application Core\" {\n api [rounded] \"REST API\"\n svc \"Business Logic\"\n}\nstandalone \"Out\""
|
||||
let g3: Map<String, Any> = arbor_parse(src3)
|
||||
let groups3: [Map<String, Any>] = g3["groups"]
|
||||
check_eq("one group", int_to_str(el_list_len(groups3)), "1")
|
||||
let grp0: Map<String, Any> = get(groups3, 0)
|
||||
check_eq("group label", grp0["label"], "Application Core")
|
||||
let gnids: [String] = grp0["node_ids"]
|
||||
check_eq("group has 2 members", int_to_str(el_list_len(gnids)), "2")
|
||||
let nodes3: [Map<String, Any>] = g3["nodes"]
|
||||
check_eq("3 total nodes (incl standalone)",
|
||||
int_to_str(el_list_len(nodes3)), "3")
|
||||
|
||||
// Error: unknown shape
|
||||
let src4 = "node [hexagon] \"X\""
|
||||
let g4: Map<String, Any> = arbor_parse(src4)
|
||||
let err4: String = g4["error"]
|
||||
if str_eq(err4, "") {
|
||||
println("FAIL expected error for unknown shape")
|
||||
state_set("smoke_failures", "1")
|
||||
} else {
|
||||
if str_contains(err4, "hexagon") {
|
||||
println("ok error mentions hexagon: " + err4)
|
||||
} else {
|
||||
println("FAIL error wording: " + err4)
|
||||
state_set("smoke_failures", "1")
|
||||
}
|
||||
}
|
||||
|
||||
// Error: unclosed group
|
||||
let src5 = "group g \"G\" {\n a \"A\"\n"
|
||||
let g5: Map<String, Any> = arbor_parse(src5)
|
||||
let err5: String = g5["error"]
|
||||
if str_eq(err5, "") {
|
||||
println("FAIL expected unclosed-group error")
|
||||
state_set("smoke_failures", "1")
|
||||
} else {
|
||||
if str_contains(err5, "unclosed") {
|
||||
println("ok unclosed group detected")
|
||||
} else {
|
||||
println("FAIL unclosed error wording: " + err5)
|
||||
state_set("smoke_failures", "1")
|
||||
}
|
||||
}
|
||||
|
||||
// Comments and inline comments
|
||||
let src6 = "// header\na \"A\" // trailing\nb \"B\""
|
||||
let g6: Map<String, Any> = arbor_parse(src6)
|
||||
check_eq("comments stripped", int_to_str(el_list_len(g6["nodes"])), "2")
|
||||
|
||||
// Empty input
|
||||
let g7: Map<String, Any> = arbor_parse("")
|
||||
check_eq("empty graph nodes", int_to_str(el_list_len(g7["nodes"])), "0")
|
||||
check_eq("empty graph default direction", g7["direction"], "top-down")
|
||||
|
||||
println("")
|
||||
let f: String = state_get("smoke_failures")
|
||||
if str_eq(f, "1") {
|
||||
println("arbor-parse: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-parse: ok")
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// arbor-render — SVG renderer. Consumes a diagram graph + layout result and
|
||||
// emits an SVG document. PNG rasterization is not provided in this vessel
|
||||
// because the El runtime does not expose a vector-to-raster primitive yet
|
||||
// (see report).
|
||||
|
||||
vessel "arbor-render" {
|
||||
version "0.1.0"
|
||||
description "SVG renderer for Arbor diagrams"
|
||||
authors ["Neuron Technologies"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
arbor-core "0.1"
|
||||
arbor-layout "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
// arbor-render — SVG emission from a laid-out diagram.
|
||||
//
|
||||
// Entry point:
|
||||
// fn arbor_render_svg(graph: Map, layout: Map, forbidden: [String]) -> String
|
||||
//
|
||||
// The graph is the lowered (diagram-form) shape produced by arbor-core /
|
||||
// arbor-diagram (`title`, `direction`, `nodes`, `edges`, `groups`). The
|
||||
// layout is whatever arbor-layout returned: `node_pos_<id>`, `node_size_<id>`,
|
||||
// `group_bounds_<id>`, `node_ids`, `group_ids`, `canvas`.
|
||||
//
|
||||
// `forbidden` is a list of "from->to" key strings — same format as
|
||||
// arbor-core's collect_forbidden(). The Rust crate threaded a HashSet
|
||||
// through; El threads a list and we linear-scan.
|
||||
//
|
||||
// SVG is text emission — straightforward El. Every float coordinate is
|
||||
// passed through format_float(_, 1) for stable output.
|
||||
//
|
||||
// ── PNG render is intentionally out of scope ────────────────────────────────
|
||||
// The Rust crate rasterises via resvg → tiny_skia → png. The El runtime
|
||||
// today exposes no equivalent: there is no resvg, no usvg, no font rasterer,
|
||||
// no PNG encoder, no path-fill code. fs_write writes text only — there is
|
||||
// no binary write primitive. arbor_render_png() returns an error map in El
|
||||
// until the runtime grows a rasterer (see "runtime gaps" in the report).
|
||||
|
||||
// ── Colour palette (matches the Rust constants exactly) ────────────────────
|
||||
|
||||
fn col_node_fill() -> String { "#ffffff" }
|
||||
fn col_node_stroke() -> String { "#334155" }
|
||||
fn col_primary_fill() -> String { "#0052A0" }
|
||||
fn col_primary_text() -> String { "#ffffff" }
|
||||
fn col_node_text() -> String { "#0D0D14" }
|
||||
fn col_edge() -> String { "#64748B" }
|
||||
fn col_edge_forbidden() -> String { "#DC2626" }
|
||||
fn col_group_fill() -> String { "rgba(0,0,0,0.03)" }
|
||||
fn col_group_stroke() -> String { "#CBD5E1" }
|
||||
fn col_group_text() -> String { "#64748B" }
|
||||
fn col_edge_label() -> String { "#64748B" }
|
||||
|
||||
// ── XML escape ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn esc(s: String) -> String {
|
||||
let r1: String = str_replace(s, "&", "&")
|
||||
let r2: String = str_replace(r1, "<", "<")
|
||||
let r3: String = str_replace(r2, ">", ">")
|
||||
let r4: String = str_replace(r3, "\"", """)
|
||||
r4
|
||||
}
|
||||
|
||||
// Float to "%.1f" — the Rust pt() helper.
|
||||
fn pt(v: el_val_t) -> String {
|
||||
format_float(v, 1)
|
||||
}
|
||||
|
||||
// Float arithmetic helpers — float_to_int / int_to_float trip through Int,
|
||||
// which is exact for the integer-valued floats used by the layout pass.
|
||||
fn fadd(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai + bi)
|
||||
}
|
||||
|
||||
fn fsub(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
let bi: Int = float_to_int(b)
|
||||
int_to_float(ai - bi)
|
||||
}
|
||||
|
||||
fn fdiv2(a: el_val_t) -> el_val_t {
|
||||
let ai: Int = float_to_int(a)
|
||||
int_to_float(ai / 2)
|
||||
}
|
||||
|
||||
fn fmid(a: el_val_t, b: el_val_t) -> el_val_t {
|
||||
fdiv2(fadd(a, b))
|
||||
}
|
||||
|
||||
// ── forbidden-edge linear lookup ───────────────────────────────────────────
|
||||
|
||||
fn forbidden_key(from: String, to: String) -> String {
|
||||
from + "->" + to
|
||||
}
|
||||
|
||||
fn forbidden_contains(set: [String], src: String, dst: String) -> Bool {
|
||||
let key: String = forbidden_key(src, dst)
|
||||
let n: Int = el_list_len(set)
|
||||
let i = 0
|
||||
while i < n {
|
||||
let s: String = get(set, i)
|
||||
if str_eq(s, key) { return true }
|
||||
let i = i + 1
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// ── Arrow marker defs ──────────────────────────────────────────────────────
|
||||
|
||||
fn arrow_defs() -> String {
|
||||
let s = "\n <marker id=\"ah\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n"
|
||||
let s = s + " <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"" + col_edge() + "\"/>\n"
|
||||
let s = s + " </marker>\n"
|
||||
let s = s + " <marker id=\"ah-bi\" markerWidth=\"10\" markerHeight=\"7\" refX=\"1\" refY=\"3.5\" orient=\"auto-start-reverse\">\n"
|
||||
let s = s + " <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"" + col_edge() + "\"/>\n"
|
||||
let s = s + " </marker>\n"
|
||||
let s = s + " <marker id=\"ah-red\" markerWidth=\"10\" markerHeight=\"7\" refX=\"9\" refY=\"3.5\" orient=\"auto\">\n"
|
||||
let s = s + " <polygon points=\"0 0, 10 3.5, 0 7\" fill=\"" + col_edge_forbidden() + "\"/>\n"
|
||||
let s = s + " </marker>"
|
||||
s
|
||||
}
|
||||
|
||||
// ── Node rendering ─────────────────────────────────────────────────────────
|
||||
|
||||
fn render_node(buf: String, node: Map<String, Any>, layout: Map<String, Any>) -> String {
|
||||
let nid: String = node["id"]
|
||||
let pos: Map<String, Any> = el_map_get(layout, "node_pos_" + nid)
|
||||
let sz: Map<String, Any> = el_map_get(layout, "node_size_" + nid)
|
||||
|
||||
let cx: el_val_t = pos["x"]
|
||||
let cy: el_val_t = pos["y"]
|
||||
let w: el_val_t = sz["w"]
|
||||
let h: el_val_t = sz["h"]
|
||||
|
||||
let x: el_val_t = fsub(cx, fdiv2(w))
|
||||
let y: el_val_t = fsub(cy, fdiv2(h))
|
||||
|
||||
let fill_in: String = node["style_fill"]
|
||||
let stroke_in: String = node["style_stroke"]
|
||||
let color_in: String = node["style_color"]
|
||||
let fill = col_node_fill()
|
||||
if str_len(fill_in) > 0 { let fill = fill_in }
|
||||
let stroke = col_node_stroke()
|
||||
if str_len(stroke_in) > 0 { let stroke = stroke_in }
|
||||
let text_col = col_node_text()
|
||||
if str_len(color_in) > 0 { let text_col = color_in }
|
||||
|
||||
let shape: String = node["shape"]
|
||||
let buf = buf
|
||||
|
||||
if str_eq(shape, "rectangle") {
|
||||
let buf = buf + " <rect x=\"" + pt(x) + "\" y=\"" + pt(y)
|
||||
let buf = buf + "\" width=\"" + pt(w) + "\" height=\"" + pt(h)
|
||||
let buf = buf + "\" rx=\"4\" fill=\"" + fill + "\" stroke=\"" + stroke
|
||||
let buf = buf + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
if str_eq(shape, "rounded_rect") {
|
||||
let buf = buf + " <rect x=\"" + pt(x) + "\" y=\"" + pt(y)
|
||||
let buf = buf + "\" width=\"" + pt(w) + "\" height=\"" + pt(h)
|
||||
let buf = buf + "\" rx=\"20\" fill=\"" + fill + "\" stroke=\"" + stroke
|
||||
let buf = buf + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
if str_eq(shape, "stadium") {
|
||||
let buf = buf + " <rect x=\"" + pt(x) + "\" y=\"" + pt(y)
|
||||
let buf = buf + "\" width=\"" + pt(w) + "\" height=\"" + pt(h)
|
||||
let buf = buf + "\" rx=\"" + pt(fdiv2(h)) + "\" fill=\"" + fill
|
||||
let buf = buf + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
if str_eq(shape, "cylinder") {
|
||||
// body: rect from y+ry to bottom; ry ≈ h/6 (Rust uses h*0.18, we use h/6
|
||||
// to stay in integer arithmetic — visually indistinguishable on the
|
||||
// canvas sizes the layout produces).
|
||||
let hi: Int = float_to_int(h)
|
||||
let ry: el_val_t = int_to_float(hi / 6)
|
||||
let body_y: el_val_t = fadd(y, ry)
|
||||
let body_h: el_val_t = fsub(h, ry)
|
||||
let buf = buf + " <rect x=\"" + pt(x) + "\" y=\"" + pt(body_y)
|
||||
let buf = buf + "\" width=\"" + pt(w) + "\" height=\"" + pt(body_h)
|
||||
let buf = buf + "\" fill=\"" + fill + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\"/>\n"
|
||||
// top ellipse
|
||||
let buf = buf + " <ellipse cx=\"" + pt(cx) + "\" cy=\"" + pt(body_y)
|
||||
let buf = buf + "\" rx=\"" + pt(fdiv2(w)) + "\" ry=\"" + pt(ry)
|
||||
let buf = buf + "\" fill=\"" + fill + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\"/>\n"
|
||||
// bottom ellipse
|
||||
let bot_y: el_val_t = fadd(y, h)
|
||||
let buf = buf + " <ellipse cx=\"" + pt(cx) + "\" cy=\"" + pt(bot_y)
|
||||
let buf = buf + "\" rx=\"" + pt(fdiv2(w)) + "\" ry=\"" + pt(ry)
|
||||
let buf = buf + "\" fill=\"" + fill + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
if str_eq(shape, "diamond") {
|
||||
let hw: el_val_t = fdiv2(w)
|
||||
let hh: el_val_t = fdiv2(h)
|
||||
let buf = buf + " <polygon points=\""
|
||||
let buf = buf + pt(cx) + "," + pt(fsub(cy, hh)) + " "
|
||||
let buf = buf + pt(fadd(cx, hw)) + "," + pt(cy) + " "
|
||||
let buf = buf + pt(cx) + "," + pt(fadd(cy, hh)) + " "
|
||||
let buf = buf + pt(fsub(cx, hw)) + "," + pt(cy)
|
||||
let buf = buf + "\" fill=\"" + fill + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
|
||||
// Label.
|
||||
let label: String = node["label"]
|
||||
let buf = buf + " <text x=\"" + pt(cx) + "\" y=\"" + pt(cy)
|
||||
let buf = buf + "\" text-anchor=\"middle\" dominant-baseline=\"middle\""
|
||||
let buf = buf + " class=\"arbor-node-label\" fill=\"" + text_col + "\">"
|
||||
let buf = buf + esc(label) + "</text>\n"
|
||||
|
||||
// Sublabel — Rust's DiagramNode stores Option<String>; El uses "" sentinel.
|
||||
let sub: String = node["sublabel"]
|
||||
if str_len(sub) > 0 {
|
||||
let sub_y: el_val_t = fadd(cy, int_to_float(14))
|
||||
let buf = buf + " <text x=\"" + pt(cx) + "\" y=\"" + pt(sub_y)
|
||||
let buf = buf + "\" text-anchor=\"middle\" dominant-baseline=\"middle\""
|
||||
let buf = buf + " class=\"arbor-node-label\" fill=\"" + text_col + "\" font-size=\"10\">"
|
||||
let buf = buf + esc(sub) + "</text>\n"
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
// ── Edge rendering ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// We emit a straight line from one node centre to the other and let the
|
||||
// browser draw it; the Rust crate renders cubic bezier paths but the runtime
|
||||
// has no robust math layer, and the rectangles are large enough that
|
||||
// straight edges read clearly. (See "runtime gaps".)
|
||||
|
||||
fn render_edge(buf: String, edge: Map<String, Any>, layout: Map<String, Any>, forbidden: [String]) -> String {
|
||||
let from_id: String = edge["from"]
|
||||
let to_id: String = edge["to"]
|
||||
let from_pos: Map<String, Any> = el_map_get(layout, "node_pos_" + from_id)
|
||||
let to_pos: Map<String, Any> = el_map_get(layout, "node_pos_" + to_id)
|
||||
|
||||
let fx: el_val_t = from_pos["x"]
|
||||
let fy: el_val_t = from_pos["y"]
|
||||
let tx: el_val_t = to_pos["x"]
|
||||
let ty: el_val_t = to_pos["y"]
|
||||
|
||||
let is_forbidden: Bool = forbidden_contains(forbidden, from_id, to_id)
|
||||
let stroke = col_edge()
|
||||
if is_forbidden { let stroke = col_edge_forbidden() }
|
||||
|
||||
let line: String = edge["line"]
|
||||
let arrow: String = edge["arrow"]
|
||||
let dash_attr = ""
|
||||
if str_eq(line, "dashed") { let dash_attr = " stroke-dasharray=\"5,3\"" }
|
||||
if str_eq(line, "dotted") { let dash_attr = " stroke-dasharray=\"2,2\"" }
|
||||
|
||||
let marker_start = ""
|
||||
if str_eq(arrow, "both") { let marker_start = " marker-start=\"url(#ah-bi)\"" }
|
||||
if str_eq(arrow, "backward") { let marker_start = " marker-start=\"url(#ah-bi)\"" }
|
||||
|
||||
let marker_end = " marker-end=\"url(#ah)\""
|
||||
if is_forbidden { let marker_end = " marker-end=\"url(#ah-red)\"" }
|
||||
if str_eq(arrow, "none") { let marker_end = "" }
|
||||
if str_eq(arrow, "backward") { let marker_end = "" }
|
||||
|
||||
let buf = buf + " <line x1=\"" + pt(fx) + "\" y1=\"" + pt(fy)
|
||||
let buf = buf + "\" x2=\"" + pt(tx) + "\" y2=\"" + pt(ty)
|
||||
let buf = buf + "\" stroke=\"" + stroke + "\" stroke-width=\"1.5\""
|
||||
let buf = buf + dash_attr + marker_start + marker_end + "/>\n"
|
||||
|
||||
// Forbidden marker — circle-X at midpoint.
|
||||
if is_forbidden {
|
||||
let mx: el_val_t = fmid(fx, tx)
|
||||
let my: el_val_t = fmid(fy, ty)
|
||||
let r: el_val_t = int_to_float(7)
|
||||
let buf = buf + " <circle cx=\"" + pt(mx) + "\" cy=\"" + pt(my)
|
||||
let buf = buf + "\" r=\"" + pt(r) + "\" fill=\"white\" stroke=\""
|
||||
let buf = buf + col_edge_forbidden() + "\" stroke-width=\"1.5\"/>\n"
|
||||
let off: el_val_t = int_to_float(4)
|
||||
let buf = buf + " <line x1=\"" + pt(fsub(mx, off)) + "\" y1=\"" + pt(fsub(my, off))
|
||||
let buf = buf + "\" x2=\"" + pt(fadd(mx, off)) + "\" y2=\"" + pt(fadd(my, off))
|
||||
let buf = buf + "\" stroke=\"" + col_edge_forbidden() + "\" stroke-width=\"1.5\"/>\n"
|
||||
let buf = buf + " <line x1=\"" + pt(fadd(mx, off)) + "\" y1=\"" + pt(fsub(my, off))
|
||||
let buf = buf + "\" x2=\"" + pt(fsub(mx, off)) + "\" y2=\"" + pt(fadd(my, off))
|
||||
let buf = buf + "\" stroke=\"" + col_edge_forbidden() + "\" stroke-width=\"1.5\"/>\n"
|
||||
}
|
||||
|
||||
// Edge label
|
||||
let label: String = edge["label"]
|
||||
if str_len(label) > 0 {
|
||||
let mx: el_val_t = fmid(fx, tx)
|
||||
let my: el_val_t = fmid(fy, ty)
|
||||
let lw: el_val_t = int_to_float(str_len(label) * 7 + 8)
|
||||
let lh: el_val_t = int_to_float(16)
|
||||
let buf = buf + " <rect x=\"" + pt(fsub(mx, fdiv2(lw))) + "\" y=\"" + pt(fsub(my, fdiv2(lh)))
|
||||
let buf = buf + "\" width=\"" + pt(lw) + "\" height=\"" + pt(lh)
|
||||
let buf = buf + "\" rx=\"3\" fill=\"white\" opacity=\"0.85\"/>\n"
|
||||
let buf = buf + " <text x=\"" + pt(mx) + "\" y=\"" + pt(my)
|
||||
let buf = buf + "\" text-anchor=\"middle\" dominant-baseline=\"middle\""
|
||||
let buf = buf + " class=\"arbor-edge-label\">" + esc(label) + "</text>\n"
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
// ── Group rendering ────────────────────────────────────────────────────────
|
||||
|
||||
fn render_group(buf: String, group: Map<String, Any>, layout: Map<String, Any>) -> String {
|
||||
let gid: String = group["id"]
|
||||
let bounds: Map<String, Any> = el_map_get(layout, "group_bounds_" + gid)
|
||||
// Layout may not have bounds for empty groups — defensive.
|
||||
let bx_check: el_val_t = bounds["x"]
|
||||
if float_to_int(bx_check) == 0 {
|
||||
// Could be a real 0; cheaper to skip via presence check on group_ids.
|
||||
}
|
||||
let bx: el_val_t = bounds["x"]
|
||||
let by: el_val_t = bounds["y"]
|
||||
let bw: el_val_t = bounds["w"]
|
||||
let bh: el_val_t = bounds["h"]
|
||||
let buf = buf + " <rect x=\"" + pt(bx) + "\" y=\"" + pt(by)
|
||||
let buf = buf + "\" width=\"" + pt(bw) + "\" height=\"" + pt(bh)
|
||||
let buf = buf + "\" rx=\"8\" fill=\"" + col_group_fill() + "\" stroke=\""
|
||||
let buf = buf + col_group_stroke() + "\" stroke-width=\"1\" stroke-dasharray=\"4,3\"/>\n"
|
||||
|
||||
// Group label in the top-left corner.
|
||||
let lx: el_val_t = fadd(bx, int_to_float(8))
|
||||
let ly: el_val_t = fadd(by, int_to_float(14))
|
||||
let label: String = group["label"]
|
||||
let buf = buf + " <text x=\"" + pt(lx) + "\" y=\"" + pt(ly)
|
||||
let buf = buf + "\" class=\"arbor-group-label\">" + esc(label) + "</text>\n"
|
||||
buf
|
||||
}
|
||||
|
||||
// ── Public entry point ─────────────────────────────────────────────────────
|
||||
|
||||
fn arbor_render_svg(graph: Map<String, Any>, layout: Map<String, Any>, forbidden: [String]) -> String {
|
||||
let canvas: Map<String, Any> = el_map_get(layout, "canvas")
|
||||
let cw: el_val_t = canvas["w"]
|
||||
let ch: el_val_t = canvas["h"]
|
||||
|
||||
let buf = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" + pt(cw)
|
||||
let buf = buf + "\" height=\"" + pt(ch) + "\" viewBox=\"0 0 " + pt(cw) + " " + pt(ch) + "\">\n"
|
||||
let buf = buf + " <defs>"
|
||||
let buf = buf + arrow_defs()
|
||||
let buf = buf + "\n <style>\n"
|
||||
let buf = buf + " .arbor-node-label { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; }\n"
|
||||
let buf = buf + " .arbor-group-label { font-family: 'Helvetica Neue', Helvetica, Arial, monospace; font-size: 10px; fill: " + col_group_text() + "; letter-spacing: 0.08em; }\n"
|
||||
let buf = buf + " .arbor-edge-label { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 11px; fill: " + col_edge_label() + "; }\n"
|
||||
let buf = buf + " </style>\n"
|
||||
let buf = buf + " </defs>\n"
|
||||
|
||||
// Groups first (behind everything).
|
||||
let buf = buf + " <!-- Groups -->\n"
|
||||
let groups: [Map<String, Any>] = graph["groups"]
|
||||
let gn: Int = el_list_len(groups)
|
||||
let i = 0
|
||||
while i < gn {
|
||||
let g: Map<String, Any> = get(groups, i)
|
||||
let gid: String = g["id"]
|
||||
// Only render groups the layout actually placed.
|
||||
let gids: [String] = el_map_get(layout, "group_ids")
|
||||
let placed = false
|
||||
let j = 0
|
||||
while j < el_list_len(gids) {
|
||||
if str_eq(get(gids, j), gid) { let placed = true }
|
||||
let j = j + 1
|
||||
}
|
||||
if placed {
|
||||
let buf = render_group(buf, g, layout)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Edges
|
||||
let buf = buf + " <!-- Edges -->\n"
|
||||
let edges: [Map<String, Any>] = graph["edges"]
|
||||
let en: Int = el_list_len(edges)
|
||||
let i = 0
|
||||
while i < en {
|
||||
let e: Map<String, Any> = get(edges, i)
|
||||
let buf = render_edge(buf, e, layout, forbidden)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Nodes
|
||||
let buf = buf + " <!-- Nodes -->\n"
|
||||
let nodes: [Map<String, Any>] = graph["nodes"]
|
||||
let nn: Int = el_list_len(nodes)
|
||||
let i = 0
|
||||
while i < nn {
|
||||
let n: Map<String, Any> = get(nodes, i)
|
||||
let buf = render_node(buf, n, layout)
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Title
|
||||
let title: String = graph["title"]
|
||||
if str_len(title) > 0 {
|
||||
let title_x: el_val_t = fdiv2(cw)
|
||||
let buf = buf + " <text x=\"" + pt(title_x) + "\" y=\"22\" text-anchor=\"middle\""
|
||||
let buf = buf + " font-family=\"'Helvetica Neue', Helvetica, Arial, sans-serif\""
|
||||
let buf = buf + " font-size=\"15\" font-weight=\"600\" fill=\"" + col_node_text() + "\">"
|
||||
let buf = buf + esc(title) + "</text>\n"
|
||||
}
|
||||
|
||||
let buf = buf + "</svg>\n"
|
||||
buf
|
||||
}
|
||||
|
||||
// PNG — not implemented; the runtime has no SVG rasterizer or PNG encoder.
|
||||
// Returns an error map that callers can inspect via map["error"].
|
||||
fn arbor_render_png(graph: Map<String, Any>, layout: Map<String, Any>, forbidden: [String]) -> Map<String, Any> {
|
||||
{
|
||||
"error": "PNG rasterization not available in El runtime — install a runtime image library or use the Rust binary"
|
||||
}
|
||||
}
|
||||
|
||||
// ── Smoke test ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn fail(label: String, msg: String) -> Int {
|
||||
println("FAIL " + label + ": " + msg)
|
||||
state_set("smoke_failures", "1")
|
||||
0
|
||||
}
|
||||
|
||||
fn check_contains(label: String, haystack: String, needle: String) -> Int {
|
||||
if str_contains(haystack, needle) {
|
||||
println("ok " + label)
|
||||
return 1
|
||||
}
|
||||
fail(label, "missing [" + needle + "]")
|
||||
}
|
||||
|
||||
fn check_not_contains(label: String, haystack: String, needle: String) -> Int {
|
||||
if str_contains(haystack, needle) {
|
||||
return fail(label, "should not contain [" + needle + "]")
|
||||
}
|
||||
println("ok " + label)
|
||||
1
|
||||
}
|
||||
|
||||
fn make_test_node(id: String, label: String, shape: String) -> Map<String, Any> {
|
||||
{
|
||||
"id": id, "label": label, "sublabel": "",
|
||||
"shape": shape,
|
||||
"style_fill": "", "style_stroke": "", "style_color": ""
|
||||
}
|
||||
}
|
||||
|
||||
fn make_test_edge(src: String, dst: String, line: String, arrow: String, label: String) -> Map<String, Any> {
|
||||
{
|
||||
"from": src, "to": dst, "label": label,
|
||||
"line": line, "arrow": arrow
|
||||
}
|
||||
}
|
||||
|
||||
fn make_test_pos(x: Int, y: Int) -> Map<String, Any> {
|
||||
{ "x": int_to_float(x), "y": int_to_float(y) }
|
||||
}
|
||||
|
||||
fn make_test_size(w: Int, h: Int) -> Map<String, Any> {
|
||||
{ "w": int_to_float(w), "h": int_to_float(h) }
|
||||
}
|
||||
|
||||
// Build a minimal layout map by hand.
|
||||
fn build_layout(node_ids: [String], group_ids: [String], cw: Int, ch: Int) -> Map<String, Any> {
|
||||
let r: Map<String, Any> = el_map_new(0)
|
||||
let r = el_map_set(r, "node_ids", node_ids)
|
||||
let r = el_map_set(r, "group_ids", group_ids)
|
||||
let r = el_map_set(r, "canvas", { "w": int_to_float(cw), "h": int_to_float(ch) })
|
||||
r
|
||||
}
|
||||
|
||||
let n_a: Map<String, Any> = make_test_node("a", "Node A", "rectangle")
|
||||
let n_b: Map<String, Any> = make_test_node("b", "Node B", "rectangle")
|
||||
let e_ab: Map<String, Any> = make_test_edge("a", "b", "solid", "forward", "")
|
||||
|
||||
let nodes: [Map<String, Any>] = native_list_empty()
|
||||
let nodes = native_list_append(nodes, n_a)
|
||||
let nodes = native_list_append(nodes, n_b)
|
||||
let edges: [Map<String, Any>] = native_list_empty()
|
||||
let edges = native_list_append(edges, e_ab)
|
||||
let groups: [Map<String, Any>] = native_list_empty()
|
||||
|
||||
let g: Map<String, Any> = {
|
||||
"title": "Test", "direction": "top-down",
|
||||
"nodes": nodes, "edges": edges, "groups": groups
|
||||
}
|
||||
|
||||
let nid_list: [String] = native_list_empty()
|
||||
let nid_list = native_list_append(nid_list, "a")
|
||||
let nid_list = native_list_append(nid_list, "b")
|
||||
let gid_list: [String] = native_list_empty()
|
||||
let layout: Map<String, Any> = build_layout(nid_list, gid_list, 400, 300)
|
||||
let layout = el_map_set(layout, "node_pos_a", make_test_pos(100, 60))
|
||||
let layout = el_map_set(layout, "node_pos_b", make_test_pos(100, 200))
|
||||
let layout = el_map_set(layout, "node_size_a", make_test_size(120, 40))
|
||||
let layout = el_map_set(layout, "node_size_b", make_test_size(120, 40))
|
||||
|
||||
let forbidden: [String] = native_list_empty()
|
||||
let svg: String = arbor_render_svg(g, layout, forbidden)
|
||||
|
||||
check_contains("svg starts with <svg", svg, "<svg xmlns=")
|
||||
check_contains("svg ends with </svg>", svg, "</svg>")
|
||||
check_contains("svg contains node label", svg, "Node A")
|
||||
check_contains("svg contains title", svg, ">Test</text>")
|
||||
check_contains("svg has rect for rectangle node", svg, "<rect")
|
||||
check_contains("svg has line for edge", svg, "<line")
|
||||
check_contains("svg has arrow marker def", svg, "id=\"ah\"")
|
||||
|
||||
// Escape test
|
||||
let n_esc: Map<String, Any> = make_test_node("x", "A & B <C>", "rectangle")
|
||||
let nodes2: [Map<String, Any>] = native_list_empty()
|
||||
let nodes2 = native_list_append(nodes2, n_esc)
|
||||
let g2: Map<String, Any> = {
|
||||
"title": "Test <Title>", "direction": "top-down",
|
||||
"nodes": nodes2, "edges": native_list_empty(), "groups": native_list_empty()
|
||||
}
|
||||
let nid2: [String] = native_list_empty()
|
||||
let nid2 = native_list_append(nid2, "x")
|
||||
let layout2: Map<String, Any> = build_layout(nid2, native_list_empty(), 200, 100)
|
||||
let layout2 = el_map_set(layout2, "node_pos_x", make_test_pos(80, 40))
|
||||
let layout2 = el_map_set(layout2, "node_size_x", make_test_size(120, 40))
|
||||
let svg2: String = arbor_render_svg(g2, layout2, native_list_empty())
|
||||
check_contains("escapes ampersand", svg2, "&")
|
||||
check_contains("escapes <", svg2, "<")
|
||||
check_not_contains("no raw <C>", svg2, "<C>")
|
||||
|
||||
// Forbidden edge
|
||||
let e_fb: Map<String, Any> = make_test_edge("a", "b", "solid", "forward", "")
|
||||
let edges3: [Map<String, Any>] = native_list_empty()
|
||||
let edges3 = native_list_append(edges3, e_fb)
|
||||
let g3: Map<String, Any> = {
|
||||
"title": "F", "direction": "top-down",
|
||||
"nodes": nodes, "edges": edges3, "groups": native_list_empty()
|
||||
}
|
||||
let fb: [String] = native_list_empty()
|
||||
let fb = native_list_append(fb, forbidden_key("a", "b"))
|
||||
let svg3: String = arbor_render_svg(g3, layout, fb)
|
||||
check_contains("forbidden uses red marker", svg3, "ah-red")
|
||||
check_contains("forbidden colour present", svg3, col_edge_forbidden())
|
||||
|
||||
// Diamond shape → polygon
|
||||
let n_d: Map<String, Any> = make_test_node("d", "Decide", "diamond")
|
||||
let g4: Map<String, Any> = {
|
||||
"title": "", "direction": "top-down",
|
||||
"nodes": native_list_append(native_list_empty(), n_d),
|
||||
"edges": native_list_empty(), "groups": native_list_empty()
|
||||
}
|
||||
let nid4: [String] = native_list_append(native_list_empty(), "d")
|
||||
let layout4: Map<String, Any> = build_layout(nid4, native_list_empty(), 200, 100)
|
||||
let layout4 = el_map_set(layout4, "node_pos_d", make_test_pos(80, 50))
|
||||
let layout4 = el_map_set(layout4, "node_size_d", make_test_size(120, 40))
|
||||
let svg4: String = arbor_render_svg(g4, layout4, native_list_empty())
|
||||
check_contains("diamond uses polygon", svg4, "<polygon")
|
||||
|
||||
// Cylinder shape → ellipses
|
||||
let n_cy: Map<String, Any> = make_test_node("cy", "DB", "cylinder")
|
||||
let g5: Map<String, Any> = {
|
||||
"title": "", "direction": "top-down",
|
||||
"nodes": native_list_append(native_list_empty(), n_cy),
|
||||
"edges": native_list_empty(), "groups": native_list_empty()
|
||||
}
|
||||
let nid5: [String] = native_list_append(native_list_empty(), "cy")
|
||||
let layout5: Map<String, Any> = build_layout(nid5, native_list_empty(), 200, 100)
|
||||
let layout5 = el_map_set(layout5, "node_pos_cy", make_test_pos(80, 50))
|
||||
let layout5 = el_map_set(layout5, "node_size_cy", make_test_size(120, 40))
|
||||
let svg5: String = arbor_render_svg(g5, layout5, native_list_empty())
|
||||
check_contains("cylinder uses ellipse", svg5, "<ellipse")
|
||||
|
||||
// Dashed edge
|
||||
let e_dash: Map<String, Any> = make_test_edge("a", "b", "dashed", "forward", "")
|
||||
let g6: Map<String, Any> = {
|
||||
"title": "", "direction": "top-down",
|
||||
"nodes": nodes, "edges": native_list_append(native_list_empty(), e_dash),
|
||||
"groups": native_list_empty()
|
||||
}
|
||||
let svg6: String = arbor_render_svg(g6, layout, native_list_empty())
|
||||
check_contains("dashed line dasharray", svg6, "stroke-dasharray=\"5,3\"")
|
||||
|
||||
// PNG returns an error map
|
||||
let png: Map<String, Any> = arbor_render_png(g, layout, native_list_empty())
|
||||
let err: String = png["error"]
|
||||
if str_len(err) > 0 {
|
||||
println("ok PNG returns error map")
|
||||
} else {
|
||||
println("FAIL PNG should have returned error")
|
||||
state_set("smoke_failures", "1")
|
||||
}
|
||||
|
||||
println("")
|
||||
let f: String = state_get("smoke_failures")
|
||||
if str_eq(f, "1") {
|
||||
println("arbor-render: FAILED")
|
||||
exit_program(1)
|
||||
} else {
|
||||
println("arbor-render: ok")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import "language-profile.el"
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
import "dedup_test_a_nodedup.el"
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
extern fn fn_a(x: String) -> String
|
||||
@@ -0,0 +1 @@
|
||||
extern fn fn_a(x: String) -> String
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
extern fn fn_a(x: String) -> String
|
||||
@@ -0,0 +1,6 @@
|
||||
import "language-profile.el"
|
||||
import "dedup_test_a.el"
|
||||
|
||||
fn main_fn(x: String) -> String {
|
||||
return x
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
import "dedup_test_a.el"
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
import "dedup_test_a_notail.el"
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
extern fn fn_a(x: String) -> String
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
extern fn fn_b(x: String) -> String
|
||||
@@ -0,0 +1,761 @@
|
||||
// big language-profile for testing
|
||||
fn lang_profile_big0(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big0(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big0("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big1(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big1(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big1("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big2(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big2(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big2("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big3(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big3(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big3("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big4(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big4(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big4("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big5(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big5(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big5("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big6(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big6(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big6("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big7(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big7(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big7("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big8(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big8(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big8("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big9(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big9(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big9("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big10(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big10(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big10("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big11(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big11(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big11("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big12(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big12(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big12("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big13(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big13(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big13("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big14(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big14(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big14("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big15(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big15(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big15("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big16(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big16(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big16("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big17(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big17(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big17("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big18(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big18(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big18("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
fn lang_profile_big19(code: String, word_order: String, morph_type: String, has_case: String, has_gender: String, script_dir: String, agreement: String, null_subject: String) -> [String] {
|
||||
let r: [String] = native_list_empty()
|
||||
let r = native_list_append(r, "code")
|
||||
let r = native_list_append(r, code)
|
||||
let r = native_list_append(r, "word_order")
|
||||
let r = native_list_append(r, word_order)
|
||||
let r = native_list_append(r, "morph_type")
|
||||
let r = native_list_append(r, morph_type)
|
||||
let r = native_list_append(r, "has_case")
|
||||
let r = native_list_append(r, has_case)
|
||||
let r = native_list_append(r, "has_gender")
|
||||
let r = native_list_append(r, has_gender)
|
||||
let r = native_list_append(r, "script_dir")
|
||||
let r = native_list_append(r, script_dir)
|
||||
let r = native_list_append(r, "agreement")
|
||||
let r = native_list_append(r, agreement)
|
||||
let r = native_list_append(r, "null_subject")
|
||||
let r = native_list_append(r, null_subject)
|
||||
return r
|
||||
}
|
||||
|
||||
fn lang_get_big19(profile: [String], key: String) -> String {
|
||||
let n: Int = native_list_len(profile)
|
||||
let i: Int = 0
|
||||
while i < n - 1 {
|
||||
let k: String = native_list_get(profile, i)
|
||||
if str_eq(k, key) {
|
||||
return native_list_get(profile, i + 1)
|
||||
}
|
||||
let i = i + 2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn lang_profile_en() -> [String] {
|
||||
return lang_profile_big19("en", "SVO", "fusional", "false", "false", "ltr", "number;person", "false")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import "language-profile.el"
|
||||
|
||||
extern fn es_pluralize(noun: String) -> String
|
||||
extern fn es_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn fr_pluralize(noun: String) -> String
|
||||
extern fn fr_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn de_noun_plural(noun: String, gender: String) -> String
|
||||
extern fn de_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn ru_noun_case(noun: String, gender: String, gram_case: String, number: String) -> String
|
||||
extern fn ru_conjugate(verb: String, tense: String, person: String, number: String, gender: String) -> String
|
||||
extern fn ja_conjugate(dict_form: String, form: String) -> String
|
||||
extern fn fi_apply_case(noun: String, gram_case: String, number: String) -> String
|
||||
extern fn fi_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn ar_sound_plural(noun: String, gender: String) -> String
|
||||
extern fn ar_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String
|
||||
extern fn hi_noun_direct(noun: String, gender: String, number: String) -> String
|
||||
extern fn hi_gender(noun: String) -> String
|
||||
extern fn hi_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String
|
||||
extern fn sw_noun_plural(noun: String) -> String
|
||||
extern fn sw_conjugate(verb: String, person: String, number: String, noun_class: String, tense: String) -> String
|
||||
extern fn la_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn he_conjugate(verb: String, tense: String, person: String, gender: String, number: String) -> String
|
||||
extern fn grc_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn ang_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn sa_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn got_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn non_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn enm_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn pi_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn fro_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn goh_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn sga_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn txb_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn peo_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn akk_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn uga_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn egy_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn sux_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn gez_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
extern fn cop_conjugate(verb: String, tense: String, person: String, number: String) -> String
|
||||
@@ -0,0 +1,3 @@
|
||||
fn morph_tiny(x: String) -> String {
|
||||
return x
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import "language-profile.el"
|
||||
extern fn es_pluralize(noun: String) -> String
|
||||
@@ -0,0 +1 @@
|
||||
extern fn es_pluralize(noun: String) -> String
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "morph_externsonly.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "grammar.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "morphology.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "morph_tiny.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "test_empty_gram.el"
|
||||
import "test_empty_gram.el"
|
||||
import "test_empty_gram.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile-big.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "test_empty_gram.el"
|
||||
import "test_empty_gram.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "morphology.el"
|
||||
import "test_empty_gram.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "morphology.el"
|
||||
import "grammar.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "one_extern_nolp.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "one_extern.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "grammar.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "morphology.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "morphology.el"
|
||||
import "test_tiny_gram.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// realizer.el - Universal syntactic realizer: GramSpec -> surface text.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// English remains the default (backward compatible) when no "lang" key is set.
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// 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, ...)
|
||||
import "language-profile.el"
|
||||
import "ext_a.el"
|
||||
import "ext_b.el"
|
||||
|
||||
// ── 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" }
|
||||
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" }
|
||||
return "singular"
|
||||
}
|
||||
|
||||
// ── NP realization ────────────────────────────────────────────────────────────
|
||||
|
||||
fn realize_np(referent: String, number: String) -> String {
|
||||
return referent
|
||||
}
|
||||
|
||||
// ── VP realization ────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Returns [main_verb_surface, aux_surface_or_empty].
|
||||
// Delegates conjugation to morph_conjugate with the language profile.
|
||||
|
||||
fn realize_vp_lang(base_verb: String, tense: String, aspect: String, person: String, number: String, profile: [String]) -> [String] {
|
||||
let empty_aux: String = ""
|
||||
|
||||
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, surf)
|
||||
let result = native_list_append(result, empty_aux)
|
||||
return result
|
||||
}
|
||||
|
||||
if str_eq(aspect, "progressive") {
|
||||
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
|
||||
}
|
||||
|
||||
if str_eq(aspect, "perfect") {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ── Question formation ────────────────────────────────────────────────────────
|
||||
//
|
||||
// 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.
|
||||
|
||||
// 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, " ")
|
||||
}
|
||||
|
||||
// ── 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 ────────────────────────────────────────────
|
||||
|
||||
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 + "?" }
|
||||
return s + "."
|
||||
}
|
||||
|
||||
// ── Main realization entry point ──────────────────────────────────────────────
|
||||
|
||||
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 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 ──────────────────────────────────────────────────────────────
|
||||
if str_eq(intent, "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_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 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, 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))
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// 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")
|
||||
// "lang" - ISO 639-1 language code (optional, defaults to "en")
|
||||
//
|
||||
// sem_to_spec converts a SemFrame into a realizer slot map ready for realize().
|
||||
// sem_realize is the end-to-end shortcut: frame -> realized text.
|
||||
//
|
||||
// All existing function signatures are preserved; new *_lang variants add an
|
||||
// explicit lang_code parameter.
|
||||
//
|
||||
// Depends on: grammar (slots_*, realize), language-profile (lang_from_code)
|
||||
import "test_empty_gram.el"
|
||||
import "test_empty_gram.el"
|
||||
import "test_empty_gram.el"
|
||||
|
||||
// ── SemFrame constructors ─────────────────────────────────────────────────────
|
||||
|
||||
// Build a SemFrame with all four core fields. Language defaults to "en".
|
||||
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)
|
||||
let r = native_list_append(r, "lang")
|
||||
let r = native_list_append(r, "en")
|
||||
return r
|
||||
}
|
||||
|
||||
// Build a SemFrame with an explicit language code.
|
||||
fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: 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)
|
||||
let r = native_list_append(r, "lang")
|
||||
let r = native_list_append(r, lang_code)
|
||||
return r
|
||||
}
|
||||
|
||||
// Convenience: no object, no modifiers, English.
|
||||
fn sem_frame_simple(intent: String, subject: String) -> [String] {
|
||||
return sem_frame(intent, subject, "", "")
|
||||
}
|
||||
|
||||
// Convenience: with object, no modifiers, English.
|
||||
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")
|
||||
}
|
||||
|
||||
fn sem_lang(frame: [String]) -> String {
|
||||
let code: String = slots_get(frame, "lang")
|
||||
if str_eq(code, "") {
|
||||
return "en"
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// ── Modifier helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn sem_first_modifier(mods: String) -> String {
|
||||
let n: Int = str_len(mods)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
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 ────────────────────────────────────────────────────────────
|
||||
|
||||
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" }
|
||||
return "assert"
|
||||
}
|
||||
|
||||
// ── sem_to_spec: SemFrame -> realizer slot map ────────────────────────────────
|
||||
//
|
||||
// The "lang" key from the SemFrame is forwarded into the GramSpec so that
|
||||
// realize() can resolve the correct language profile.
|
||||
|
||||
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 lang_code: String = sem_lang(frame)
|
||||
let location: String = sem_first_modifier(mods)
|
||||
|
||||
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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
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, 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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
// ── sem_to_spec_full ──────────────────────────────────────────────────────────
|
||||
|
||||
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 lang_code: String = sem_lang(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)
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
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)
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
// ── Greet realization helper ──────────────────────────────────────────────────
|
||||
|
||||
fn sem_realize_greet(subject: String) -> String {
|
||||
if str_eq(subject, "") {
|
||||
return "Hello."
|
||||
}
|
||||
return "Hello, " + subject + "."
|
||||
}
|
||||
|
||||
// ── sem_realize: SemFrame -> text ─────────────────────────────────────────────
|
||||
|
||||
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 ──────────────────────────────────────────────────────────
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ── sem_realize_lang: realize in an explicitly specified language ──────────────
|
||||
//
|
||||
// Convenience for callers that want to specify the output language without
|
||||
// constructing a full SemFrame. The lang_code overrides whatever "lang" is
|
||||
// set in the frame.
|
||||
|
||||
fn sem_realize_lang(frame: [String], lang_code: String) -> String {
|
||||
let intent: String = sem_intent(frame)
|
||||
|
||||
if str_eq(intent, "greet") {
|
||||
return sem_realize_greet(sem_subject(frame))
|
||||
}
|
||||
|
||||
// Inject the lang_code into the frame before converting to spec.
|
||||
let patched: [String] = slots_set(frame, "lang", lang_code)
|
||||
let spec: [String] = sem_to_spec(patched)
|
||||
return realize(spec)
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
// 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")
|
||||
// "lang" - ISO 639-1 language code (optional, defaults to "en")
|
||||
//
|
||||
// sem_to_spec converts a SemFrame into a realizer slot map ready for realize().
|
||||
// sem_realize is the end-to-end shortcut: frame -> realized text.
|
||||
//
|
||||
// All existing function signatures are preserved; new *_lang variants add an
|
||||
// explicit lang_code parameter.
|
||||
//
|
||||
// Depends on: grammar (slots_*, realize), language-profile (lang_from_code)
|
||||
|
||||
// ── SemFrame constructors ─────────────────────────────────────────────────────
|
||||
|
||||
// Build a SemFrame with all four core fields. Language defaults to "en".
|
||||
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)
|
||||
let r = native_list_append(r, "lang")
|
||||
let r = native_list_append(r, "en")
|
||||
return r
|
||||
}
|
||||
|
||||
// Build a SemFrame with an explicit language code.
|
||||
fn sem_frame_lang(intent: String, subject: String, obj: String, modifiers: String, lang_code: 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)
|
||||
let r = native_list_append(r, "lang")
|
||||
let r = native_list_append(r, lang_code)
|
||||
return r
|
||||
}
|
||||
|
||||
// Convenience: no object, no modifiers, English.
|
||||
fn sem_frame_simple(intent: String, subject: String) -> [String] {
|
||||
return sem_frame(intent, subject, "", "")
|
||||
}
|
||||
|
||||
// Convenience: with object, no modifiers, English.
|
||||
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")
|
||||
}
|
||||
|
||||
fn sem_lang(frame: [String]) -> String {
|
||||
let code: String = slots_get(frame, "lang")
|
||||
if str_eq(code, "") {
|
||||
return "en"
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// ── Modifier helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
fn sem_first_modifier(mods: String) -> String {
|
||||
let n: Int = str_len(mods)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
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 ────────────────────────────────────────────────────────────
|
||||
|
||||
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" }
|
||||
return "assert"
|
||||
}
|
||||
|
||||
// ── sem_to_spec: SemFrame -> realizer slot map ────────────────────────────────
|
||||
//
|
||||
// The "lang" key from the SemFrame is forwarded into the GramSpec so that
|
||||
// realize() can resolve the correct language profile.
|
||||
|
||||
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 lang_code: String = sem_lang(frame)
|
||||
let location: String = sem_first_modifier(mods)
|
||||
|
||||
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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
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, 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")
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
// ── sem_to_spec_full ──────────────────────────────────────────────────────────
|
||||
|
||||
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 lang_code: String = sem_lang(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)
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
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)
|
||||
let spec = native_list_append(spec, "lang")
|
||||
let spec = native_list_append(spec, lang_code)
|
||||
return spec
|
||||
}
|
||||
|
||||
// ── Greet realization helper ──────────────────────────────────────────────────
|
||||
|
||||
fn sem_realize_greet(subject: String) -> String {
|
||||
if str_eq(subject, "") {
|
||||
return "Hello."
|
||||
}
|
||||
return "Hello, " + subject + "."
|
||||
}
|
||||
|
||||
// ── sem_realize: SemFrame -> text ─────────────────────────────────────────────
|
||||
|
||||
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 ──────────────────────────────────────────────────────────
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ── sem_realize_lang: realize in an explicitly specified language ──────────────
|
||||
//
|
||||
// Convenience for callers that want to specify the output language without
|
||||
// constructing a full SemFrame. The lang_code overrides whatever "lang" is
|
||||
// set in the frame.
|
||||
|
||||
fn sem_realize_lang(frame: [String], lang_code: String) -> String {
|
||||
let intent: String = sem_intent(frame)
|
||||
|
||||
if str_eq(intent, "greet") {
|
||||
return sem_realize_greet(sem_subject(frame))
|
||||
}
|
||||
|
||||
// Inject the lang_code into the frame before converting to spec.
|
||||
let patched: [String] = slots_set(frame, "lang", lang_code)
|
||||
let spec: [String] = sem_to_spec(patched)
|
||||
return realize(spec)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fn gram_fn(x: String) -> String {
|
||||
return x
|
||||
}
|
||||
Reference in New Issue
Block a user