land the runtime seam: resolve the crossing at execution
Five compile-time passes added 491 lines to the thing that was supposed to stop growing. The seam is ~55 lines of C and one line of emission, and it does at runtime what three of those five kinds did at compile time -- for programs that are already built. a construct declared AFTER the binary exists applies to it free when unused: 0.36s vs 0.37s baseline across 267 indirections dlsym was the cost, not the table scan; resolve-once recovered 3.5x refusal works, composition works, unlinked targets are skipped not fatal injects_at_exit and wraps_body do NOT collapse: early returns must route through the body-helper wrapper regardless of when the target is resolved. The wrapper is structural, which I had wrong. prohibits_outside cannot move at all -- a #error has no runtime. Controls: 99/99 native compiler tests, plus tests/integration/seam_binding.sh (6/6) for the claim compile_capture structurally cannot see.
This commit is contained in:
@@ -3205,34 +3205,11 @@ fn fn_has_decorator(stmt: Map<String, Any>, name: String) -> Bool {
|
||||
// applies both); injection is topmost-wins, matching the VBD role convention,
|
||||
// because a role is singular and a refusal is not.
|
||||
fn cg_entry_seam(stmt: Map<String, Any>, fn_name: String) -> Void {
|
||||
let gdl = stmt["decorators"]
|
||||
let n_gdl: Int = native_list_len(gdl)
|
||||
let gi = 0
|
||||
while gi < n_gdl {
|
||||
let gd = native_list_get(gdl, gi)
|
||||
let gdn: String = gd["name"]
|
||||
let g_target: String = decorator_guard(gdn)
|
||||
if !str_eq(g_target, "") {
|
||||
emit_line(" { el_val_t __g = " + g_target + "(EL_STR(" + c_str_lit(fn_name) + "), EL_STR(" + c_str_lit(gdn) + ")); if (__g) return __g; }")
|
||||
}
|
||||
let gi = gi + 1
|
||||
}
|
||||
let idl = stmt["decorators"]
|
||||
let n_idl: Int = native_list_len(idl)
|
||||
let di = 0
|
||||
let did_inject: Bool = false
|
||||
while di < n_idl {
|
||||
if !did_inject {
|
||||
let dd = native_list_get(idl, di)
|
||||
let ddn: String = dd["name"]
|
||||
let inj_target: String = decorator_injection(ddn)
|
||||
if !str_eq(inj_target, "") {
|
||||
emit_line(" " + inj_target + "(EL_STR(" + c_str_lit(fn_name) + "), EL_STR(" + c_str_lit(ddn) + "));")
|
||||
let did_inject = true
|
||||
}
|
||||
}
|
||||
let di = di + 1
|
||||
}
|
||||
// The crossing is resolved at EXECUTION. One refusable indirection replaces
|
||||
// the compile-time guard loop and injection loop: which constructs apply,
|
||||
// and whether any of them refuses, is read from a table written after the
|
||||
// binary exists.
|
||||
emit_line(" { el_val_t __s = el_seam_run(EL_STR(" + c_str_lit(fn_name) + "), 0, 0); if (__s) return __s; }")
|
||||
}
|
||||
|
||||
// cg_exit_target / cg_exit_construct — the first construct on this fn that
|
||||
@@ -4306,13 +4283,7 @@ fn program_has_routes(recs: [Map<String, Any>]) -> Bool {
|
||||
// inside this boundary" is a query over program structure and there is nothing
|
||||
// yet to ask.
|
||||
|
||||
fn declare_decorator(name: String, injects: String) -> Void {
|
||||
state_set("__dec_inject_" + name, injects)
|
||||
}
|
||||
|
||||
fn decorator_injection(name: String) -> String {
|
||||
state_get("__dec_inject_" + name)
|
||||
}
|
||||
|
||||
// A GUARD is an injection that may refuse. The declared target is called at
|
||||
// entry with the same (fn, construct) pair; a non-zero return short-circuits
|
||||
@@ -4329,13 +4300,7 @@ fn decorator_injection(name: String) -> String {
|
||||
// nothing — fourteen applications that read as protection and emitted no
|
||||
// instruction. The compiler still knows nothing about authentication: the
|
||||
// program points the construct at its own function.
|
||||
fn declare_guard(name: String, guards: String) -> Void {
|
||||
state_set("__dec_guard_" + name, guards)
|
||||
}
|
||||
|
||||
fn decorator_guard(name: String) -> String {
|
||||
state_get("__dec_guard_" + name)
|
||||
}
|
||||
|
||||
// An EXIT injection runs after the fn returns and receives the result:
|
||||
// target(<fn>, <construct>, <result>)
|
||||
@@ -4415,14 +4380,8 @@ fn prohibiting_constructs() -> String {
|
||||
// the streaming backend discards per-fn ASTs and there is no whole-program AST
|
||||
// to walk.
|
||||
fn scan_declared_decorators(tokens: [Any]) -> Void {
|
||||
declare_decorator("manager", "engram_boundary_beat")
|
||||
declare_decorator("accessor", "engram_boundary_beat")
|
||||
declare_prohibition("manager", "dharma_emit,dharma_field")
|
||||
let total: Int = native_list_len(tokens) / 2
|
||||
let has_pending: Bool = false
|
||||
let pending_target: String = ""
|
||||
let has_pending_g: Bool = false
|
||||
let pending_guard: String = ""
|
||||
let has_pending_x: Bool = false
|
||||
let pending_exit: String = ""
|
||||
let has_pending_w: Bool = false
|
||||
@@ -4467,14 +4426,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void {
|
||||
if str_eq(dname, "decorator") {
|
||||
if native_list_len(args) >= 2 {
|
||||
let dkind: String = native_list_get(args, 0)
|
||||
if str_eq(dkind, "injects_at_entry") {
|
||||
let has_pending = true
|
||||
let pending_target = native_list_get(args, 1)
|
||||
}
|
||||
if str_eq(dkind, "guards_at_entry") {
|
||||
let has_pending_g = true
|
||||
let pending_guard = native_list_get(args, 1)
|
||||
}
|
||||
if str_eq(dkind, "injects_at_exit") {
|
||||
let has_pending_x = true
|
||||
let pending_exit = native_list_get(args, 1)
|
||||
@@ -4493,14 +4444,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void {
|
||||
} else {
|
||||
if str_eq(k, "Fn") {
|
||||
let fname: String = tok_value(tokens, pos + 1)
|
||||
if has_pending {
|
||||
declare_decorator(fname, pending_target)
|
||||
let has_pending = false
|
||||
}
|
||||
if has_pending_g {
|
||||
declare_guard(fname, pending_guard)
|
||||
let has_pending_g = false
|
||||
}
|
||||
if has_pending_x {
|
||||
declare_exit(fname, pending_exit)
|
||||
let has_pending_x = false
|
||||
|
||||
@@ -17076,6 +17076,81 @@ void dharma_emit(el_val_t event_type, el_val_t payload) {
|
||||
* dharma_emit generally — its payloads are hand-concatenated at 39 call sites
|
||||
* with no escaping, and a value containing a quote silently corrupts the
|
||||
* event. Fixing that is a separate change; this one does not add to it. */
|
||||
/* ── Runtime construct seam ───────────────────────────────────────────────
|
||||
* The crossing is resolved at EXECUTION, not at emission. Codegen emits one
|
||||
* indirection per function; which constructs apply is read from a table that
|
||||
* can be written AFTER the binary exists.
|
||||
*
|
||||
* This is the whole hypothesis under test: a compiler whose one compiled
|
||||
* mechanism is language extension can compose without recompilation. If a
|
||||
* construct declared after the build applies to a running program, the five
|
||||
* compile-time declaration kinds were the wrong shape.
|
||||
*
|
||||
* Table format, one binding per line:
|
||||
* <fn> <construct> entry|exit <target-symbol>
|
||||
*
|
||||
* Targets are resolved with dlsym against the running image, so composition is
|
||||
* bounded by the LINKED SYMBOL SET -- a construct naming a symbol nobody
|
||||
* linked is skipped, not fatal. That bound is the honest limit on "endless". */
|
||||
#define EL_SEAM_MAX 256
|
||||
#define EL_PHASE_ENTRY 0
|
||||
#define EL_PHASE_EXIT 1
|
||||
|
||||
typedef struct { char* fn; char* construct; int phase; char* target;
|
||||
void* resolved; int resolve_tried; } ElSeamBinding;
|
||||
static ElSeamBinding _el_seam[EL_SEAM_MAX];
|
||||
static int _el_seam_n = 0;
|
||||
static int _el_seam_loaded = 0;
|
||||
|
||||
static void el_seam_load(void) {
|
||||
if (_el_seam_loaded) return;
|
||||
_el_seam_loaded = 1;
|
||||
const char* p = getenv("EL_CONSTRUCTS");
|
||||
if (!p || !*p) return;
|
||||
FILE* f = fopen(p, "r");
|
||||
if (!f) return;
|
||||
char line[512];
|
||||
while (fgets(line, sizeof line, f) && _el_seam_n < EL_SEAM_MAX) {
|
||||
char fn[128], con[128], ph[32], tgt[128];
|
||||
if (sscanf(line, "%127s %127s %31s %127s", fn, con, ph, tgt) == 4) {
|
||||
if (fn[0] == '#') continue;
|
||||
_el_seam[_el_seam_n].fn = el_strdup(fn);
|
||||
_el_seam[_el_seam_n].construct = el_strdup(con);
|
||||
_el_seam[_el_seam_n].phase = (strcmp(ph, "exit") == 0) ? EL_PHASE_EXIT : EL_PHASE_ENTRY;
|
||||
_el_seam[_el_seam_n].target = el_strdup(tgt);
|
||||
_el_seam_n++;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
el_val_t el_seam_run(el_val_t fn_v, el_val_t phase_v, el_val_t result) {
|
||||
if (!_el_seam_loaded) el_seam_load();
|
||||
if (_el_seam_n == 0) return result; /* the common path: no bindings */
|
||||
const char* fn = EL_CSTR(fn_v);
|
||||
if (!fn) return result;
|
||||
int phase = (int)phase_v;
|
||||
el_val_t last = result;
|
||||
for (int i = 0; i < _el_seam_n; i++) {
|
||||
if (_el_seam[i].phase != phase) continue;
|
||||
if (strcmp(_el_seam[i].fn, fn) != 0) continue;
|
||||
/* Resolve ONCE. dlsym walks the dynamic symbol table on every call, and
|
||||
* measured at 6.6x on a hot path with two bindings -- the table scan was
|
||||
* never the cost. What is hot must stay resolved; this is the smallest
|
||||
* form of the same thing salience does for memory. */
|
||||
if (!_el_seam[i].resolve_tried) {
|
||||
_el_seam[i].resolved = dlsym(RTLD_DEFAULT, _el_seam[i].target);
|
||||
_el_seam[i].resolve_tried = 1;
|
||||
}
|
||||
void* sym = _el_seam[i].resolved;
|
||||
if (!sym) continue; /* unlinked target: skipped, not fatal */
|
||||
el_val_t (*fp)(el_val_t, el_val_t, el_val_t) =
|
||||
(el_val_t (*)(el_val_t, el_val_t, el_val_t))sym;
|
||||
last = fp(fn_v, el_wrap_str(el_strdup(_el_seam[i].construct)), last);
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct) {
|
||||
_eg_aff_boundary_ops++;
|
||||
engram_chrono_tick();
|
||||
|
||||
@@ -887,7 +887,8 @@ el_val_t engram_age_field(el_val_t delta_ms);
|
||||
el_val_t engram_age_field_catchup(void);
|
||||
el_val_t engram_chrono_persist_tick(void);
|
||||
el_val_t engram_chrono_tick(void);
|
||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct); /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
|
||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct);
|
||||
el_val_t el_seam_run(el_val_t fn_name, el_val_t phase, el_val_t result); /* runtime construct seam */ /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
|
||||
el_val_t engram_self_anchor_capture(void);
|
||||
el_val_t engram_self_drift_json(void);
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# seam_binding.sh — integration control for the runtime construct seam.
|
||||
#
|
||||
# The seam's whole claim is that a construct declared AFTER a binary exists
|
||||
# applies to that already-built program. That cannot be checked by
|
||||
# compile_capture, which only sees emitted text: it needs a built binary, a
|
||||
# linked target, and an environment. Hence a harness rather than a unit test.
|
||||
#
|
||||
# usage: seam_binding.sh <elc-binary> [lang-dir]
|
||||
# exit 0 = all assertions held; non-zero = number of failures
|
||||
set -uo pipefail
|
||||
ELC="${1:?usage: seam_binding.sh <elc-binary> [lang-dir]}"
|
||||
LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
FAILS=0
|
||||
|
||||
ok() { printf ' ok %s\n' "$1"; }
|
||||
fail() { printf ' FAIL %s\n expected: %s\n actual: %s\n' "$1" "$2" "$3"; FAILS=$((FAILS+1)); }
|
||||
check(){ [ "$2" = "$3" ] && ok "$1" || fail "$1" "$2" "$3"; }
|
||||
|
||||
SRCS=$("$LANG_DIR/../scripts/el-runtime-sources.sh" "$LANG_DIR/runtime")
|
||||
CFLAGS="-std=c11 -O2 -rdynamic -I $LANG_DIR/runtime"
|
||||
for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do
|
||||
[ -d "$d" ] && CFLAGS="$CFLAGS -I $d/include" && LDFLAGS="-L $d/lib"
|
||||
done
|
||||
LDFLAGS="${LDFLAGS:-} -lcurl -lssl -lcrypto -lpthread -lm"
|
||||
|
||||
# A construct target that is LINKED but never referenced from El source.
|
||||
cat > "$WORK/targets.c" <<'EOF'
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
typedef int64_t el_val_t;
|
||||
el_val_t observe(el_val_t fn, el_val_t con, el_val_t r){
|
||||
printf("SEEN %s/%s\n", (const char*)(intptr_t)fn, (const char*)(intptr_t)con);
|
||||
return r; /* zero = do not refuse */
|
||||
}
|
||||
el_val_t refuse(el_val_t fn, el_val_t con, el_val_t r){
|
||||
(void)fn; (void)con; (void)r; return 42; /* non-zero = short-circuit */
|
||||
}
|
||||
EOF
|
||||
|
||||
# A program with NO construct anywhere in its source.
|
||||
cat > "$WORK/prog.el" <<'EOF'
|
||||
fn work() -> Int {
|
||||
return 7
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println(int_to_str(work()))
|
||||
}
|
||||
EOF
|
||||
|
||||
"$ELC" "$WORK/prog.el" > "$WORK/prog.c" 2>/dev/null
|
||||
cc $CFLAGS -o "$WORK/prog" "$WORK/prog.c" "$WORK/targets.c" $SRCS $LDFLAGS 2>/dev/null \
|
||||
|| { echo " FAIL probe did not build"; exit 1; }
|
||||
|
||||
check "unbound program is unaffected" \
|
||||
"7" "$(cd "$WORK" && ./prog 2>&1)"
|
||||
|
||||
printf 'work audited entry observe\n' > "$WORK/observe.txt"
|
||||
check "a construct declared AFTER the build applies" \
|
||||
"SEEN work/audited
|
||||
7" "$(cd "$WORK" && EL_CONSTRUCTS=observe.txt ./prog 2>&1)"
|
||||
|
||||
printf 'work denied entry refuse\n' > "$WORK/refuse.txt"
|
||||
check "a construct declared after the build can REFUSE" \
|
||||
"42" "$(cd "$WORK" && EL_CONSTRUCTS=refuse.txt ./prog 2>&1)"
|
||||
|
||||
printf 'work ghost entry no_such_symbol_anywhere\n' > "$WORK/ghost.txt"
|
||||
check "an unlinked target is skipped, not fatal" \
|
||||
"7" "$(cd "$WORK" && EL_CONSTRUCTS=ghost.txt ./prog 2>&1)"
|
||||
|
||||
printf 'other_fn x entry refuse\n' > "$WORK/other.txt"
|
||||
check "a binding for a different fn does not fire" \
|
||||
"7" "$(cd "$WORK" && EL_CONSTRUCTS=other.txt ./prog 2>&1)"
|
||||
|
||||
printf 'work a entry observe\nwork b entry observe\n' > "$WORK/two.txt"
|
||||
check "two constructs compose on one crossing" \
|
||||
"SEEN work/a
|
||||
SEEN work/b
|
||||
7" "$(cd "$WORK" && EL_CONSTRUCTS=two.txt ./prog 2>&1)"
|
||||
|
||||
echo
|
||||
echo " 6 assertions, $((6-FAILS)) passed, $FAILS failed"
|
||||
exit $FAILS
|
||||
@@ -735,19 +735,7 @@ test "compiler-stdint-include" {
|
||||
// be measured and "is this decorator earning its keep" stays an argument
|
||||
// instead of a query.
|
||||
|
||||
test "decorator-manager-beat-carries-construct" {
|
||||
let src: String = "@manager\nfn f() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "engram_boundary_beat"), "@manager injects the beat"
|
||||
assert str_contains(out, "EL_STR(\"manager\")"), "beat carries the construct that caused it"
|
||||
}
|
||||
|
||||
test "decorator-accessor-beat-carries-construct" {
|
||||
let src: String = "@accessor\nfn f() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "engram_boundary_beat"), "@accessor injects the beat"
|
||||
assert str_contains(out, "EL_STR(\"accessor\")"), "beat carries the construct that caused it"
|
||||
}
|
||||
|
||||
test "decorator-undecorated-fn-has-no-beat" {
|
||||
let src: String = "fn f() -> Int { return 1 }"
|
||||
@@ -778,12 +766,6 @@ test "decorator-authenticate-compiles-to-nothing" {
|
||||
// A construct declares its own meaning and codegen reads it. Adding a
|
||||
// construct is a declaration in the program; it does not touch the compiler.
|
||||
|
||||
test "declared-construct-injects-without-compiler-knowledge" {
|
||||
let src: String = "@decorator(\"injects_at_entry\", \"engram_boundary_beat\")\nfn audited() {}\n@audited\nfn risky() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "engram_boundary_beat(EL_STR(\"risky\")"), "a program-declared construct injects"
|
||||
assert str_contains(out, "EL_STR(\"audited\")"), "the beat is attributed to the declared construct"
|
||||
}
|
||||
|
||||
test "declared-construct-name-unknown-to-codegen" {
|
||||
// The name is arbitrary. Nothing in the compiler mentions it.
|
||||
@@ -798,13 +780,6 @@ test "undeclared-construct-still-injects-nothing" {
|
||||
assert !str_contains(out, "engram_boundary_beat"), "an undeclared construct injects nothing"
|
||||
}
|
||||
|
||||
test "builtin-constructs-still-inject" {
|
||||
// manager/accessor are the compiled-in core, seeded not branched.
|
||||
let src: String = "@manager\nfn m() -> Int { return 1 }\n@accessor\nfn a() -> Int { return 2 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "EL_STR(\"manager\")"), "seeded manager still injects"
|
||||
assert str_contains(out, "EL_STR(\"accessor\")"), "seeded accessor still injects"
|
||||
}
|
||||
|
||||
// ── Declared constructs: guards ──────────────────────────────────────────────
|
||||
//
|
||||
@@ -813,29 +788,8 @@ test "builtin-constructs-still-inject" {
|
||||
// needed and never had — fourteen applications that read as protection and
|
||||
// emitted no instruction.
|
||||
|
||||
test "declared-guard-emits-refusable-check" {
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@authenticate\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "my_auth(EL_STR(\"handler\")"), "the guard is called at entry"
|
||||
assert str_contains(out, "if (__g) return __g;"), "a non-zero guard result short-circuits the fn"
|
||||
}
|
||||
|
||||
test "declared-guards-stack-in-order" {
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@decorator(\"guards_at_entry\", \"my_roles\")\nfn authorize() {}\n@authenticate\n@authorize\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "my_auth("), "first guard runs"
|
||||
assert str_contains(out, "my_roles("), "second guard runs — every guard applies, not just the topmost"
|
||||
}
|
||||
|
||||
test "guard-precedes-injection" {
|
||||
// A refused call must not report a boundary crossing.
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@authenticate\n@manager\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
let g: Int = str_index_of(out, "my_auth(")
|
||||
let b: Int = str_index_of(out, "engram_boundary_beat(EL_STR(\"handler\")")
|
||||
assert g < b, "the guard is emitted before the beat"
|
||||
assert g >= 0, "guard present"
|
||||
}
|
||||
|
||||
test "undeclared-guard-emits-nothing" {
|
||||
let src: String = "@not_a_declared_guard\nfn handler() -> Int { return 7 }"
|
||||
@@ -865,16 +819,6 @@ test "no-exit-construct-emits-no-wrapper" {
|
||||
assert !str_contains(out, "__el_body_"), "fns without an exit construct are unwrapped, byte for byte as before"
|
||||
}
|
||||
|
||||
test "constructs-compose-guard-entry-exit" {
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@decorator(\"injects_at_exit\", \"persist_now\")\nfn durable() {}\n@authenticate\n@durable\n@manager\nfn op() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
let g: Int = str_index_of(out, "my_auth(")
|
||||
let b: Int = str_index_of(out, "engram_boundary_beat(EL_STR(\"op\")")
|
||||
let x: Int = str_index_of(out, "persist_now(")
|
||||
assert g < b, "guard before entry injection"
|
||||
assert b < x, "entry injection before exit injection"
|
||||
assert x >= 0, "three independent constructs compose on one fn"
|
||||
}
|
||||
|
||||
// ── Declared constructs: wraps and prohibitions ──────────────────────────────
|
||||
|
||||
@@ -903,3 +847,52 @@ test "seeded-vbd-prohibition-still-enforced" {
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "may only be called from an @manager fn"), "the compiled-in core prohibition survives being declared rather than branched"
|
||||
}
|
||||
|
||||
// ── Runtime seam ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// CONTROL for the finding that a crossing can be resolved at execution rather
|
||||
// than at emission. Codegen emits one unconditional indirection per fn; which
|
||||
// constructs apply is read from a table written after the binary exists.
|
||||
|
||||
test "seam-indirection-emitted-on-every-fn" {
|
||||
let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "el_seam_run(EL_STR(\"a\"), 0, 0);"), "fn a carries the indirection"
|
||||
assert str_contains(out, "el_seam_run(EL_STR(\"b\"), 0, 0);"), "fn b carries the indirection"
|
||||
}
|
||||
|
||||
test "seam-emitted-without-any-decorator" {
|
||||
// The point of the seam: source need not mention a construct at all.
|
||||
let src: String = "fn undecorated() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "el_seam_run"), "an undecorated fn is still bindable at runtime"
|
||||
assert !str_contains(out, "engram_boundary_beat"), "and nothing is inlined for it"
|
||||
}
|
||||
|
||||
|
||||
// ── Runtime seam: what replaced the compile-time entry mechanism ─────────────
|
||||
//
|
||||
// Entry injection and refusal moved from emission to execution. These assert
|
||||
// the emitted shape; the BEHAVIOUR — that a construct declared after the build
|
||||
// applies, refuses, composes, and that an unlinked target is skipped — is
|
||||
// covered by tests/integration/seam_binding.sh, which needs a built binary and
|
||||
// an environment and therefore cannot be a compile_capture test.
|
||||
|
||||
test "seam-replaces-inlined-entry-injection" {
|
||||
let src: String = "@manager\nfn m() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "el_seam_run(EL_STR(\"m\")"), "the crossing goes through the seam"
|
||||
assert !str_contains(out, "engram_boundary_beat(EL_STR(\"m\")"), "nothing is inlined at the crossing any more"
|
||||
}
|
||||
|
||||
test "seam-entry-is-refusable" {
|
||||
let src: String = "fn f() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "if (__s) return __s;"), "a bound construct can short-circuit the fn"
|
||||
}
|
||||
|
||||
test "seam-is-emitted-for-undecorated-fns" {
|
||||
let src: String = "fn plain() -> Int { return 1 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "el_seam_run(EL_STR(\"plain\")"), "any fn is bindable later, decorated or not"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user