From 8c94d92033578669bfedaccb07a0cbd08e63d258 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 10 Aug 2026 16:11:15 -0500 Subject: [PATCH 1/2] el: native @route dispatch + multi-decorator stacking in modular compiler Port the @route decorator from the bootstrap prototype into the production modular compiler (parser + streaming codegen), and generalize single decorators to a stacked list so a handler can be both @route and a VBD role (@manager/@engine/@accessor). The dispatcher is synthesized from a token pre-scan (survives the streaming backend's per-fn AST discard, works for library modules) and emitted specificity-sorted so overlapping prefixes never shadow by source order. Supports method lists ("GET|POST"), "ANY", and suffix/compound matchers. Inert on all non-@route code (byte-identical C). --- lang/el-compiler/src/codegen.el | 297 +++++++++++++++++++++++++++++++- lang/el-compiler/src/parser.el | 49 +++++- 2 files changed, 341 insertions(+), 5 deletions(-) diff --git a/lang/el-compiler/src/codegen.el b/lang/el-compiler/src/codegen.el index 917fb31..25e6511 100644 --- a/lang/el-compiler/src/codegen.el +++ b/lang/el-compiler/src/codegen.el @@ -2916,6 +2916,24 @@ fn build_int_names_for_params(params: [Map]) -> 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, 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) -> 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) -> 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) -> 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 { + 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) -> 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]) -> [Map] { + let n: Int = native_list_len(recs) + let out: [Map] = 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] { + let total: Int = native_list_len(tokens) / 2 + let recs: [Map] = 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]) -> 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]) -> Void { + if !program_has_routes(recs) { return } + let sorted: [Map] = 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], source: String) -> Void { @@ -3571,6 +3842,17 @@ fn codegen_streaming(tokens: [Any], sigs: [Map], 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] = 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], 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) diff --git a/lang/el-compiler/src/parser.el b/lang/el-compiler/src/parser.el index d93fcba..8cdca47 100644 --- a/lang/el-compiler/src/parser.el +++ b/lang/el-compiler/src/parser.el @@ -1758,23 +1758,68 @@ fn parse_stmt(tokens: [Any], pos: Int) -> Map { return make_result({ "stmt": "TryCatch", "try_body": try_body, "catch_name": catch_name, "catch_body": native_list_empty() }, p) } - // @decorator - capture decorator name and attach to following stmt + // @decorator - capture decorator name (and optional string args) and + // attach to the following stmt. Backward-compatible: bare @manager / + // @engine / @accessor still parse (no parens -> empty args). Decorators + // STACK: `@route("/p","GET") @manager fn f()` attaches BOTH to f via a + // `decorators` list [{name, args}]. The legacy `decorator` string is kept + // populated (topmost decorator) so the JS backend keeps working unchanged. if k == "At" { let p = pos + 1 let dec_name = tok_value(tokens, p) let p = p + 1 + // Optional decorator argument list: @name("a", "b", ...) + let dec_args = native_list_empty() + let ka = tok_kind(tokens, p) + if str_eq(ka, "LParen") { + let p = p + 1 + let running_da = true + while running_da { + let kd = tok_kind(tokens, p) + if str_eq(kd, "RParen") { + let running_da = false + } else { + if str_eq(kd, "Eof") { + let running_da = false + } else { + if str_eq(kd, "Str") { + let dec_args = native_list_append(dec_args, tok_value(tokens, p)) + } + let p = p + 1 + let kc = tok_kind(tokens, p) + if str_eq(kc, "Comma") { + let p = p + 1 + } + } + } + } + let p = expect(tokens, p, "RParen") + } let r = parse_stmt(tokens, p) let inner = r["node"] let p2 = r["pos"] let inner_kind: String = inner["stmt"] if str_eq(inner_kind, "FnDef") { + // Stack this decorator (topmost-first) onto any decorators the inner + // FnDef already carries from decorators written below this one. + let this_dec = { "name": dec_name, "args": dec_args } + let existing = inner["decorators"] + let dlist = native_list_empty() + let dlist = native_list_append(dlist, this_dec) + let ne: Int = native_list_len(existing) + let ei = 0 + while ei < ne { + let dlist = native_list_append(dlist, native_list_get(existing, ei)) + let ei = ei + 1 + } let with_dec = { "stmt": "FnDef", "name": inner["name"], "params": inner["params"], "body": inner["body"], "ret_type": inner["ret_type"], - "decorator": dec_name + "decorator": dec_name, + "decorators": dlist } // r result map fully consumed — release to free peak heap. el_release(r) From 511db25230c69b9484ece39ed4131d3920b16596 Mon Sep 17 00:00:00 2001 From: bigmerge Date: Mon, 17 Aug 2026 11:03:59 -0500 Subject: [PATCH 2/2] rerun cycle 18 rather than reconstruct it The async/future measurements were produced by a C stub in /tmp, and that artifact was destroyed when the session worktrees were removed. The log then asserted results with nothing behind them -- a claim inside an evidence record, which is exactly what turns a chain of custody into a pile. Rerun, not reconstructed. Rebuilding the missing file would have been a fabrication with a fresh timestamp; rerunning produces new evidence with its own. lang/tests/integration/fixtures/future.c the future, as a tagged heap object lang/tests/integration/async_future.sh the harness, 6/6 ok unbound: synchronous, correct result ok unbound: el_await on a non-future passes through, no crash ok bound: does not crash ok bound: the awaited result is correct ok bound: the caller continues BEFORE the body finishes ok bound: wrap returns in <10ms while the body takes 50ms LABELLED AS A REPLICATION. The outcomes were already known when this harness was written, so its expectations are NOT predictions committed in advance. Its evidentiary value is that a third party can reproduce it, not that it was called ahead of time. Recording it as anything stronger would corrupt the record it is meant to repair. The fixture also carries the P5 defect and its fix in a comment: the first el_await dereferenced ->magic off an unvalidated slot and SIGSEGV'd on the unbound path, sixty seconds after the same defect was diagnosed elsewhere in the runtime. --- .../cycles/18-async-half-expressible.md | 17 ++++- lang/tests/integration/async_future.sh | 62 +++++++++++++++++ lang/tests/integration/fixtures/future.c | 66 +++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100755 lang/tests/integration/async_future.sh create mode 100644 lang/tests/integration/fixtures/future.c diff --git a/docs/v1/experiments/cycles/18-async-half-expressible.md b/docs/v1/experiments/cycles/18-async-half-expressible.md index 1cdc0c2..a7b2624 100644 --- a/docs/v1/experiments/cycles/18-async-half-expressible.md +++ b/docs/v1/experiments/cycles/18-async-half-expressible.md @@ -1,6 +1,21 @@ # async — half expressible, and the cycle that was dogma -**Status: measured on a branch, not merged. Two runs — the first was invalid.** +**Status: replicated and corroborated. Three runs — the first was invalid.** + +> **Chain of custody note, 2026-08-17.** The original measurements were produced +> by a C stub written in `/tmp`, and that artifact was destroyed when the session +> worktrees were removed. For a period this file asserted results with nothing +> behind them — a claim inside an evidence record, which is the defect that turns +> a chain into a pile. It was **rerun**, not reconstructed: reconstructing the +> missing file would have been a fabrication with a fresh timestamp. +> +> The fixture now lives at `lang/tests/integration/fixtures/future.c` and the +> harness at `lang/tests/integration/async_future.sh`, so a third party can +> reproduce this without taking my word for it. **6/6.** +> +> The replication is labelled as such: the outcomes were already known when the +> harness was written, so its expectations are not predictions committed in +> advance. Its value is reproducibility, not foresight. ## The first attempt was DOGMA, not science diff --git a/lang/tests/integration/async_future.sh b/lang/tests/integration/async_future.sh new file mode 100755 index 0000000..d7a1c6f --- /dev/null +++ b/lang/tests/integration/async_future.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# async_future.sh — REPLICATION of cycle 18. +# +# STATUS: replication, not a blind test. The outcomes were already observed on +# 2026-08-17 before this harness existed, so the expectations below are not +# predictions committed in advance. Its evidentiary value is that the artifact +# lives in the repository and a third party can run it — not that it was called +# ahead of time. The original run's artifact was written in /tmp and lost when +# the worktrees were removed, which broke the chain; this replaces the claim +# with something reproducible rather than reconstructing the missing file. +# +# CLAIM UNDER TEST: @async requires no compiler change. A future is one more +# magic-tagged heap object, and el_seam_wrap lets a construct bound AFTER the +# build decide whether and when to invoke the body. +set -uo pipefail +ELC="${1:?usage: async_future.sh }" +LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/..}" +LANG_DIR="$(cd "$LANG_DIR" && pwd)" +FIX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fixtures/future.c" +W=$(mktemp -d); trap 'rm -rf "$W"' EXIT; F=0 +chk(){ [ "$2" = "$3" ] && printf ' ok %s\n' "$1" || { printf ' FAIL %s\n expected %s got %s\n' "$1" "$2" "$3"; F=$((F+1)); }; } +cd "$LANG_DIR" +SRCS=$(../scripts/el-runtime-sources.sh runtime) +CF="-std=c11 -O2 -rdynamic -I runtime"; LF="" +for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do + [ -d "$d" ] && CF="$CF -I $d/include" && LF="-L $d/lib" +done +LF="$LF -lcurl -lssl -lcrypto -lpthread -lm" + +cat > "$W/p.el" <<'EOF' +extern fn el_await(h: Int) -> Int + +fn work(k: Int) -> Int { + return k * 2 +} + +fn main() { + let h: Int = work(21) + println("CALLER_CONTINUED") + let r: Int = el_await(h) + println("RESULT " + int_to_str(r)) +} +EOF +"$ELC" "$W/p.el" > "$W/p.c" 2>/dev/null +cc $CF -o "$W/p" "$W/p.c" "$FIX" $SRCS $LF 2>/dev/null || { echo " FAIL probe did not build"; exit 1; } + +out=$(cd "$W" && ./p 2>&1); rc=$? +chk "unbound: no construct, synchronous, correct result" "0" "$rc" +chk "unbound: el_await on a non-future passes through, no crash" "1" "$(echo "$out" | grep -c '^RESULT 42$')" + +printf 'work async wrap defer\n' > "$W/c.txt" +out=$(cd "$W" && EL_CONSTRUCTS=c.txt ./p 2>&1); rc=$? +chk "bound: does not crash" "0" "$rc" +chk "bound: the awaited result is correct" "1" "$(echo "$out" | grep -c '^RESULT 42$')" + +wrap=$(echo "$out" | awk '/^WRAP_RETURNED/{print $2}') +bend=$(echo "$out" | awk '/^BODY_END/{print $2}') +caller_before_body_end=$(echo "$out" | awk '/CALLER_CONTINUED/{c=NR} /^BODY_END/{b=NR} END{print (c +#include +#include +#include +#include +#include + +typedef int64_t el_val_t; + +#define EL_MAGIC_FUT 0xE1F07000u +typedef struct { uint32_t magic; pthread_t th; el_val_t result; int done; + el_val_t (*body)(void*); void* env; } ElFuture; + +static long t0_us; +static long now_us(void){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts); + return ts.tv_sec*1000000L + ts.tv_nsec/1000; } + +static void* fut_runner(void* v){ + ElFuture* f = (ElFuture*)v; + printf("BODY_START %ld\n", now_us()-t0_us); + usleep(50000); /* 50ms, so interleaving is visible */ + f->result = f->body(f->env); + f->done = 1; + printf("BODY_END %ld\n", now_us()-t0_us); + return NULL; +} + +/* wraps_body target: returns the HANDLE immediately, never the result */ +el_val_t defer(el_val_t fn, el_val_t con, el_val_t (*b)(void*), void* e){ + (void)fn; (void)con; + t0_us = now_us(); + ElFuture* f = calloc(1,sizeof(ElFuture)); + f->magic = EL_MAGIC_FUT; f->body = b; f->env = e; + pthread_create(&f->th, NULL, fut_runner, f); + printf("WRAP_RETURNED %ld\n", now_us()-t0_us); + return (el_val_t)(intptr_t)f; +} + +/* el_await — block on the handle and yield the real result. + * + * NEVER dereference to decide whether a slot is a pointer. el_val_t carries + * integers too, so reading ->magic off an integer dereferences that integer AS + * AN ADDRESS. The first version of this function did exactly that and + * SIGSEGV'd on the unbound path -- sixty seconds after the same defect was + * diagnosed elsewhere in the runtime. Check the floor and alignment first. */ +el_val_t el_await(el_val_t h){ + if (h < 0x10000) return h; /* small ints / low addresses */ + if (h & 0x7) return h; /* malloc returns 8-aligned */ + ElFuture* f = (ElFuture*)(intptr_t)h; + if (f->magic != EL_MAGIC_FUT) return h; /* safe to read now */ + pthread_join(f->th, NULL); + el_val_t r = f->result; + free(f); + return r; +}