reshape: decorator-as-seam — port @route codegen, prove decorate->serve, rewrite surface as decorated El

Ground-truth the three seams (route/telemetry+interoception/bus) with file:line
evidence. Port the tested @route codegen+parser from feat/el-route-decorators
into the worktree elc (decoration synthesizes el_route_dispatch — no hand-written
90-branch handle_request). Rebuild elc self-host; prove decorate->serve end-to-end
(route_proof.el on :8951). Rewrite surface.el as El-native decorated components:
@route + @accessor/@manager, in-process engram_* builtins (not http_get), @manager
ops emit on the real dharma_* bus (same transport as wt/swarm-ccr). Identity
keystones refused in write/relate/supersede. Gate-1 clone recipe (WAL-aside
cold-boot + ENGRAM_WAL=on) proves the FULL op set live on the clone. Boundary
auto-emit (telemetry/interoception/bus) staged as a reviewable cg_fn diff
(SEAM_STAGED.md) — needs the cognition-engram rebuild to verify link. Live :8742
untouched; no push, no cutover.
This commit is contained in:
bigmerge
2026-08-14 21:01:27 -05:00
parent f19040e484
commit d4f401de1c
7 changed files with 848 additions and 239 deletions
+294 -3
View File
@@ -2916,6 +2916,24 @@ fn build_int_names_for_params(params: [Map<String, Any>]) -> Bool {
return true
}
// fn_has_decorator does this FnDef carry a decorator named `name`?
// Reads the `decorators` list [{name, args}] attached by the parser. Absent
// key -> native_list_len returns 0 -> false. This is the multi-decorator-aware
// replacement for the old single `decorator` string check, so a fn may stack
// roles with other decorators (e.g. `@route(...) @manager fn ...`).
fn fn_has_decorator(stmt: Map<String, Any>, name: String) -> Bool {
let dl = stmt["decorators"]
let n: Int = native_list_len(dl)
let i = 0
while i < n {
let d = native_list_get(dl, i)
let dn: String = d["name"]
if str_eq(dn, name) { return true }
let i = i + 1
}
false
}
fn cg_fn(stmt: Map<String, Any>) -> Void {
let fn_name: String = stmt["name"]
// Skip El's `fn main()` - C provides its own main() for top-level stmts
@@ -2927,10 +2945,10 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
let params_c: String = params_to_c(params)
// VBD role enforcement: dharma_emit / dharma_field may only be called
// from @manager-decorated functions. Surface violations to the C compiler
// via #error directives emitted before the function definition.
let decorator: String = stmt["decorator"]
// via #error directives emitted before the function definition. Read the
// decorator LIST so the role may be stacked with other decorators.
if vbd_has_restricted_call(body) {
if !str_eq(decorator, "manager") {
if !fn_has_decorator(stmt, "manager") {
emit_line("#error \"VBD violation: dharma_emit/dharma_field called from non-@manager fn '" + fn_name + "'\"")
}
}
@@ -3479,6 +3497,259 @@ fn cg_decl_streaming(stmt: Map<String, Any>) -> Void {
}
}
// @route dispatcher generation
//
// Scan the token stream for @route-decorated fns and synthesize a generic HTTP
// dispatcher `el_route_dispatch(method, clean, path, body)`. A decorated handler
// must have the uniform signature (method, path, body) -> String. The dispatcher
// matches `clean` (the query-stripped path, supplied by the caller) against each
// route and calls the handler with the ORIGINAL `path` so query strings survive.
// Returns the sentinel "__EL_NO_ROUTE__" when nothing matches, so the caller may
// fall through to any remaining hand-written branches (mixed mode).
//
// Decorator grammar: @route(path, method, kind, suffix)
// path the match string (or the prefix, for compound)
// method "GET" | "POST" | ... ; a '|'-list like "GET|POST"; "ANY"/"" = no guard
// kind "exact" (default) | "prefix" | "suffix" | "compound"
// suffix for "compound": the required str_ends_with suffix
//
// The dispatch table is emitted SPECIFICITY-SORTED (most-specific first), NOT in
// source order, so overlapping prefixes (e.g. /api/x/search vs /api/x) never
// shadow each other regardless of how the handlers are written.
// split_pipe split "GET|POST" on '|' into ["GET","POST"]. Self-contained
// (no dependency on str_split runtime semantics).
fn split_pipe(s: String) -> [String] {
let out: [String] = native_list_empty()
let cur: String = ""
let n: Int = str_len(s)
let i: Int = 0
while i < n {
let ch: String = str_slice(s, i, i + 1)
if str_eq(ch, "|") {
let out = native_list_append(out, cur)
let cur = ""
} else {
let cur = cur + ch
}
let i = i + 1
}
let out = native_list_append(out, cur)
out
}
// route_make_record build a route record map from the @route decorator args.
fn route_make_record(fn_name: String, args: [String]) -> Map<String, Any> {
let na: Int = native_list_len(args)
let rpath: String = ""
if na >= 1 { let rpath = native_list_get(args, 0) }
let rmethod: String = "GET"
if na >= 2 { let rmethod = native_list_get(args, 1) }
let rkind: String = "exact"
if na >= 3 { let rkind = native_list_get(args, 2) }
let rsuffix: String = ""
if na >= 4 { let rsuffix = native_list_get(args, 3) }
{ "name": fn_name, "path": rpath, "method": rmethod, "kind": rkind, "suffix": rsuffix }
}
// route_spec_score higher = more specific = emitted earlier. Ordering:
// exact > compound > suffix > prefix; within a class, a longer path/suffix
// wins (so /api/x/search sorts before /api/x). Guarantees correct dispatch
// independent of source order.
fn route_spec_score(rec: Map<String, Any>) -> Int {
let kind: String = rec["kind"]
let path: String = rec["path"]
let suffix: String = rec["suffix"]
let plen: Int = str_len(path)
let slen: Int = str_len(suffix)
if str_eq(kind, "exact") { return 4000000 + plen }
if str_eq(kind, "compound") { return 3000000 + plen * 100 + slen }
if str_eq(kind, "suffix") { return 2000000 + slen }
return 1000000 + plen
}
// route_sort_desc selection sort of route records by descending specificity.
// N is small (routes per module), so O(n^2) is fine and keeps codegen simple.
fn route_sort_desc(recs: [Map<String, Any>]) -> [Map<String, Any>] {
let n: Int = native_list_len(recs)
let out: [Map<String, Any>] = native_list_empty()
let used: [Bool] = native_list_empty()
let u: Int = 0
while u < n {
let used = native_list_append(used, false)
let u = u + 1
}
let picked: Int = 0
while picked < n {
let best_i: Int = 0 - 1
let best_score: Int = 0 - 1
let i: Int = 0
while i < n {
let is_used: Bool = native_list_get(used, i)
if !is_used {
let sc: Int = route_spec_score(native_list_get(recs, i))
if sc > best_score {
let best_score = sc
let best_i = i
}
}
let i = i + 1
}
let out = native_list_append(out, native_list_get(recs, best_i))
// Rebuild `used` with best_i marked (runtime has no native_list_set).
let new_used: [Bool] = native_list_empty()
let j: Int = 0
while j < n {
if j == best_i {
let new_used = native_list_append(new_used, true)
} else {
let new_used = native_list_append(new_used, native_list_get(used, j))
}
let j = j + 1
}
let used = new_used
let picked = picked + 1
}
out
}
// scan_routes token-level scan collecting every @route-decorated fn as a
// route record. Runs once per module (like scan_fn_sigs) so the dispatcher can
// be synthesized in the streaming backend, which discards per-fn ASTs. Handles
// decorator STACKING: `@route(...) @manager fn` still records the route.
fn scan_routes(tokens: [Any]) -> [Map<String, Any>] {
let total: Int = native_list_len(tokens) / 2
let recs: [Map<String, Any>] = native_list_empty()
let has_pending: Bool = false
let pending_args: [String] = native_list_empty()
let pos: Int = 0
let going: Bool = true
while going {
if pos >= total {
let going = false
} else {
let k: String = tok_kind(tokens, pos)
if str_eq(k, "Eof") {
let going = false
} else {
if str_eq(k, "At") {
let dname: String = tok_value(tokens, pos + 1)
let p: Int = pos + 2
let args: [String] = native_list_empty()
let ka: String = tok_kind(tokens, p)
if str_eq(ka, "LParen") {
let p = p + 1
let running: Bool = true
while running {
let kd: String = tok_kind(tokens, p)
if str_eq(kd, "RParen") {
let running = false
} else {
if str_eq(kd, "Eof") {
let running = false
} else {
if str_eq(kd, "Str") {
let args = native_list_append(args, tok_value(tokens, p))
}
let p = p + 1
}
}
}
if str_eq(tok_kind(tokens, p), "RParen") { let p = p + 1 }
}
if str_eq(dname, "route") {
let has_pending = true
let pending_args = args
}
let pos = p
} else {
if str_eq(k, "Fn") {
let fname: String = tok_value(tokens, pos + 1)
if has_pending {
let recs = native_list_append(recs, route_make_record(fname, pending_args))
let has_pending = false
}
let pos = pos + 2
} else {
let pos = pos + 1
}
}
}
}
}
recs
}
// program_has_routes did scan_routes find any @route fn?
fn program_has_routes(recs: [Map<String, Any>]) -> Bool {
native_list_len(recs) > 0
}
// route_method_guard C boolean prefix guarding on HTTP method, or "" for none.
fn route_method_guard(method: String) -> String {
if str_eq(method, "") { return "" }
if str_eq(method, "ANY") { return "" }
if str_contains(method, "|") {
let parts: [String] = split_pipe(method)
let np: Int = native_list_len(parts)
let expr: String = ""
let i: Int = 0
while i < np {
let m: String = native_list_get(parts, i)
if str_eq(m, "") {
let i = i + 1
} else {
let piece: String = "str_eq(method, EL_STR(" + c_str_lit(m) + "))"
if str_eq(expr, "") {
let expr = piece
} else {
let expr = expr + " || " + piece
}
let i = i + 1
}
}
if str_eq(expr, "") { return "" }
return "(" + expr + ") && "
}
"str_eq(method, EL_STR(" + c_str_lit(method) + ")) && "
}
// route_match_expr C boolean matching `clean` against the route path/kind.
fn route_match_expr(kind: String, path: String, suffix: String) -> String {
if str_eq(kind, "prefix") {
return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + "))"
}
if str_eq(kind, "suffix") {
return "str_ends_with(clean, EL_STR(" + c_str_lit(path) + "))"
}
if str_eq(kind, "compound") {
return "str_starts_with(clean, EL_STR(" + c_str_lit(path) + ")) && str_ends_with(clean, EL_STR(" + c_str_lit(suffix) + "))"
}
"str_eq(clean, EL_STR(" + c_str_lit(path) + "))"
}
// emit_route_dispatch emit the generated el_route_dispatch definition from the
// specificity-sorted route records. No-op if there are no routes.
fn emit_route_dispatch(recs: [Map<String, Any>]) -> Void {
if !program_has_routes(recs) { return }
let sorted: [Map<String, Any>] = route_sort_desc(recs)
emit_line("// ── generated @route dispatcher (specificity-sorted) ──")
emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body) {")
let n: Int = native_list_len(sorted)
let i: Int = 0
while i < n {
let rec = native_list_get(sorted, i)
let guard: String = route_method_guard(rec["method"])
let match_e: String = route_match_expr(rec["kind"], rec["path"], rec["suffix"])
let fn_name: String = rec["name"]
emit_line(" if (" + guard + match_e + ") { return " + fn_name + "(method, path, body); }")
let i = i + 1
}
emit_line(" return EL_STR(\"__EL_NO_ROUTE__\");")
emit_line("}")
emit_blank()
}
// emit_streaming_preamble emit #includes, forward decls, and file-scope lets
// using the pre-scanned signature data (no full AST).
fn emit_streaming_preamble(sigs: [Map<String, Any>], source: String) -> Void {
@@ -3571,6 +3842,17 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
emit_streaming_preamble(sigs, source)
el_arena_pop(preamble_mark)
// @route: scan the token stream once for @route-decorated fns. Kept in
// codegen_streaming scope (survives the per-fn arena pops and el_release of
// tokens below via refcount, like `sigs`). If any exist, forward-declare the
// generated dispatcher NOW so hand-written fns (e.g. handle_request) may call
// it before its definition is emitted after the fn-emit loop.
let route_records: [Map<String, Any>] = scan_routes(tokens)
if program_has_routes(route_records) {
emit_line("el_val_t el_route_dispatch(el_val_t method, el_val_t clean, el_val_t path, el_val_t body);")
emit_blank()
}
// Detect whether there is a fn main() and whether there are top-level
// executable stmts (for library detection) from sigs.
let has_el_main: Bool = false
@@ -3758,6 +4040,15 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
}
}
// @route: emit the generated dispatcher definition now after every handler
// fn has been emitted, but before `tokens` is released (route_records holds
// its own refs to the extracted strings). No-op unless the module declared
// at least one @route fn. Emitted before the test/library early-returns so it
// is present in library modules (e.g. neuron's routes.el) too.
let route_arena_mark: Any = el_arena_push()
emit_route_dispatch(route_records)
el_arena_pop(route_arena_mark)
// Tokens fully consumed by the streaming loop release now to free peak heap.
el_release(tokens)