diff --git a/hosts/javascript/platform.py b/hosts/javascript/platform.py index a9c20a4..5ca8756 100644 --- a/hosts/javascript/platform.py +++ b/hosts/javascript/platform.py @@ -1140,6 +1140,7 @@ PRIMITIVES_JS_MODULES: dict[str, str] = { } PRIMITIVES["scope-emit!"] = scopeEmit; PRIMITIVES["scope-peek"] = scopePeek; + PRIMITIVES["scope-emitted"] = sxEmitted; // ---- VM stack primitives ---- // The VM spec (vm.sx) requires these array-like operations. diff --git a/hosts/ocaml/bin/run_tests.ml b/hosts/ocaml/bin/run_tests.ml index b4084f2..cb0bd94 100644 --- a/hosts/ocaml/bin/run_tests.ml +++ b/hosts/ocaml/bin/run_tests.ml @@ -177,33 +177,39 @@ let make_test_env () = (* --- Environment operations --- *) + (* Env operations — accept both Env and Dict *) + let uw = Sx_runtime.unwrap_env in bind "env-get" (fun args -> match args with - | [Env e; String k] -> Sx_types.env_get e k - | [Env e; Keyword k] -> Sx_types.env_get e k + | [e; String k] -> Sx_types.env_get (uw e) k + | [e; Keyword k] -> Sx_types.env_get (uw e) k | _ -> raise (Eval_error "env-get: expected env and string")); bind "env-has?" (fun args -> match args with - | [Env e; String k] -> Bool (Sx_types.env_has e k) - | [Env e; Keyword k] -> Bool (Sx_types.env_has e k) + | [e; String k] -> Bool (Sx_types.env_has (uw e) k) + | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) | _ -> raise (Eval_error "env-has?: expected env and string")); bind "env-bind!" (fun args -> match args with - | [Env e; String k; v] -> Sx_types.env_bind e k v - | [Env e; Keyword k; v] -> Sx_types.env_bind e k v + | [e; String k; v] -> + let ue = uw e in + if k = "x" || k = "children" || k = "i" then + Printf.eprintf "[env-bind!] '%s' env-id=%d bindings-before=%d\n%!" k (Obj.obj (Obj.repr ue) : int) (Hashtbl.length ue.Sx_types.bindings); + Sx_types.env_bind ue k v + | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v | _ -> raise (Eval_error "env-bind!: expected env, key, value")); bind "env-set!" (fun args -> match args with - | [Env e; String k; v] -> Sx_types.env_set e k v - | [Env e; Keyword k; v] -> Sx_types.env_set e k v + | [e; String k; v] -> Sx_types.env_set (uw e) k v + | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v | _ -> raise (Eval_error "env-set!: expected env, key, value")); bind "env-extend" (fun args -> match args with - | [Env e] -> Env (Sx_types.env_extend e) + | [e] -> Env (Sx_types.env_extend (uw e)) | _ -> raise (Eval_error "env-extend: expected env")); bind "env-merge" (fun args -> @@ -279,18 +285,66 @@ let make_test_env () = | _ -> Nil); bind "eval-expr" (fun args -> match args with - | [expr; Env e] -> eval_expr expr (Env e) - | [expr; Dict d] -> - (* Dict used as env — wrap it *) - let e = Sx_types.make_env () in - Hashtbl.iter (fun k v -> ignore (Sx_types.env_bind e k v)) d; - eval_expr expr (Env e) + | [expr; e] -> + let ue = Sx_runtime.unwrap_env e in + eval_expr expr (Env ue) | [expr] -> eval_expr expr (Env env) | _ -> raise (Eval_error "eval-expr: expected (expr env)")); + (* Scope primitives — use a local scope stacks table. + Must match the same pattern as sx_server.ml's _scope_stacks. *) + let _scope_stacks : (string, Sx_types.value list) Hashtbl.t = Hashtbl.create 8 in bind "scope-push!" (fun args -> - ignore (Sx_runtime.scope_push (List.hd args) (if List.length args > 1 then List.nth args 1 else Nil)); Nil); + match args with + | [String name; value] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + Hashtbl.replace _scope_stacks name (value :: stack); Nil + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + Hashtbl.replace _scope_stacks name (Nil :: stack); Nil + | _ -> Nil); bind "scope-pop!" (fun args -> - ignore (Sx_runtime.scope_pop (List.hd args)); Nil); + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with _ :: rest -> Hashtbl.replace _scope_stacks name rest | [] -> ()); Nil + | _ -> Nil); + bind "scope-emit!" (fun args -> + match args with + | [String name; value] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with + | List items :: rest -> + Hashtbl.replace _scope_stacks name (List (items @ [value]) :: rest) + | _ :: rest -> + Hashtbl.replace _scope_stacks name (List [value] :: rest) + | [] -> + Hashtbl.replace _scope_stacks name [List [value]]); + Nil + | _ -> Nil); + bind "emitted" (fun args -> + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with List items :: _ -> List items | _ -> List []) + | _ -> List []); + bind "scope-emitted" (fun args -> + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with List items :: _ -> List items | _ -> List []) + | _ -> List []); + bind "provide-push!" (fun args -> + match args with + | [String name; value] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + Hashtbl.replace _scope_stacks name (value :: stack); Nil + | _ -> Nil); + bind "provide-pop!" (fun args -> + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with _ :: rest -> Hashtbl.replace _scope_stacks name rest | [] -> ()); Nil + | _ -> Nil); bind "cond-scheme?" (fun args -> match args with | [(List clauses | ListRef { contents = clauses })] -> diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 5142baf..3585f0f 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -194,6 +194,43 @@ let () = Sx_primitives.register "clear-collected!" (fun args -> Nil | _ -> Nil) +(* emit!/emitted — adapter-html.sx uses these for spread attr collection *) +let () = Sx_primitives.register "scope-emit!" (fun args -> + match args with + | [String name; value] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with + | List items :: rest -> + Hashtbl.replace _scope_stacks name (List (items @ [value]) :: rest) + | v :: rest -> + (* Non-list top — wrap current entries as list + new value *) + Hashtbl.replace _scope_stacks name (List [value] :: v :: rest) + | [] -> + Hashtbl.replace _scope_stacks name [List [value]]); + Nil + | _ -> Nil) + +let () = Sx_primitives.register "emitted" (fun args -> + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with List items :: _ -> List items | _ -> List []) + | _ -> List []) + +let () = Sx_primitives.register "scope-emitted" (fun args -> + match args with + | [String name] -> + let stack = try Hashtbl.find _scope_stacks name with Not_found -> [] in + (match stack with List items :: _ -> List items | _ -> List []) + | _ -> List []) + +let () = Sx_primitives.register "provide-push!" (fun args -> + match Sx_primitives.get_primitive "scope-push!" with + | NativeFn (_, fn) -> fn args | _ -> Nil) +let () = Sx_primitives.register "provide-pop!" (fun args -> + match Sx_primitives.get_primitive "scope-pop!" with + | NativeFn (_, fn) -> fn args | _ -> Nil) + let () = Sx_primitives.register "scope-emit!" (fun args -> match args with | [String name; value] -> @@ -510,7 +547,7 @@ let make_server_env () = Route to the OCaml CEK machine. *) bind "eval-expr" (fun args -> match args with - | [expr; Env e] -> Sx_ref.eval_expr expr (Env e) + | [expr; e] -> Sx_ref.eval_expr expr (Env (Sx_runtime.unwrap_env e)) | [expr] -> Sx_ref.eval_expr expr (Env env) | _ -> raise (Eval_error "eval-expr: expected (expr env?)")); bind "trampoline" (fun args -> @@ -792,34 +829,35 @@ let make_server_env () = | [v] -> String (inspect v) | _ -> raise (Eval_error "sx-serialize: expected 1 arg")); - (* Env operations *) + (* Env operations — accept both Env and Dict (adapter-html.sx passes dicts) *) + let uw = Sx_runtime.unwrap_env in bind "env-get" (fun args -> match args with - | [Env e; String k] -> env_get e k - | [Env e; Keyword k] -> env_get e k + | [e; String k] -> Sx_types.env_get (uw e) k + | [e; Keyword k] -> Sx_types.env_get (uw e) k | _ -> raise (Eval_error "env-get: expected env and string")); bind "env-has?" (fun args -> match args with - | [Env e; String k] -> Bool (env_has e k) - | [Env e; Keyword k] -> Bool (env_has e k) + | [e; String k] -> Bool (Sx_types.env_has (uw e) k) + | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) | _ -> raise (Eval_error "env-has?: expected env and string")); bind "env-bind!" (fun args -> match args with - | [Env e; String k; v] -> env_bind e k v - | [Env e; Keyword k; v] -> env_bind e k v + | [e; String k; v] -> Sx_types.env_bind (uw e) k v + | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v | _ -> raise (Eval_error "env-bind!: expected env, key, value")); bind "env-set!" (fun args -> match args with - | [Env e; String k; v] -> env_set e k v - | [Env e; Keyword k; v] -> env_set e k v + | [e; String k; v] -> Sx_types.env_set (uw e) k v + | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v | _ -> raise (Eval_error "env-set!: expected env, key, value")); bind "env-extend" (fun args -> match args with - | [Env e] -> Env (env_extend e) + | [e] -> Env (Sx_types.env_extend (uw e)) | _ -> raise (Eval_error "env-extend: expected env")); bind "env-merge" (fun args -> diff --git a/hosts/ocaml/bootstrap.py b/hosts/ocaml/bootstrap.py index c5df711..236530b 100644 --- a/hosts/ocaml/bootstrap.py +++ b/hosts/ocaml/bootstrap.py @@ -72,6 +72,9 @@ let () = trampoline_fn := (fun v -> | Thunk (expr, env) -> eval_expr expr (Env env) | _ -> v) +(* Wire up the primitives trampoline so call_any in HO forms resolves Thunks *) +let () = Sx_primitives._sx_trampoline_fn := !trampoline_fn + (* Override recursive cek_run with iterative loop *) let cek_run_iterative state = let s = ref state in diff --git a/hosts/ocaml/lib/sx_primitives.ml b/hosts/ocaml/lib/sx_primitives.ml index 6d4e7f5..73e0045 100644 --- a/hosts/ocaml/lib/sx_primitives.ml +++ b/hosts/ocaml/lib/sx_primitives.ml @@ -701,49 +701,8 @@ let () = | [String name] -> Bool (Hashtbl.mem primitives name) | _ -> Bool false); - (* ---- Scope stack primitives (for adapter-html.sx tree-walk rendering) ---- *) - let scope_stacks : (string, (value * value list) list) Hashtbl.t = Hashtbl.create 8 in - register "scope-push!" (fun args -> - match args with - | [String name; value] -> - let stack = try Hashtbl.find scope_stacks name with Not_found -> [] in - Hashtbl.replace scope_stacks name ((value, []) :: stack); Nil - | [String name] -> - let stack = try Hashtbl.find scope_stacks name with Not_found -> [] in - Hashtbl.replace scope_stacks name ((Nil, []) :: stack); Nil - | _ -> Nil); - register "scope-pop!" (fun args -> - match args with - | [String name] -> - let stack = try Hashtbl.find scope_stacks name with Not_found -> [] in - (match stack with _ :: rest -> Hashtbl.replace scope_stacks name rest | [] -> ()); Nil - | _ -> Nil); - register "scope-peek" (fun args -> - match args with - | [String name] -> - (match Hashtbl.find_opt scope_stacks name with - | Some ((v, _) :: _) -> v - | _ -> Nil) - | _ -> Nil); - register "scope-emit!" (fun args -> - match args with - | [String name; value] -> - (match Hashtbl.find_opt scope_stacks name with - | Some ((v, emitted) :: rest) -> - Hashtbl.replace scope_stacks name ((v, emitted @ [value]) :: rest) - | _ -> ()); Nil - | _ -> Nil); - register "emitted" (fun args -> - match args with - | [String name] -> - (match Hashtbl.find_opt scope_stacks name with - | Some ((_, emitted) :: _) -> List emitted - | _ -> List []) - | _ -> List []); - register "provide-push!" (fun args -> - Hashtbl.find primitives "scope-push!" args); - register "provide-pop!" (fun args -> - Hashtbl.find primitives "scope-pop!" args); + (* Scope stack primitives are registered by sx_server.ml / run_tests.ml + because they use a shared scope stacks table with collect!/collected. *) (* ---- Predicates needed by adapter-html.sx ---- *) register "lambda?" (fun args -> diff --git a/hosts/ocaml/lib/sx_ref.ml b/hosts/ocaml/lib/sx_ref.ml index 67603bb..a06b0db 100644 --- a/hosts/ocaml/lib/sx_ref.ml +++ b/hosts/ocaml/lib/sx_ref.ml @@ -399,23 +399,23 @@ and step_sf_lambda args env kont = (* step-sf-scope *) and step_sf_scope args env kont = - (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let rest_args = (prim_call "slice" [args; (Number 1.0)]) in let val' = ref (Nil) in let body = ref (Nil) in (let () = ignore ((if sx_truthy ((let _and = (prim_call ">=" [(len (rest_args)); (Number 2.0)]) in if not (sx_truthy _and) then _and else (let _and = (prim_call "=" [(type_of ((first (rest_args)))); (String "keyword")]) in if not (sx_truthy _and) then _and else (prim_call "=" [(keyword_name ((first (rest_args)))); (String "value")])))) then (let () = ignore ((val' := (trampoline ((eval_expr ((nth (rest_args) ((Number 1.0)))) (env)))); Nil)) in (body := (prim_call "slice" [rest_args; (Number 2.0)]); Nil)) else (body := rest_args; Nil))) in (let () = ignore ((scope_push (name) (!val'))) in (let result' = ref (Nil) in (let () = ignore ((List.iter (fun expr -> ignore ((result' := (trampoline ((eval_expr (expr) (env)))); Nil))) (sx_to_list !body); Nil)) in (let () = ignore ((scope_pop (name))) in (make_cek_value (!result') (env) (kont)))))))) + (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let rest_args = (prim_call "slice" [args; (Number 1.0)]) in let val' = ref (Nil) in let body = ref (Nil) in (let () = ignore ((if sx_truthy ((let _and = (prim_call ">=" [(len (rest_args)); (Number 2.0)]) in if not (sx_truthy _and) then _and else (let _and = (prim_call "=" [(type_of ((first (rest_args)))); (String "keyword")]) in if not (sx_truthy _and) then _and else (prim_call "=" [(keyword_name ((first (rest_args)))); (String "value")])))) then (let () = ignore ((val' := (trampoline ((eval_expr ((nth (rest_args) ((Number 1.0)))) (env)))); Nil)) in (body := (prim_call "slice" [rest_args; (Number 2.0)]); Nil)) else (body := rest_args; Nil))) in (if sx_truthy ((empty_p (!body))) then (make_cek_value (Nil) (env) (kont)) else (make_cek_state ((first (!body))) (env) ((kont_push ((make_scope_acc_frame (name) (!val') ((rest (!body))) (env))) (kont))))))) (* step-sf-provide *) and step_sf_provide args env kont = - (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let val' = (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) in let body = (prim_call "slice" [args; (Number 2.0)]) in (let () = ignore ((scope_push (name) (val'))) in (let result' = ref (Nil) in (let () = ignore ((List.iter (fun expr -> ignore ((result' := (trampoline ((eval_expr (expr) (env)))); Nil))) (sx_to_list body); Nil)) in (let () = ignore ((scope_pop (name))) in (make_cek_value (!result') (env) (kont))))))) + (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let val' = (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) in let body = (prim_call "slice" [args; (Number 2.0)]) in (if sx_truthy ((empty_p (body))) then (make_cek_value (Nil) (env) (kont)) else (make_cek_state ((first (body))) (env) ((kont_push ((make_provide_frame (name) (val') ((rest (body))) (env))) (kont)))))) (* step-sf-context *) and step_sf_context args env kont = - (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let default_val = (if sx_truthy ((prim_call ">=" [(len (args)); (Number 2.0)])) then (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) else Nil) in let val' = (scope_peek (name)) in (make_cek_value ((if sx_truthy ((is_nil (val'))) then default_val else val')) (env) (kont))) + (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let default_val = (if sx_truthy ((prim_call ">=" [(len (args)); (Number 2.0)])) then (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) else Nil) in let frame = (kont_find_provide (kont) (name)) in (make_cek_value ((if sx_truthy ((is_nil (frame))) then default_val else (get (frame) ((String "value"))))) (env) (kont))) (* step-sf-emit *) and step_sf_emit args env kont = - (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let val' = (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) in (let () = ignore ((scope_emit (name) (val'))) in (make_cek_value (Nil) (env) (kont)))) + (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let val' = (trampoline ((eval_expr ((nth (args) ((Number 1.0)))) (env)))) in let frame = (kont_find_scope_acc (kont) (name)) in (let () = ignore ((if sx_truthy (frame) then (sx_dict_set_b frame (String "emitted") (prim_call "append" [(get (frame) ((String "emitted"))); (List [val'])])) else Nil)) in (make_cek_value (Nil) (env) (kont)))) (* step-sf-emitted *) and step_sf_emitted args env kont = - (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let val' = (scope_peek (name)) in (make_cek_value ((if sx_truthy ((is_nil (val'))) then (List []) else val')) (env) (kont))) + (let name = (trampoline ((eval_expr ((first (args))) (env)))) in let frame = (kont_find_scope_acc (kont) (name)) in (make_cek_value ((if sx_truthy ((is_nil (frame))) then (List []) else (get (frame) ((String "emitted"))))) (env) (kont))) (* step-sf-reset *) and step_sf_reset args env kont = @@ -516,6 +516,9 @@ let () = trampoline_fn := (fun v -> | Thunk (expr, env) -> eval_expr expr (Env env) | _ -> v) +(* Wire up the primitives trampoline so call_any in HO forms resolves Thunks *) +let () = Sx_primitives._sx_trampoline_fn := !trampoline_fn + (* Override recursive cek_run with iterative loop *) let cek_run_iterative state = let s = ref state in diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index a7af9c0..2aa1df1 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-24T01:05:46Z"; + var SX_VERSION = "2026-03-24T02:21:48Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -520,6 +520,7 @@ } PRIMITIVES["scope-emit!"] = scopeEmit; PRIMITIVES["scope-peek"] = scopePeek; + PRIMITIVES["scope-emitted"] = sxEmitted; // ---- VM stack primitives ---- // The VM spec (vm.sx) requires these array-like operations. @@ -540,6 +541,11 @@ if (name in PRIMITIVES) return PRIMITIVES[name]; throw new Error("VM undefined: " + name); }; + PRIMITIVES["call-primitive"] = function(name, args) { + if (!(name in PRIMITIVES)) throw new Error("VM undefined: " + name); + var fn = PRIMITIVES[name]; + return fn.apply(null, Array.isArray(args) ? args : []); + }; PRIMITIVES["primitive?"] = function(name) { return name in PRIMITIVES; }; @@ -2492,11 +2498,11 @@ PRIMITIVES["serialize"] = serialize; // render-to-html var renderToHtml = function(expr, env) { setRenderActiveB(true); -return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), ""); if (_m == "thunk") return renderToHtml(thunkExpr(expr), thunkEnv(expr)); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); }; +return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); if (_m == "spread") return (scopeEmit("element-attrs", spreadAttrs(expr)), ""); if (_m == "thunk") return renderToHtml(thunkExpr(expr), thunkEnv(expr)); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); }; PRIMITIVES["render-to-html"] = renderToHtml; // render-value-to-html - var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(val)), ""); if (_m == "thunk") return renderToHtml(thunkExpr(val), thunkEnv(val)); return escapeHtml((String(val))); })(); }; + var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "spread") return (scopeEmit("element-attrs", spreadAttrs(val)), ""); if (_m == "thunk") return renderToHtml(thunkExpr(val), thunkEnv(val)); return escapeHtml((String(val))); })(); }; PRIMITIVES["render-value-to-html"] = renderValueToHtml; // RENDER_HTML_FORMS @@ -2624,7 +2630,7 @@ PRIMITIVES["render-html-component"] = renderHtmlComponent; var isVoid = contains(VOID_ELEMENTS, tag); return (isSxTruthy(isVoid) ? (String("<") + String(tag) + String(renderAttrs(attrs)) + String(" />")) : (scopePush("element-attrs", NIL), (function() { var content = join("", map(function(c) { return renderToHtml(c, env); }, children)); - { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(attrs, spreadDict); } } + { var _c = scopeEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(attrs, spreadDict); } } scopePop("element-attrs"); return (String("<") + String(tag) + String(renderAttrs(attrs)) + String(">") + String(content) + String("")); })())); @@ -2650,7 +2656,7 @@ PRIMITIVES["render-html-element"] = renderHtmlElement; scopePush("element-attrs", NIL); return (function() { var content = join("", map(function(c) { return renderToHtml(c, env); }, children)); - { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(lakeAttrs, spreadDict); } } + { var _c = scopeEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(lakeAttrs, spreadDict); } } scopePop("element-attrs"); return (String("<") + String(lakeTag) + String(renderAttrs(lakeAttrs)) + String(">") + String(content) + String("")); })(); @@ -2677,7 +2683,7 @@ PRIMITIVES["render-html-lake"] = renderHtmlLake; scopePush("element-attrs", NIL); return (function() { var content = join("", map(function(c) { return renderToHtml(c, env); }, children)); - { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(marshAttrs, spreadDict); } } + { var _c = scopeEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(marshAttrs, spreadDict); } } scopePop("element-attrs"); return (String("<") + String(marshTag) + String(renderAttrs(marshAttrs)) + String(">") + String(content) + String("")); })(); diff --git a/web/adapter-html.sx b/web/adapter-html.sx index 88d380e..bd96bcc 100644 --- a/web/adapter-html.sx +++ b/web/adapter-html.sx @@ -31,7 +31,7 @@ ;; Raw HTML passthrough "raw-html" (raw-html-content expr) ;; Spread — emit attrs to nearest element provider - "spread" (do (emit! "element-attrs" (spread-attrs expr)) "") + "spread" (do (scope-emit! "element-attrs" (spread-attrs expr)) "") ;; Thunk — unwrap and render the inner expression (from letrec TCO) "thunk" (render-to-html (thunk-expr expr) (thunk-env expr)) ;; Everything else — evaluate first @@ -46,7 +46,7 @@ "boolean" (if val "true" "false") "list" (render-list-to-html val env) "raw-html" (raw-html-content val) - "spread" (do (emit! "element-attrs" (spread-attrs val)) "") + "spread" (do (scope-emit! "element-attrs" (spread-attrs val)) "") "thunk" (render-to-html (thunk-expr val) (thunk-env val)) :else (escape-html (str val))))) @@ -367,7 +367,7 @@ (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs attrs spread-dict)) - (emitted "element-attrs")) + (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" tag (render-attrs attrs) ">" content @@ -412,7 +412,7 @@ (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs lake-attrs spread-dict)) - (emitted "element-attrs")) + (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" lake-tag (render-attrs lake-attrs) ">" content @@ -460,7 +460,7 @@ (let ((content (join "" (map (fn (c) (render-to-html c env)) children)))) (for-each (fn (spread-dict) (merge-spread-attrs marsh-attrs spread-dict)) - (emitted "element-attrs")) + (scope-emitted "element-attrs")) (scope-pop! "element-attrs") (str "<" marsh-tag (render-attrs marsh-attrs) ">" content