Fix WASM browser click handlers: 8 bugs, 50 new VM tests

The sx-get links were doing full page refreshes because click handlers
never attached. Root causes: VM frame management bug, missing primitives,
CEK/VM type dispatch mismatch, and silent error swallowing.

Fixes:
- VM frame exhaustion: frames <- [] now properly pops to rest_frames
- length primitive: add alias for len in OCaml primitives
- call_sx_fn: use sx_call directly instead of eval_expr (CEK checks
  for type "lambda" but VmClosure reports "function")
- Boot error surfacing: Sx.init() now has try/catch + failure summary
- Callback error surfacing: catch-all handler for non-Eval_error exceptions
- Silent JIT failures: log before CEK fallback instead of swallowing
- vm→env sync: loadModule now calls sync_vm_to_env()
- sx_build_bytecode MCP tool added for bytecode compilation

Tests: 50 new tests across test-vm.sx and test-vm-primitives.sx covering
nested VM calls, frame integrity, CEK bridge, primitive availability,
cross-module symbol resolution, and callback dispatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 00:37:21 +00:00
parent 00de248ee9
commit c923a34fa8
38 changed files with 6016 additions and 4513 deletions

View File

@@ -519,6 +519,21 @@ let rec handle_tool name args =
| Unix.WEXITED 0 -> text_result (Printf.sprintf "OK — %s build succeeded\n%s" target (String.trim output))
| _ -> error_result (Printf.sprintf "%s build failed:\n%s" target output))
| "sx_build_bytecode" ->
let project_dir = try Sys.getenv "SX_PROJECT_DIR" with Not_found ->
let spec_dir = try Sys.getenv "SX_SPEC_DIR" with Not_found -> "spec" in
Filename.dirname spec_dir
in
let cmd = Printf.sprintf "cd %s && node hosts/ocaml/browser/compile-modules.js shared/static/wasm 2>&1" project_dir in
let ic = Unix.open_process_in cmd in
let lines = ref [] in
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
let status = Unix.close_process_in ic in
let output = String.concat "\n" (List.rev !lines) in
(match status with
| Unix.WEXITED 0 -> text_result (Printf.sprintf "OK — bytecode compilation succeeded\n%s" (String.trim output))
| _ -> error_result (Printf.sprintf "Bytecode compilation failed:\n%s" output))
| "sx_test" ->
let host = args |> member "host" |> to_string_option |> Option.value ~default:"js" in
let full = args |> member "full" |> to_bool_option |> Option.value ~default:false in
@@ -748,8 +763,10 @@ let rec handle_tool name args =
(match actions with Some a -> Some ("actions", `String a) | None -> None);
(match island with Some i -> Some ("island", `String i) | None -> None);
]) in
let args_json = Yojson.Safe.to_string (Yojson.Safe.from_string (Yojson.Basic.to_string inspector_args)) in
let cmd = Printf.sprintf "cd %s && node tests/playwright/sx-inspect.js '%s' 2>&1" project_dir (String.escaped args_json) in
let args_json = Yojson.Basic.to_string inspector_args in
(* Single-quote shell wrapping — escape any literal single quotes in JSON *)
let shell_safe = String.concat "'\\''" (String.split_on_char '\'' args_json) in
let cmd = Printf.sprintf "cd %s && node tests/playwright/sx-inspect.js '%s' 2>&1" project_dir shell_safe in
let ic = Unix.open_process_in cmd in
let lines = ref [] in
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
@@ -1304,6 +1321,8 @@ let tool_definitions = `List [
[("target", `Assoc [("type", `String "string"); ("description", `String "Build target: \"js\" (default) or \"ocaml\"")]);
("full", `Assoc [("type", `String "boolean"); ("description", `String "Include extensions and type system (default: false)")])]
[];
tool "sx_build_bytecode" "Compile all web .sx files to pre-compiled .sxbc.json bytecode modules for the WASM browser kernel."
[] [];
tool "sx_test" "Run SX test suite. Returns pass/fail summary and any failures."
[("host", `Assoc [("type", `String "string"); ("description", `String "Test host: \"js\" (default) or \"ocaml\"")]);
("full", `Assoc [("type", `String "boolean"); ("description", `String "Run full test suite including extensions (default: false)")])]
@@ -1354,13 +1373,13 @@ let tool_definitions = `List [
("files", `Assoc [("type", `String "array"); ("items", `Assoc [("type", `String "string")]); ("description", `String "Multiple .sx files to load in order")]);
("setup", `Assoc [("type", `String "string"); ("description", `String "SX setup expression to run before main evaluation")])]
["expr"];
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page/island report with leak detection and handler audit), diff (full SSR vs hydrated DOM), hydrate (lake-focused SSR vs hydrated comparison — detects clobbering), eval (JS expression), interact (action sequence), screenshot."
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page/island report with leak detection and handler audit), diff (full SSR vs hydrated DOM), hydrate (lake-focused SSR vs hydrated comparison — detects clobbering), eval (JS expression), interact (action sequence), screenshot, listeners (CDP event listener inspection), trace (click + capture console/network/pushState), cdp (raw CDP command)."
[("spec", `Assoc [("type", `String "string"); ("description", `String "Spec file to run (run mode). e.g. stepper.spec.js")]);
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, hydrate, eval, interact, screenshot")]);
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, hydrate, eval, interact, screenshot, listeners, trace, cdp")]);
("url", `Assoc [("type", `String "string"); ("description", `String "URL path to navigate to (default: /)")]);
("island", `Assoc [("type", `String "string"); ("description", `String "Filter inspect to a specific island by name (e.g. home/stepper)")]);
("selector", `Assoc [("type", `String "string"); ("description", `String "CSS selector to focus on (screenshot mode)")]);
("expr", `Assoc [("type", `String "string"); ("description", `String "JS expression to evaluate (eval mode)")]);
("selector", `Assoc [("type", `String "string"); ("description", `String "CSS selector for screenshot/listeners/trace modes")]);
("expr", `Assoc [("type", `String "string"); ("description", `String "JS expression (eval mode), selector (listeners/trace), or CDP command (cdp mode)")]);
("actions", `Assoc [("type", `String "string"); ("description", `String "Semicolon-separated action sequence (interact mode). Actions: click:sel, fill:sel:val, wait:ms, text:sel, html:sel, attrs:sel, screenshot, screenshot:sel, count:sel, visible:sel")])]
[];
]

View File

@@ -58,7 +58,8 @@ let global_env = make_env ()
let _sx_render_mode = ref false
let call_sx_fn (fn : value) (args : value list) : value =
Sx_ref.eval_expr (List (fn :: args)) (Env global_env)
let result = Sx_runtime.sx_call fn args in
!Sx_primitives._sx_trampoline_fn result
(* ================================================================== *)
(* Value conversion: OCaml <-> JS *)
@@ -107,10 +108,18 @@ let rec value_to_js (v : value) : Js.Unsafe.any =
let args = match arg with Nil -> [] | _ -> [arg] in
let result = call_sx_fn v args in
value_to_js result
with Eval_error msg ->
with
| Eval_error msg ->
let fn_info = Printf.sprintf " [callback %s handle=%d]" (type_of v) handle in
ignore (Js.Unsafe.meth_call
(Js.Unsafe.get Js.Unsafe.global (Js.string "console"))
"error" [| Js.Unsafe.inject (Js.string ("[sx] " ^ msg)) |]);
"error" [| Js.Unsafe.inject (Js.string ("[sx] " ^ msg ^ fn_info)) |]);
Js.Unsafe.inject Js.null
| exn ->
let fn_info = Printf.sprintf " [callback %s handle=%d]" (type_of v) handle in
ignore (Js.Unsafe.meth_call
(Js.Unsafe.get Js.Unsafe.global (Js.string "console"))
"error" [| Js.Unsafe.inject (Js.string ("[sx] UNCAUGHT: " ^ Printexc.to_string exn ^ fn_info)) |]);
Js.Unsafe.inject Js.null) in
Js.Unsafe.fun_call _tag_fn [|
Js.Unsafe.inject inner;
@@ -189,6 +198,31 @@ and js_to_value (js : Js.Unsafe.any) : value =
let return_via_side_channel (v : Js.Unsafe.any) : Js.Unsafe.any =
Js.Unsafe.set Js.Unsafe.global (Js.string "__sxR") v; v
(* ================================================================== *)
(* Persistent VM globals — synced with global_env *)
(* ================================================================== *)
(* String-keyed mirror of global_env.bindings for VmClosures.
VmClosures from bytecode modules hold vm_env_ref pointing here.
Must stay in sync so VmClosures see post-boot definitions. *)
let _vm_globals : (string, value) Hashtbl.t = Hashtbl.create 512
let _in_batch = ref false
(* Sync env→VM: copy all bindings from global_env.bindings to _vm_globals.
Called after CEK eval/load so VmClosures can see new definitions. *)
let sync_env_to_vm () =
Hashtbl.iter (fun id v ->
Hashtbl.replace _vm_globals (unintern id) v
) global_env.bindings
(* Hook: intercept env_bind on global_env to also update _vm_globals.
This ensures VmClosures see new definitions immediately, even during
a single boot-init call that loads page scripts and components. *)
let () =
Sx_types._env_bind_hook := Some (fun env name v ->
if env == global_env then
Hashtbl.replace _vm_globals name v)
(* ================================================================== *)
(* Core API *)
(* ================================================================== *)
@@ -207,6 +241,7 @@ let api_eval src_js =
let exprs = Sx_parser.parse_all src in
let env = Env global_env in
let result = List.fold_left (fun _acc expr -> Sx_ref.eval_expr expr env) Nil exprs in
sync_env_to_vm ();
return_via_side_channel (value_to_js result)
with
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
@@ -215,7 +250,9 @@ let api_eval src_js =
let api_eval_expr expr_js _env_js =
let expr = js_to_value expr_js in
try
return_via_side_channel (value_to_js (Sx_ref.eval_expr expr (Env global_env)))
let result = Sx_ref.eval_expr expr (Env global_env) in
sync_env_to_vm ();
return_via_side_channel (value_to_js result)
with Eval_error msg ->
Js.Unsafe.inject (Js.string ("Error: " ^ msg))
@@ -226,56 +263,66 @@ let api_load src_js =
let env = Env global_env in
let count = ref 0 in
List.iter (fun expr -> ignore (Sx_ref.eval_expr expr env); incr count) exprs;
sync_env_to_vm ();
Js.Unsafe.inject !count
with
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| Parse_error msg -> Js.Unsafe.inject (Js.string ("Parse error: " ^ msg))
(* Shared globals table for batch module loading.
Created by beginModuleLoad, accumulated across loadModule calls,
flushed to env by endModuleLoad. Ensures closures from early
modules can see definitions from later modules. *)
let _module_globals : (string, value) Hashtbl.t option ref = ref None
let api_begin_module_load () =
let g = Hashtbl.create 512 in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) global_env.bindings;
_module_globals := Some g;
(* Snapshot current env into the persistent VM globals table *)
Hashtbl.clear _vm_globals;
Hashtbl.iter (fun id v -> Hashtbl.replace _vm_globals (unintern id) v) global_env.bindings;
_in_batch := true;
Js.Unsafe.inject true
let api_end_module_load () =
(match !_module_globals with
| Some g ->
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) g;
_module_globals := None
| None -> ());
if !_in_batch then begin
(* Copy VM globals back to env (bytecode modules defined new symbols) *)
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) _vm_globals;
_in_batch := false
end;
Js.Unsafe.inject true
let sync_vm_to_env () =
Hashtbl.iter (fun name v ->
let id = intern name in
if not (Hashtbl.mem global_env.bindings id) then
Hashtbl.replace global_env.bindings id v
else begin
(* Update existing binding if the VM has a newer value *)
let existing = Hashtbl.find global_env.bindings id in
match existing, v with
| VmClosure _, VmClosure _ -> Hashtbl.replace global_env.bindings id v
| _, VmClosure _ -> Hashtbl.replace global_env.bindings id v
| _ -> ()
end
) _vm_globals
let api_load_module module_js =
try
let code_val = js_to_value module_js in
let code = Sx_vm.code_from_value code_val in
let globals = match !_module_globals with
| Some g -> g (* use shared table *)
| None ->
(* standalone mode: create temp table *)
let g = Hashtbl.create 256 in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) global_env.bindings;
g
in
let _result = Sx_vm.execute_module code globals in
(* If standalone (no batch), copy back immediately *)
if !_module_globals = None then
Hashtbl.iter (fun k v ->
Hashtbl.replace global_env.bindings (intern k) v
) globals;
Js.Unsafe.inject (Hashtbl.length globals)
let _result = Sx_vm.execute_module code _vm_globals in
sync_vm_to_env ();
Js.Unsafe.inject (Hashtbl.length _vm_globals)
with
| Eval_error msg -> Js.Unsafe.inject (Js.string ("Error: " ^ msg))
| exn -> Js.Unsafe.inject (Js.string ("Error: " ^ Printexc.to_string exn))
let api_debug_env name_js =
let name = Js.to_string name_js in
let id = intern name in
let found_env = Hashtbl.find_opt global_env.bindings id in
let found_vm = Hashtbl.find_opt _vm_globals name in
let total_env = Hashtbl.length global_env.bindings in
let total_vm = Hashtbl.length _vm_globals in
let env_s = match found_env with Some v -> "env:" ^ type_of v | None -> "env:MISSING" in
let vm_s = match found_vm with Some v -> "vm:" ^ type_of v | None -> "vm:MISSING" in
Js.Unsafe.inject (Js.string (Printf.sprintf "%s %s (env=%d vm=%d)" env_s vm_s total_env total_vm))
let api_compile_module src_js =
let src = Js.to_string src_js in
try
@@ -318,7 +365,9 @@ let api_register_native name_js callback_js =
let js_args = args |> List.map value_to_js |> Array.of_list in
js_to_value (Js.Unsafe.fun_call callback_js [| Js.Unsafe.inject (Js.array js_args) |])
in
ignore (env_bind global_env name (NativeFn (name, native_fn)));
let v = NativeFn (name, native_fn) in
ignore (env_bind global_env name v);
Hashtbl.replace _vm_globals name v;
Js.Unsafe.inject Js.null
let api_call_fn fn_js args_js =
@@ -627,18 +676,12 @@ let _jit_compiling = ref false
let _jit_enabled = ref false
let () =
(* Convert int-keyed env.bindings to string-keyed Hashtbl for VM globals *)
let env_to_vm_globals env =
let g = Hashtbl.create (Hashtbl.length env.bindings) in
Hashtbl.iter (fun id v -> Hashtbl.replace g (unintern id) v) env.bindings;
g
in
Sx_ref.jit_call_hook := Some (fun f args ->
match f with
| Lambda l when !_jit_enabled ->
(match l.l_compiled with
| Some cl when not (Sx_vm.is_jit_failed cl) ->
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
(try Some (Sx_vm.call_closure cl args _vm_globals)
with Eval_error msg ->
let fn_name = match l.l_name with Some n -> n | None -> "?" in
Printf.eprintf "[jit] DISABLED %s — %s\n%!" fn_name msg;
@@ -649,13 +692,12 @@ let () =
if !_jit_compiling then None
else begin
_jit_compiling := true;
let globals = env_to_vm_globals global_env in
let compiled = Sx_vm.jit_compile_lambda l globals in
let compiled = Sx_vm.jit_compile_lambda l _vm_globals in
_jit_compiling := false;
(match compiled with
| Some cl ->
l.l_compiled <- Some cl;
(try Some (Sx_vm.call_closure cl args cl.vm_env_ref)
(try Some (Sx_vm.call_closure cl args _vm_globals)
with _ ->
l.l_compiled <- Some Sx_vm.jit_failed_sentinel;
None)
@@ -693,5 +735,6 @@ let () =
Js.Unsafe.set sx (Js.string "callFn") (wrap api_call_fn);
Js.Unsafe.set sx (Js.string "isCallable") (Js.wrap_callback api_is_callable);
Js.Unsafe.set sx (Js.string "fnArity") (Js.wrap_callback api_fn_arity);
Js.Unsafe.set sx (Js.string "debugEnv") (Js.wrap_callback api_debug_env);
Js.Unsafe.set Js.Unsafe.global (Js.string "SxKernel") sx

View File

@@ -362,6 +362,7 @@ let () =
| [Macro _] | [Thunk _] | [Keyword _] | [Symbol _] -> Number 0.0
| _ -> raise (Eval_error (Printf.sprintf "len: %d args"
(List.length args))));
register "length" (Hashtbl.find primitives "len");
register "first" (fun args ->
match args with
| [List (x :: _)] | [ListRef { contents = x :: _ }] -> x

View File

@@ -148,7 +148,11 @@ and vm_call vm f args =
(* Cached bytecode — run on VM using the closure's captured env,
not the caller's globals. Closure vars were merged at compile time. *)
(try push vm (call_closure cl args cl.vm_env_ref)
with _ -> push vm (Sx_ref.cek_call f (List args)))
with e ->
let msg = match e with Eval_error m -> m | e -> Printexc.to_string e in
Printf.eprintf "[vm] JIT call failed for %s: %s — falling back to CEK\n%!"
(match l.l_name with Some n -> n | None -> "<anon>") msg;
push vm (Sx_ref.cek_call f (List args)))
| Some _ ->
(* Compile failed — CEK *)
push vm (Sx_ref.cek_call f (List args))
@@ -161,7 +165,10 @@ and vm_call vm f args =
| Some cl ->
l.l_compiled <- Some cl;
(try push vm (call_closure cl args cl.vm_env_ref)
with _ ->
with e ->
let msg = match e with Eval_error m -> m | e -> Printexc.to_string e in
Printf.eprintf "[vm] JIT first-call failed for %s: %s — marking failed, falling back to CEK\n%!"
(match l.l_name with Some n -> n | None -> "<anon>") msg;
l.l_compiled <- Some jit_failed_sentinel;
push vm (Sx_ref.cek_call f (List args)))
| None ->
@@ -187,8 +194,17 @@ and run vm =
| frame :: rest_frames ->
let bc = frame.closure.vm_code.vc_bytecode in
let consts = frame.closure.vm_code.vc_constants in
if frame.ip >= Array.length bc then
vm.frames <- [] (* bytecode exhausted — stop *)
if frame.ip >= Array.length bc then begin
(* Bytecode exhausted without explicit RETURN — pop frame like RETURN *)
let fn_name = match frame.closure.vm_name with Some n -> n | None -> "?" in
Printf.eprintf "[vm] WARN: bytecode exhausted without RETURN in %s (base=%d sp=%d frames=%d)\n%!"
fn_name frame.base vm.sp (List.length rest_frames);
let result = if vm.sp > frame.base then pop vm else Nil in
vm.frames <- rest_frames;
vm.sp <- frame.base;
if rest_frames <> [] then push vm result
(* If no more frames, result stays on stack for call_closure to pop *)
end
else begin
let saved_ip = frame.ip in
let op = bc.(frame.ip) in
@@ -612,4 +628,5 @@ let jit_compile_lambda (l : lambda) globals =
(* Wire up forward references *)
let () = jit_compile_ref := jit_compile_lambda
let () = _vm_call_closure_ref := (fun cl args -> call_closure cl args cl.vm_env_ref)
let () = _vm_call_closure_ref := (fun cl args ->
call_closure cl args cl.vm_env_ref)

View File

@@ -0,0 +1,172 @@
(define
vm-eval
(fn
(expr)
(let
((code (compile expr)))
(vm-execute-module (code-from-value code) {}))))
(define
vm-eval-with
(fn
(expr globals)
(let
((code (compile expr)))
(vm-execute-module (code-from-value code) globals))))
(define
try-eval
(fn
(expr)
(try-catch (fn () (vm-eval expr)) (fn (err) (str "Error: " err)))))
(defsuite
"vm-primitive-availability"
(deftest
"length is available"
(assert-equal 3 (vm-eval (quote (length (list 1 2 3))))))
(deftest
"len is available"
(assert-equal 3 (vm-eval (quote (len (list 1 2 3))))))
(deftest
"first is available"
(assert-equal 1 (vm-eval (quote (first (list 1 2 3))))))
(deftest
"rest is available"
(assert-equal (list 2 3) (vm-eval (quote (rest (list 1 2 3))))))
(deftest
"nth is available"
(assert-equal 2 (vm-eval (quote (nth (list 1 2 3) 1)))))
(deftest
"get is available"
(assert-equal "v" (vm-eval (quote (get {:k "v"} :k)))))
(deftest
"type-of is available"
(assert-equal "number" (vm-eval (quote (type-of 42)))))
(deftest
"str concatenation works"
(assert-equal "ab" (vm-eval (quote (str "a" "b")))))
(deftest
"empty? is available"
(assert-equal true (vm-eval (quote (empty? (list))))))
(deftest
"not is available"
(assert-equal true (vm-eval (quote (not false)))))
(deftest
"append is available"
(assert-equal
(list 1 2 3)
(vm-eval (quote (append (list 1 2) (list 3))))))
(deftest
"assoc is available"
(assert-equal "b" (vm-eval (quote (get (assoc {:a "a"} :a "b") :a)))))
(deftest
"keys is available"
(assert-equal 1 (vm-eval (quote (len (keys {:a 1}))))))
(deftest
"contains? is available"
(assert-equal true (vm-eval (quote (contains? "hello" "ell")))))
(deftest
"starts-with? is available"
(assert-equal true (vm-eval (quote (starts-with? "hello" "hel")))))
(deftest
"split is available"
(assert-equal 3 (vm-eval (quote (len (split "a,b,c" ","))))))
(deftest
"join is available"
(assert-equal "a,b" (vm-eval (quote (join "," (list "a" "b"))))))
(deftest
"trim is available"
(assert-equal "x" (vm-eval (quote (trim " x ")))))
(deftest
"upper is available"
(assert-equal "ABC" (vm-eval (quote (upper "abc"))))))
(defsuite
"vm-cross-module-symbols"
(deftest
"module B sees module A definition"
(let
((g (dict)))
(vm-eval-with (quote (define helper (fn (x) (+ x 100)))) g)
(vm-eval-with (quote (define user (fn (x) (helper x)))) g)
(assert-equal 142 (vm-eval-with (quote (user 42)) g))))
(deftest
"module C sees definitions from both A and B"
(let
((g (dict)))
(vm-eval-with (quote (define a (fn (x) (* x 2)))) g)
(vm-eval-with (quote (define b (fn (x) (+ (a x) 1)))) g)
(vm-eval-with (quote (define c (fn (x) (b (a x))))) g)
(assert-equal 21 (vm-eval-with (quote (c 5)) g))))
(deftest
"late-defined symbol visible to earlier closure"
(let
((g (dict)))
(vm-eval-with (quote (define caller (fn () (callee 10)))) g)
(vm-eval-with (quote (define callee (fn (x) (* x 3)))) g)
(assert-equal 30 (vm-eval-with (quote (caller)) g)))))
(defsuite
"vm-callback-dispatch"
(deftest
"simple closure callback"
(let
((g (dict)))
(vm-eval-with
(quote (define make-fn (fn (n) (fn (x) (+ n x)))))
g)
(let
((result (vm-eval-with (quote (let ((f (make-fn 10))) (f 5))) g)))
(assert-equal 15 result))))
(deftest
"closure capturing multiple vars"
(let
((g (dict)))
(vm-eval-with
(quote (define make-adder (fn (a b) (fn (x) (+ a b x)))))
g)
(assert-equal 60 (vm-eval-with (quote ((make-adder 10 20) 30)) g))))
(deftest
"nested closure callback"
(let
((g (dict)))
(vm-eval-with
(quote (define wrap (fn (f) (fn (x) (f (f x))))))
g)
(vm-eval-with (quote (define inc (fn (x) (+ x 1)))) g)
(assert-equal 12 (vm-eval-with (quote ((wrap inc) 10)) g)))))
(defsuite
"vm-error-surfacing"
(deftest
"undefined symbol in compiled fn raises error"
(let
((g (dict)) (ok false))
(vm-eval-with (quote (define good-fn (fn () 42))) g)
(set! ok (= 42 (vm-eval-with (quote (good-fn)) g)))
(assert-equal true ok)))
(deftest
"compiled fn calling another compiled fn works"
(let
((g (dict)))
(vm-eval-with (quote (define a (fn (x) (+ x 1)))) g)
(vm-eval-with (quote (define b (fn (x) (a x)))) g)
(assert-equal 6 (vm-eval-with (quote (b 5)) g))))
(deftest
"when block executes all body forms"
(assert-equal
30
(vm-eval
(quote
(let ((x 0)) (when true (set! x 10) (set! x (+ x 20))) x)))))
(deftest
"when block: function call then side effect"
(assert-equal
50
(vm-eval
(quote
(let
((x 0) (f (fn (n) (* n 10))))
(when true (f 1) (set! x (f 5)))
x))))))

File diff suppressed because it is too large Load Diff

View File

@@ -257,6 +257,7 @@
console.log("[sx-platform] ok " + path + " (bytecode)");
return true;
} catch(e) {
console.error("[sx-platform] bytecode EXCEPTION " + path + ":", e);
return null;
}
}
@@ -329,18 +330,23 @@
];
var loaded = 0, bcCount = 0, srcCount = 0;
if (K.beginModuleLoad) K.beginModuleLoad();
var t0 = performance.now();
var forceSrc = (typeof window !== "undefined" && window.location.search.indexOf("nosxbc") !== -1);
if (!forceSrc && K.beginModuleLoad) K.beginModuleLoad();
for (var i = 0; i < files.length; i++) {
var r = loadBytecodeFile(files[i]);
if (r) { bcCount++; continue; }
if (!forceSrc) {
var r = loadBytecodeFile(files[i]);
if (r) { bcCount++; continue; }
}
// Bytecode not available — end batch, load source, restart batch
if (K.endModuleLoad) K.endModuleLoad();
if (!forceSrc && K.endModuleLoad) K.endModuleLoad();
r = loadSxFile(files[i]);
if (typeof r === "number") { loaded += r; srcCount++; }
if (K.beginModuleLoad) K.beginModuleLoad();
if (!forceSrc && K.beginModuleLoad) K.beginModuleLoad();
}
if (K.endModuleLoad) K.endModuleLoad();
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs)");
if (!forceSrc && K.endModuleLoad) K.endModuleLoad();
var elapsed = Math.round(performance.now() - t0);
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs) in " + elapsed + "ms");
return loaded;
}
@@ -358,46 +364,33 @@
engine: function() { return K.engine(); },
// Boot entry point (called by auto-init or manually)
init: function() {
if (typeof K.eval === "function") {
// Check boot-init exists
// Step through boot manually
console.log("[sx] init-css-tracking...");
K.eval("(init-css-tracking)");
console.log("[sx] process-page-scripts...");
K.eval("(process-page-scripts)");
console.log("[sx] routes after pages:", K.eval("(len _page-routes)"));
console.log("[sx] process-sx-scripts...");
K.eval("(process-sx-scripts nil)");
console.log("[sx] sx-hydrate-elements...");
K.eval("(sx-hydrate-elements nil)");
console.log("[sx] sx-hydrate-islands...");
K.eval("(sx-hydrate-islands nil)");
console.log("[sx] process-elements...");
K.eval("(process-elements nil)");
// Debug islands
console.log("[sx] ~home/stepper defined?", K.eval("(type-of ~home/stepper)"));
console.log("[sx] ~layouts/header defined?", K.eval("(type-of ~layouts/header)"));
// Try manual island query
console.log("[sx] manual island query:", K.eval("(len (dom-query-all (dom-body) \"[data-sx-island]\"))"));
// Try hydrating again
console.log("[sx] retry hydrate-islands...");
K.eval("(sx-hydrate-islands nil)");
// Check if links are boosted
var links = document.querySelectorAll("a[href]");
var boosted = 0;
for (var i = 0; i < links.length; i++) {
if (links[i]._sxBoundboost) boosted++;
if (typeof K.eval !== "function") return;
var steps = [
'(log-info (str "sx-browser " SX_VERSION))',
'(init-css-tracking)',
'(process-page-scripts)',
'(process-sx-scripts nil)',
'(sx-hydrate-elements nil)',
'(sx-hydrate-islands nil)',
'(run-post-render-hooks)',
'(process-elements nil)',
'(dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0)))'
];
var failures = [];
for (var i = 0; i < steps.length; i++) {
try {
var r = K.eval(steps[i]);
if (typeof r === "string" && r.indexOf("Error") === 0) {
console.error("[sx] boot step " + i + " FAILED: " + steps[i].substring(0, 60), r);
failures.push({ step: i, expr: steps[i], error: r });
}
} catch(e) {
console.error("[sx] boot step " + i + " THREW: " + steps[i].substring(0, 60), e);
failures.push({ step: i, expr: steps[i], error: String(e) });
}
console.log("[sx] boosted links:", boosted, "/", links.length);
// Check island state
var islands = document.querySelectorAll("[data-sx-island]");
console.log("[sx] islands:", islands.length);
for (var j = 0; j < islands.length; j++) {
console.log("[sx] island:", islands[j].getAttribute("data-sx-island"),
"hydrated:", !!islands[j]._sxBoundislandhydrated || !!islands[j]["_sxBound" + "island-hydrated"],
"children:", islands[j].children.length);
}
console.log("[sx] boot done");
}
if (failures.length > 0) {
console.error("[sx] BOOT FAILED: " + failures.length + " step(s) errored:", failures.map(function(f) { return "step " + f.step + ": " + f.error; }).join("; "));
}
}
};
@@ -410,8 +403,8 @@
var _doInit = function() {
loadWebStack();
Sx.init();
// Enable JIT after all boot code has run
setTimeout(function() { K.eval('(enable-jit!)'); }, 0);
// JIT disabled for debugging
// setTimeout(function() { K.eval('(enable-jit!)'); }, 0);
};
if (document.readyState === "loading") {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,45 +1,434 @@
(define HEAD_HOIST_SELECTOR "meta, title, link[rel='canonical'], script[type='application/ld+json']")
(define
HEAD_HOIST_SELECTOR
"meta, title, link[rel='canonical'], script[type='application/ld+json']")
(define hoist-head-elements-full :effects (mutation io) (fn (root) (let ((els (dom-query-all root HEAD_HOIST_SELECTOR))) (for-each (fn (el) (let ((tag (lower (dom-tag-name el)))) (cond (= tag "title") (do (set-document-title (dom-text-content el)) (dom-remove-child (dom-parent el) el)) (= tag "meta") (do (let ((name (dom-get-attr el "name")) (prop (dom-get-attr el "property"))) (when name (remove-head-element (str "meta[name=\"" name "\"]"))) (when prop (remove-head-element (str "meta[property=\"" prop "\"]")))) (dom-remove-child (dom-parent el) el) (dom-append-to-head el)) (and (= tag "link") (= (dom-get-attr el "rel") "canonical")) (do (remove-head-element "link[rel=\"canonical\"]") (dom-remove-child (dom-parent el) el) (dom-append-to-head el)) :else (do (dom-remove-child (dom-parent el) el) (dom-append-to-head el))))) els))))
(define
hoist-head-elements-full
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all root HEAD_HOIST_SELECTOR)))
(for-each
(fn
(el)
(let
((tag (lower (dom-tag-name el))))
(cond
(= tag "title")
(do
(set-document-title (dom-text-content el))
(dom-remove-child (dom-parent el) el))
(= tag "meta")
(do
(let
((name (dom-get-attr el "name"))
(prop (dom-get-attr el "property")))
(when
name
(remove-head-element (str "meta[name=\"" name "\"]")))
(when
prop
(remove-head-element (str "meta[property=\"" prop "\"]"))))
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el))
(and (= tag "link") (= (dom-get-attr el "rel") "canonical"))
(do
(remove-head-element "link[rel=\"canonical\"]")
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el))
:else (do
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el)))))
els))))
(define sx-mount :effects (mutation io) (fn (target (source :as string) (extra-env :as dict)) (let ((el (resolve-mount-target target))) (when el (when (empty? (dom-child-list el)) (let ((node (sx-render-with-env source extra-env))) (dom-set-text-content el "") (dom-append el node) (hoist-head-elements-full el))) (process-elements el) (sx-hydrate-elements el) (sx-hydrate-islands el) (run-post-render-hooks)))))
(define
sx-mount
:effects (mutation io)
(fn
(target (source :as string) (extra-env :as dict))
(let
((el (resolve-mount-target target)))
(when
el
(when
(empty? (dom-child-list el))
(let
((node (sx-render-with-env source extra-env)))
(dom-set-text-content el "")
(dom-append el node)
(hoist-head-elements-full el)))
(process-elements el)
(sx-hydrate-elements el)
(sx-hydrate-islands el)
(run-post-render-hooks)))))
(define resolve-suspense :effects (mutation io) (fn ((id :as string) (sx :as string)) (process-sx-scripts nil) (let ((el (dom-query (str "[data-suspense=\"" id "\"]")))) (if el (do (let ((exprs (parse sx)) (env (get-render-env nil))) (dom-set-text-content el "") (for-each (fn (expr) (dom-append el (render-to-dom expr env nil))) exprs) (process-elements el) (sx-hydrate-elements el) (sx-hydrate-islands el) (run-post-render-hooks) (dom-dispatch el "sx:resolved" {:id id}))) (log-warn (str "resolveSuspense: no element for id=" id))))))
(define
resolve-suspense
:effects (mutation io)
(fn
((id :as string) (sx :as string))
(process-sx-scripts nil)
(let
((el (dom-query (str "[data-suspense=\"" id "\"]"))))
(if
el
(do
(let
((exprs (parse sx)) (env (get-render-env nil)))
(dom-set-text-content el "")
(for-each
(fn (expr) (dom-append el (render-to-dom expr env nil)))
exprs)
(process-elements el)
(sx-hydrate-elements el)
(sx-hydrate-islands el)
(run-post-render-hooks)
(dom-dispatch el "sx:resolved" {:id id})))
(log-warn (str "resolveSuspense: no element for id=" id))))))
(define sx-hydrate-elements :effects (mutation io) (fn (root) (let ((els (dom-query-all (or root (dom-body)) "[data-sx]"))) (for-each (fn (el) (when (not (is-processed? el "hydrated")) (mark-processed! el "hydrated") (sx-update-element el nil))) els))))
(define
sx-hydrate-elements
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all (or root (dom-body)) "[data-sx]")))
(for-each
(fn
(el)
(when
(not (is-processed? el "hydrated"))
(mark-processed! el "hydrated")
(sx-update-element el nil)))
els))))
(define sx-update-element :effects (mutation io) (fn (el new-env) (let ((target (resolve-mount-target el))) (when target (let ((source (dom-get-attr target "data-sx"))) (when source (let ((base-env (parse-env-attr target)) (env (merge-envs base-env new-env))) (let ((node (sx-render-with-env source env))) (dom-set-text-content target "") (dom-append target node) (when new-env (store-env-attr target base-env new-env))))))))))
(define
sx-update-element
:effects (mutation io)
(fn
(el new-env)
(let
((target (resolve-mount-target el)))
(when
target
(let
((source (dom-get-attr target "data-sx")))
(when
source
(let
((base-env (parse-env-attr target))
(env (merge-envs base-env new-env)))
(let
((node (sx-render-with-env source env)))
(dom-set-text-content target "")
(dom-append target node)
(when new-env (store-env-attr target base-env new-env))))))))))
(define sx-render-component :effects (mutation io) (fn ((name :as string) (kwargs :as dict) (extra-env :as dict)) (let ((full-name (if (starts-with? name "~") name (str "~" name)))) (let ((env (get-render-env extra-env)) (comp (env-get env full-name))) (if (not (component? comp)) (error (str "Unknown component: " full-name)) (let ((call-expr (list (make-symbol full-name)))) (for-each (fn ((k :as string)) (append! call-expr (make-keyword (to-kebab k))) (append! call-expr (dict-get kwargs k))) (keys kwargs)) (render-to-dom call-expr env nil)))))))
(define
sx-render-component
:effects (mutation io)
(fn
((name :as string) (kwargs :as dict) (extra-env :as dict))
(let
((full-name (if (starts-with? name "~") name (str "~" name))))
(let
((env (get-render-env extra-env)) (comp (env-get env full-name)))
(if
(not (component? comp))
(error (str "Unknown component: " full-name))
(let
((call-expr (list (make-symbol full-name))))
(for-each
(fn
((k :as string))
(append! call-expr (make-keyword (to-kebab k)))
(append! call-expr (dict-get kwargs k)))
(keys kwargs))
(render-to-dom call-expr env nil)))))))
(define process-sx-scripts :effects (mutation io) (fn (root) (let ((scripts (query-sx-scripts root))) (for-each (fn (s) (when (not (is-processed? s "script")) (mark-processed! s "script") (let ((text (dom-text-content s))) (cond (dom-has-attr? s "data-components") (process-component-script s text) (or (nil? text) (empty? (trim text))) nil (dom-has-attr? s "data-init") (let ((exprs (sx-parse text))) (for-each (fn (expr) (eval-expr expr (env-extend (dict)))) exprs)) (dom-has-attr? s "data-mount") (let ((mount-sel (dom-get-attr s "data-mount")) (target (dom-query mount-sel))) (when target (sx-mount target text nil))) :else (sx-load-components text))))) scripts))))
(define
process-sx-scripts
:effects (mutation io)
(fn
(root)
(let
((scripts (query-sx-scripts root)))
(for-each
(fn
(s)
(when
(not (is-processed? s "script"))
(mark-processed! s "script")
(let
((text (dom-text-content s)))
(cond
(dom-has-attr? s "data-components")
(process-component-script s text)
(or (nil? text) (empty? (trim text)))
nil
(dom-has-attr? s "data-init")
(let
((exprs (sx-parse text)))
(for-each (fn (expr) (cek-eval expr)) exprs))
(dom-has-attr? s "data-mount")
(let
((mount-sel (dom-get-attr s "data-mount"))
(target (dom-query mount-sel)))
(when target (sx-mount target text nil)))
:else (sx-load-components text)))))
scripts))))
(define process-component-script :effects (mutation io) (fn (script (text :as string)) (let ((hash (dom-get-attr script "data-hash"))) (if (nil? hash) (when (and text (not (empty? (trim text)))) (sx-load-components text)) (let ((has-inline (and text (not (empty? (trim text)))))) (let ((cached-hash (local-storage-get "sx-components-hash"))) (if (= cached-hash hash) (if has-inline (do (local-storage-set "sx-components-hash" hash) (local-storage-set "sx-components-src" text) (sx-load-components text) (log-info "components: downloaded (cookie stale)")) (let ((cached (local-storage-get "sx-components-src"))) (if cached (do (sx-load-components cached) (log-info (str "components: cached (" hash ")"))) (do (clear-sx-comp-cookie) (browser-reload))))) (if has-inline (do (local-storage-set "sx-components-hash" hash) (local-storage-set "sx-components-src" text) (sx-load-components text) (log-info (str "components: downloaded (" hash ")"))) (do (local-storage-remove "sx-components-hash") (local-storage-remove "sx-components-src") (clear-sx-comp-cookie) (browser-reload))))) (set-sx-comp-cookie hash))))))
(define
process-component-script
:effects (mutation io)
(fn
(script (text :as string))
(let
((hash (dom-get-attr script "data-hash")))
(if
(nil? hash)
(when
(and text (not (empty? (trim text))))
(sx-load-components text))
(let
((has-inline (and text (not (empty? (trim text))))))
(let
((cached-hash (local-storage-get "sx-components-hash")))
(if
(= cached-hash hash)
(if
has-inline
(do
(local-storage-set "sx-components-hash" hash)
(local-storage-set "sx-components-src" text)
(sx-load-components text)
(log-info "components: downloaded (cookie stale)"))
(let
((cached (local-storage-get "sx-components-src")))
(if
cached
(do
(sx-load-components cached)
(log-info (str "components: cached (" hash ")")))
(do (clear-sx-comp-cookie) (browser-reload)))))
(if
has-inline
(do
(local-storage-set "sx-components-hash" hash)
(local-storage-set "sx-components-src" text)
(sx-load-components text)
(log-info (str "components: downloaded (" hash ")")))
(do
(local-storage-remove "sx-components-hash")
(local-storage-remove "sx-components-src")
(clear-sx-comp-cookie)
(browser-reload)))))
(set-sx-comp-cookie hash))))))
(define _page-routes (list))
(define process-page-scripts :effects (mutation io) (fn () (let ((scripts (query-page-scripts))) (log-info (str "pages: found " (len scripts) " script tags")) (for-each (fn (s) (when (not (is-processed? s "pages")) (mark-processed! s "pages") (let ((text (dom-text-content s))) (log-info (str "pages: script text length=" (if text (len text) 0))) (if (and text (not (empty? (trim text)))) (let ((pages (parse text))) (log-info (str "pages: parsed " (len pages) " entries")) (for-each (fn ((page :as dict)) (append! _page-routes (merge page {:parsed (parse-route-pattern (get page "path"))}))) pages)) (log-warn "pages: script tag is empty"))))) scripts) (log-info (str "pages: " (len _page-routes) " routes loaded")))))
(define
process-page-scripts
:effects (mutation io)
(fn
()
(let
((scripts (query-page-scripts)))
(log-info (str "pages: found " (len scripts) " script tags"))
(for-each
(fn
(s)
(when
(not (is-processed? s "pages"))
(mark-processed! s "pages")
(let
((text (dom-text-content s)))
(log-info
(str "pages: script text length=" (if text (len text) 0)))
(if
(and text (not (empty? (trim text))))
(let
((pages (parse text)))
(log-info (str "pages: parsed " (len pages) " entries"))
(for-each
(fn
((page :as dict))
(append! _page-routes (merge page {:parsed (parse-route-pattern (get page "path"))})))
pages))
(log-warn "pages: script tag is empty")))))
scripts)
(log-info (str "pages: " (len _page-routes) " routes loaded")))))
(define sx-hydrate-islands :effects (mutation io) (fn (root) (let ((els (dom-query-all (or root (dom-body)) "[data-sx-island]"))) (log-info (str "sx-hydrate-islands: " (len els) " island(s) in " (if root "subtree" "document"))) (for-each (fn (el) (if (is-processed? el "island-hydrated") (log-info (str " skip (already hydrated): " (dom-get-attr el "data-sx-island"))) (do (log-info (str " hydrating: " (dom-get-attr el "data-sx-island"))) (mark-processed! el "island-hydrated") (hydrate-island el)))) els))))
(define
sx-hydrate-islands
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all (or root (dom-body)) "[data-sx-island]")))
(log-info
(str
"sx-hydrate-islands: "
(len els)
" island(s) in "
(if root "subtree" "document")))
(for-each
(fn
(el)
(if
(is-processed? el "island-hydrated")
(log-info
(str
" skip (already hydrated): "
(dom-get-attr el "data-sx-island")))
(do
(log-info
(str " hydrating: " (dom-get-attr el "data-sx-island")))
(mark-processed! el "island-hydrated")
(hydrate-island el))))
els))))
(define hydrate-island :effects (mutation io) (fn (el) (let ((name (dom-get-attr el "data-sx-island")) (state-sx (or (dom-get-attr el "data-sx-state") "{}"))) (let ((comp-name (str "~" name)) (env (get-render-env nil))) (let ((comp (env-get env comp-name))) (if (not (or (component? comp) (island? comp))) (log-warn (str "hydrate-island: unknown island " comp-name)) (let ((kwargs (or (first (sx-parse state-sx)) {})) (disposers (list)) (local (env-merge (component-closure comp) env))) (for-each (fn ((p :as string)) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) (let ((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el))))) (dom-set-text-content el "") (dom-append el body-dom) (dom-set-data el "sx-disposers" disposers) (process-elements el) (log-info (str "hydrated island: " comp-name " (" (len disposers) " disposers)"))))))))))
(define
hydrate-island
:effects (mutation io)
(fn
(el)
(let
((name (dom-get-attr el "data-sx-island"))
(state-sx (or (dom-get-attr el "data-sx-state") "{}")))
(let
((comp-name (str "~" name)) (env (get-render-env nil)))
(let
((comp (env-get env comp-name)))
(if
(not (or (component? comp) (island? comp)))
(log-warn (str "hydrate-island: unknown island " comp-name))
(let
((kwargs (or (first (sx-parse state-sx)) {}))
(disposers (list))
(local (env-merge (component-closure comp) env)))
(for-each
(fn
((p :as string))
(env-bind!
local
p
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp))
(let
((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el)))))
(dom-set-text-content el "")
(dom-append el body-dom)
(dom-set-data el "sx-disposers" disposers)
(process-elements el)
(log-info
(str
"hydrated island: "
comp-name
" ("
(len disposers)
" disposers)"))))))))))
(define dispose-island :effects (mutation io) (fn (el) (let ((disposers (dom-get-data el "sx-disposers"))) (when disposers (for-each (fn ((d :as lambda)) (when (callable? d) (d))) disposers) (dom-set-data el "sx-disposers" nil))) (clear-processed! el "island-hydrated")))
(define
dispose-island
:effects (mutation io)
(fn
(el)
(let
((disposers (dom-get-data el "sx-disposers")))
(when
disposers
(for-each
(fn ((d :as lambda)) (when (callable? d) (d)))
disposers)
(dom-set-data el "sx-disposers" nil)))
(clear-processed! el "island-hydrated")))
(define dispose-islands-in :effects (mutation io) (fn (root) (when root (let ((islands (dom-query-all root "[data-sx-island]"))) (when (and islands (not (empty? islands))) (let ((to-dispose (filter (fn (el) (not (is-processed? el "island-hydrated"))) islands))) (when (not (empty? to-dispose)) (log-info (str "disposing " (len to-dispose) " island(s)")) (for-each dispose-island to-dispose))))))))
(define
dispose-islands-in
:effects (mutation io)
(fn
(root)
(when
root
(let
((islands (dom-query-all root "[data-sx-island]")))
(when
(and islands (not (empty? islands)))
(let
((to-dispose (filter (fn (el) (not (is-processed? el "island-hydrated"))) islands)))
(when
(not (empty? to-dispose))
(log-info (str "disposing " (len to-dispose) " island(s)"))
(for-each dispose-island to-dispose))))))))
(define force-dispose-islands-in :effects (mutation io) (fn (root) (when root (let ((islands (dom-query-all root "[data-sx-island]"))) (when (and islands (not (empty? islands))) (log-info (str "force-disposing " (len islands) " island(s)")) (for-each dispose-island islands))))))
(define
force-dispose-islands-in
:effects (mutation io)
(fn
(root)
(when
root
(let
((islands (dom-query-all root "[data-sx-island]")))
(when
(and islands (not (empty? islands)))
(log-info (str "force-disposing " (len islands) " island(s)"))
(for-each dispose-island islands))))))
(define *pre-render-hooks* (list))
(define *post-render-hooks* (list))
(define register-pre-render-hook :effects (mutation) (fn ((hook-fn :as lambda)) (append! *pre-render-hooks* hook-fn)))
(define
register-pre-render-hook
:effects (mutation)
(fn ((hook-fn :as lambda)) (append! *pre-render-hooks* hook-fn)))
(define register-post-render-hook :effects (mutation) (fn ((hook-fn :as lambda)) (append! *post-render-hooks* hook-fn)))
(define
register-post-render-hook
:effects (mutation)
(fn ((hook-fn :as lambda)) (append! *post-render-hooks* hook-fn)))
(define run-pre-render-hooks :effects (mutation io) (fn () (for-each (fn (hook) (cek-call hook nil)) *pre-render-hooks*)))
(define
run-pre-render-hooks
:effects (mutation io)
(fn () (for-each (fn (hook) (cek-call hook nil)) *pre-render-hooks*)))
(define run-post-render-hooks :effects (mutation io) (fn () (log-info (str "run-post-render-hooks: " (len *post-render-hooks*) " hooks")) (for-each (fn (hook) (log-info (str " hook type: " (type-of hook) " callable: " (callable? hook) " lambda: " (lambda? hook))) (cek-call hook nil)) *post-render-hooks*)))
(define
run-post-render-hooks
:effects (mutation io)
(fn
()
(log-info
(str "run-post-render-hooks: " (len *post-render-hooks*) " hooks"))
(for-each
(fn
(hook)
(log-info
(str
" hook type: "
(type-of hook)
" callable: "
(callable? hook)
" lambda: "
(lambda? hook)))
(cek-call hook nil))
*post-render-hooks*)))
(define boot-init :effects (mutation io) (fn () (do (log-info (str "sx-browser " SX_VERSION)) (init-css-tracking) (process-page-scripts) (process-sx-scripts nil) (sx-hydrate-elements nil) (sx-hydrate-islands nil) (run-post-render-hooks) (process-elements nil) (dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0))))))
(define
boot-init
:effects (mutation io)
(fn
()
(do
(log-info (str "sx-browser " SX_VERSION))
(init-css-tracking)
(process-page-scripts)
(process-sx-scripts nil)
(sx-hydrate-elements nil)
(sx-hydrate-islands nil)
(run-post-render-hooks)
(process-elements nil)
(dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0))))))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,425 +1,418 @@
;; ==========================================================================
;; dom.sx — DOM library functions
;;
;; All DOM operations expressed using the host FFI primitives:
;; host-get — read property from host object
;; host-set! — write property on host object
;; host-call — call method on host object
;; host-new — construct host object
;; host-global — access global (window/document/etc.)
;; host-callback — wrap SX function as host callback
;; host-typeof — check host object type
;;
;; These are LIBRARY FUNCTIONS — portable, auditable, in-band SX.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; Globals
;; --------------------------------------------------------------------------
(define dom-document (fn () (host-global "document")))
(define dom-window (fn () (host-global "window")))
(define dom-body (fn () (host-get (dom-document) "body")))
(define dom-head (fn () (host-get (dom-document) "head")))
(define dom-window (fn () (host-global "window")))
;; --------------------------------------------------------------------------
;; Node creation
;; --------------------------------------------------------------------------
(define dom-body (fn () (host-get (dom-document) "body")))
(define dom-create-element
(fn (tag &rest ns-arg)
(let ((ns (if (and ns-arg (not (empty? ns-arg))) (first ns-arg) nil)))
(if ns
(define dom-head (fn () (host-get (dom-document) "head")))
(define
dom-create-element
(fn
(tag &rest ns-arg)
(let
((ns (if (and ns-arg (not (empty? ns-arg))) (first ns-arg) nil)))
(if
ns
(host-call (dom-document) "createElementNS" ns tag)
(host-call (dom-document) "createElement" tag)))))
(define create-text-node
(fn (s)
(host-call (dom-document) "createTextNode" s)))
(define
create-text-node
(fn (s) (host-call (dom-document) "createTextNode" s)))
(define create-fragment
(fn ()
(host-call (dom-document) "createDocumentFragment")))
(define
create-fragment
(fn () (host-call (dom-document) "createDocumentFragment")))
(define create-comment
(fn (text)
(host-call (dom-document) "createComment" (or text ""))))
(define
create-comment
(fn (text) (host-call (dom-document) "createComment" (or text ""))))
(define
dom-append
(fn
(parent child)
(when (and parent child) (host-call parent "appendChild" child))))
;; --------------------------------------------------------------------------
;; Tree manipulation
;; --------------------------------------------------------------------------
(define
dom-prepend
(fn
(parent child)
(when (and parent child) (host-call parent "prepend" child))))
(define dom-append
(fn (parent child)
(when (and parent child)
(host-call parent "appendChild" child))))
(define
dom-insert-before
(fn
(parent child ref)
(when (and parent child) (host-call parent "insertBefore" child ref))))
(define dom-prepend
(fn (parent child)
(when (and parent child)
(host-call parent "prepend" child))))
(define dom-insert-before
(fn (parent child ref)
(when (and parent child)
(host-call parent "insertBefore" child ref))))
(define dom-insert-after
(fn (ref node)
(define
dom-insert-after
(fn
(ref node)
"Insert node after ref in the same parent."
(let ((parent (host-get ref "parentNode"))
(next (host-get ref "nextSibling")))
(when parent
(if next
(let
((parent (host-get ref "parentNode"))
(next (host-get ref "nextSibling")))
(when
parent
(if
next
(host-call parent "insertBefore" node next)
(host-call parent "appendChild" node))))))
(define dom-remove
(fn (el)
(when el (host-call el "remove"))))
(define dom-remove (fn (el) (when el (host-call el "remove"))))
(define dom-is-active-element?
(fn (el)
(let ((active (host-get (dom-document) "activeElement")))
(if (and active el)
(identical? el active)
false))))
(define
dom-is-active-element?
(fn
(el)
(let
((active (host-get (dom-document) "activeElement")))
(if (and active el) (identical? el active) false))))
(define dom-is-input-element?
(fn (el)
(let ((tag (upper (or (dom-tag-name el) ""))))
(define
dom-is-input-element?
(fn
(el)
(let
((tag (upper (or (dom-tag-name el) ""))))
(or (= tag "INPUT") (= tag "TEXTAREA") (= tag "SELECT")))))
(define dom-is-child-of?
(fn (child parent)
(and child parent (host-call parent "contains" child))))
(define
dom-is-child-of?
(fn (child parent) (and child parent (host-call parent "contains" child))))
(define dom-attr-list
(fn (el)
;; Return list of (name value) pairs for all attributes on the element.
(let ((attrs (host-get el "attributes"))
(result (list)))
(when attrs
(let ((n (host-get attrs "length")))
(let loop ((i 0))
(when (< i n)
(let ((attr (host-call attrs "item" i)))
(append! result (list (host-get attr "name") (host-get attr "value"))))
(define
dom-attr-list
(fn
(el)
(let
((attrs (host-get el "attributes")) (result (list)))
(when
attrs
(let
((n (host-get attrs "length")))
(let
loop
((i 0))
(when
(< i n)
(let
((attr (host-call attrs "item" i)))
(append!
result
(list (host-get attr "name") (host-get attr "value"))))
(loop (+ i 1))))))
result)))
(define dom-remove-child
(fn (parent child)
(when (and parent child)
(host-call parent "removeChild" child))))
(define
dom-remove-child
(fn
(parent child)
(when (and parent child) (host-call parent "removeChild" child))))
(define dom-replace-child
(fn (parent new-child old-child)
(when (and parent new-child old-child)
(define
dom-replace-child
(fn
(parent new-child old-child)
(when
(and parent new-child old-child)
(host-call parent "replaceChild" new-child old-child))))
(define dom-clone
(fn (node deep)
(host-call node "cloneNode" (if (nil? deep) true deep))))
(define
dom-clone
(fn (node deep) (host-call node "cloneNode" (if (nil? deep) true deep))))
;; --------------------------------------------------------------------------
;; Queries
;; --------------------------------------------------------------------------
(define dom-query
(fn (root-or-sel &rest rest)
(if (empty? rest)
;; Single arg: selector on document
(define
dom-query
(fn
(root-or-sel &rest rest)
(if
(empty? rest)
(host-call (dom-document) "querySelector" root-or-sel)
;; Two args: root element + selector
(host-call root-or-sel "querySelector" (first rest)))))
(define dom-query-all
(fn (root sel)
(define
dom-query-all
(fn
(root sel)
"Query DOM and return an SX list (not a host NodeList)."
(let ((node-list (if (nil? sel)
(host-call (dom-document) "querySelectorAll" root)
(host-call root "querySelectorAll" sel))))
;; Convert NodeList → SX list by indexing
(if (nil? node-list)
(let
((node-list (if (nil? sel) (host-call (dom-document) "querySelectorAll" root) (host-call root "querySelectorAll" sel))))
(if
(nil? node-list)
(list)
(let ((n (host-get node-list "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(let
((n (host-get node-list "length")) (result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call node-list "item" i))
(loop (+ i 1))))
result)))))
(define dom-query-by-id
(fn (id)
(host-call (dom-document) "getElementById" id)))
(define
dom-query-by-id
(fn (id) (host-call (dom-document) "getElementById" id)))
(define dom-closest
(fn (el sel)
(when el (host-call el "closest" sel))))
(define dom-closest (fn (el sel) (when el (host-call el "closest" sel))))
(define dom-matches?
(fn (el sel)
(if (and el (host-get el "matches"))
(host-call el "matches" sel)
false)))
(define
dom-matches?
(fn
(el sel)
(if (and el (host-get el "matches")) (host-call el "matches" sel) false)))
;; --------------------------------------------------------------------------
;; Attributes
;; --------------------------------------------------------------------------
(define dom-get-attr
(fn (el name)
(if (and el (host-get el "getAttribute"))
(let ((v (host-call el "getAttribute" name)))
(if (nil? v) nil v))
(define
dom-get-attr
(fn
(el name)
(if
(and el (host-get el "getAttribute"))
(let ((v (host-call el "getAttribute" name))) (if (nil? v) nil v))
nil)))
(define dom-set-attr
(fn (el name val)
(when (and el (host-get el "setAttribute"))
(define
dom-set-attr
(fn
(el name val)
(when
(and el (host-get el "setAttribute"))
(host-call el "setAttribute" name val))))
(define dom-remove-attr
(fn (el name)
(when (and el (host-get el "removeAttribute"))
(define
dom-remove-attr
(fn
(el name)
(when
(and el (host-get el "removeAttribute"))
(host-call el "removeAttribute" name))))
(define dom-has-attr?
(fn (el name)
(if (and el (host-get el "hasAttribute"))
(define
dom-has-attr?
(fn
(el name)
(if
(and el (host-get el "hasAttribute"))
(host-call el "hasAttribute" name)
false)))
(define
dom-add-class
(fn (el cls) (when el (host-call (host-get el "classList") "add" cls))))
;; --------------------------------------------------------------------------
;; Classes
;; --------------------------------------------------------------------------
(define
dom-remove-class
(fn
(el cls)
(when el (host-call (host-get el "classList") "remove" cls))))
(define dom-add-class
(fn (el cls)
(when el
(host-call (host-get el "classList") "add" cls))))
(define
dom-has-class?
(fn
(el cls)
(if el (host-call (host-get el "classList") "contains" cls) false)))
(define dom-remove-class
(fn (el cls)
(when el
(host-call (host-get el "classList") "remove" cls))))
(define dom-text-content (fn (el) (host-get el "textContent")))
(define dom-has-class?
(fn (el cls)
(if el
(host-call (host-get el "classList") "contains" cls)
false)))
(define dom-set-text-content (fn (el val) (host-set! el "textContent" val)))
(define dom-inner-html (fn (el) (host-get el "innerHTML")))
;; --------------------------------------------------------------------------
;; Content
;; --------------------------------------------------------------------------
(define dom-set-inner-html (fn (el val) (host-set! el "innerHTML" val)))
(define dom-text-content
(fn (el) (host-get el "textContent")))
(define dom-outer-html (fn (el) (host-get el "outerHTML")))
(define dom-set-text-content
(fn (el val) (host-set! el "textContent" val)))
(define
dom-insert-adjacent-html
(fn (el position html) (host-call el "insertAdjacentHTML" position html)))
(define dom-inner-html
(fn (el) (host-get el "innerHTML")))
(define dom-get-style (fn (el prop) (host-get (host-get el "style") prop)))
(define dom-set-inner-html
(fn (el val) (host-set! el "innerHTML" val)))
(define dom-outer-html
(fn (el) (host-get el "outerHTML")))
(define dom-insert-adjacent-html
(fn (el position html)
(host-call el "insertAdjacentHTML" position html)))
;; --------------------------------------------------------------------------
;; Style & properties
;; --------------------------------------------------------------------------
(define dom-get-style
(fn (el prop)
(host-get (host-get el "style") prop)))
(define dom-set-style
(fn (el prop val)
(define
dom-set-style
(fn
(el prop val)
(host-call (host-get el "style") "setProperty" prop val)))
(define dom-get-prop
(fn (el name) (host-get el name)))
(define dom-get-prop (fn (el name) (host-get el name)))
(define dom-set-prop
(fn (el name val) (host-set! el name val)))
(define dom-set-prop (fn (el name val) (host-set! el name val)))
(define
dom-tag-name
(fn (el) (if el (lower (or (host-get el "tagName") "")) "")))
;; --------------------------------------------------------------------------
;; Node info
;; --------------------------------------------------------------------------
(define dom-node-type (fn (el) (host-get el "nodeType")))
(define dom-tag-name
(fn (el)
(if el (lower (or (host-get el "tagName") "")) "")))
(define dom-node-name (fn (el) (host-get el "nodeName")))
(define dom-node-type
(fn (el) (host-get el "nodeType")))
(define dom-id (fn (el) (host-get el "id")))
(define dom-node-name
(fn (el) (host-get el "nodeName")))
(define dom-parent (fn (el) (host-get el "parentNode")))
(define dom-id
(fn (el) (host-get el "id")))
(define dom-first-child (fn (el) (host-get el "firstChild")))
(define dom-parent
(fn (el) (host-get el "parentNode")))
(define dom-next-sibling (fn (el) (host-get el "nextSibling")))
(define dom-first-child
(fn (el) (host-get el "firstChild")))
(define dom-next-sibling
(fn (el) (host-get el "nextSibling")))
(define dom-child-list
(fn (el)
(define
dom-child-list
(fn
(el)
"Return child nodes as an SX list."
(if el
(let ((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(if
el
(let
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1))))
result)
(list))))
(define dom-is-fragment?
(fn (el) (= (host-get el "nodeType") 11)))
(define dom-is-fragment? (fn (el) (= (host-get el "nodeType") 11)))
(define dom-child-nodes
(fn (el)
(define
dom-child-nodes
(fn
(el)
"Return child nodes as an SX list."
(if el
(let ((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(if
el
(let
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1))))
result)
(list))))
(define dom-remove-children-after
(fn (marker)
(define
dom-remove-children-after
(fn
(marker)
"Remove all siblings after marker node."
(let ((parent (dom-parent marker)))
(when parent
(let loop ()
(let ((next (dom-next-sibling marker)))
(when next
(host-call parent "removeChild" next)
(loop))))))))
(let
((parent (dom-parent marker)))
(when
parent
(let
loop
()
(let
((next (dom-next-sibling marker)))
(when next (host-call parent "removeChild" next) (loop))))))))
(define dom-focus
(fn (el) (when el (host-call el "focus"))))
(define dom-focus (fn (el) (when el (host-call el "focus"))))
(define dom-parse-html
(fn (html)
(let ((parser (host-new "DOMParser"))
(doc (host-call parser "parseFromString" html "text/html")))
(define
dom-parse-html
(fn
(html)
(let
((parser (host-new "DOMParser"))
(doc (host-call parser "parseFromString" html "text/html")))
(host-get (host-get doc "body") "childNodes"))))
;; --------------------------------------------------------------------------
;; Events
;; --------------------------------------------------------------------------
(define dom-listen
(fn (el event-name handler)
(let ((cb (host-callback handler)))
(define
dom-listen
(fn
(el event-name handler)
(let
((cb (host-callback handler)))
(host-call el "addEventListener" event-name cb)
;; Return cleanup function
(fn () (host-call el "removeEventListener" event-name cb)))))
;; dom-add-listener — addEventListener with optional options
;; Used by orchestration.sx: (dom-add-listener el event handler opts)
(define dom-add-listener
(fn (el event-name handler &rest opts)
(let ((cb (host-callback handler)))
(if (and opts (not (empty? opts)))
(define
dom-add-listener
(fn
(el event-name handler &rest opts)
(let
((cb (host-callback handler)))
(if
(and opts (not (empty? opts)))
(host-call el "addEventListener" event-name cb (first opts))
(host-call el "addEventListener" event-name cb))
;; Return cleanup function
(fn () (host-call el "removeEventListener" event-name cb)))))
(define dom-dispatch
(fn (el event-name detail)
(let ((evt (host-new "CustomEvent" event-name
(dict "detail" detail "bubbles" true))))
(define
dom-dispatch
(fn
(el event-name detail)
(let
((evt (host-new "CustomEvent" event-name (dict "detail" detail "bubbles" true))))
(host-call el "dispatchEvent" evt))))
(define event-detail
(fn (evt) (host-get evt "detail")))
(define event-detail (fn (evt) (host-get evt "detail")))
(define prevent-default
(fn (e) (when e (host-call e "preventDefault"))))
(define prevent-default (fn (e) (when e (host-call e "preventDefault"))))
(define stop-propagation
(fn (e) (when e (host-call e "stopPropagation"))))
(define stop-propagation (fn (e) (when e (host-call e "stopPropagation"))))
(define event-modifier-key?
(fn (e)
(and e (or (host-get e "ctrlKey") (host-get e "metaKey")
(host-get e "shiftKey") (host-get e "altKey")))))
(define
event-modifier-key?
(fn
(e)
(and
e
(or
(host-get e "ctrlKey")
(host-get e "metaKey")
(host-get e "shiftKey")
(host-get e "altKey")))))
(define element-value
(fn (el)
(if (and el (not (nil? (host-get el "value"))))
(define
element-value
(fn
(el)
(if
(and el (not (nil? (host-get el "value"))))
(host-get el "value")
nil)))
(define error-message
(fn (e)
(if (and e (host-get e "message"))
(host-get e "message")
(str e))))
(define
error-message
(fn
(e)
(if (and e (host-get e "message")) (host-get e "message") (str e))))
;; --------------------------------------------------------------------------
;; DOM data storage
;; --------------------------------------------------------------------------
(define dom-get-data
(fn (el key)
(let ((store (host-get el "__sx_data")))
(define
dom-get-data
(fn
(el key)
(let
((store (host-get el "__sx_data")))
(if store (host-get store key) nil))))
(define dom-set-data
(fn (el key val)
(when (not (host-get el "__sx_data"))
(define
dom-set-data
(fn
(el key val)
(when
(not (host-get el "__sx_data"))
(host-set! el "__sx_data" (dict)))
(host-set! (host-get el "__sx_data") key val)))
(define
dom-append-to-head
(fn (el) (when (dom-head) (host-call (dom-head) "appendChild" el))))
;; --------------------------------------------------------------------------
;; Head manipulation
;; --------------------------------------------------------------------------
(define dom-append-to-head
(fn (el)
(when (dom-head)
(host-call (dom-head) "appendChild" el))))
(define set-document-title
(fn (title)
(host-set! (dom-document) "title" title)))
(define
set-document-title
(fn (title) (host-set! (dom-document) "title" title)))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"magic":"SXBC","version":1,"hash":"19bca721c37b25b6","module":{"bytecode":[52,1,0,0,128,0,0,5,51,3,0,128,2,0,5,51,5,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,50],"constants":[{"t":"s","v":"freeze-registry"},{"t":"s","v":"dict"},{"t":"s","v":"freeze-signal"},{"t":"code","v":{"bytecode":[1,1,0,2,52,0,0,2,17,2,16,2,33,56,0,20,3,0,16,2,52,2,0,2,6,34,5,0,5,52,4,0,0,17,3,20,5,0,16,3,1,7,0,16,0,1,8,0,16,1,52,6,0,4,48,2,5,20,3,0,16,2,16,3,52,9,0,3,32,1,0,2,50],"constants":[{"t":"s","v":"context"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"append!"},{"t":"s","v":"dict"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"dict-set!"}]}},{"t":"s","v":"freeze-scope"},{"t":"code","v":{"bytecode":[1,1,0,16,0,52,0,0,2,5,20,3,0,16,0,52,4,0,0,52,2,0,3,5,20,5,0,16,1,2,48,2,5,1,1,0,52,6,0,1,5,2,50],"constants":[{"t":"s","v":"scope-push!"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"dict-set!"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"cek-call"},{"t":"s","v":"scope-pop!"}]}},{"t":"s","v":"cek-freeze-scope"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,6,34,5,0,5,52,2,0,0,17,1,52,3,0,0,17,2,51,5,0,1,2,16,1,52,4,0,2,5,1,6,0,16,0,1,7,0,16,2,52,3,0,4,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"dict"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[18,0,16,0,1,2,0,52,1,0,2,20,3,0,16,0,1,4,0,52,1,0,2,48,1,52,0,0,3,50],"constants":[{"t":"s","v":"dict-set!"},{"t":"s","v":"get"},{"t":"s","v":"name"},{"t":"s","v":"signal-value"},{"t":"s","v":"signal"}]}},{"t":"s","v":"name"},{"t":"s","v":"signals"}]}},{"t":"s","v":"cek-freeze-all"},{"t":"code","v":{"bytecode":[51,1,0,20,3,0,52,2,0,1,52,0,0,2,50],"constants":[{"t":"s","v":"map"},{"t":"code","v":{"bytecode":[20,0,0,16,0,49,1,50],"constants":[{"t":"s","v":"cek-freeze-scope"}]}},{"t":"s","v":"keys"},{"t":"s","v":"freeze-registry"}]}},{"t":"s","v":"cek-thaw-scope"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,6,34,5,0,5,52,2,0,0,17,2,16,1,1,3,0,52,0,0,2,17,3,16,3,33,14,0,51,5,0,1,3,16,2,52,4,0,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"signals"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[16,0,1,1,0,52,0,0,2,17,1,16,0,1,2,0,52,0,0,2,17,2,18,0,16,1,52,0,0,2,17,3,16,3,52,4,0,1,52,3,0,1,33,12,0,20,5,0,16,2,16,3,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"not"},{"t":"s","v":"nil?"},{"t":"s","v":"reset!"}]}}]}},{"t":"s","v":"cek-thaw-all"},{"t":"code","v":{"bytecode":[51,1,0,16,0,52,0,0,2,50],"constants":[{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[20,0,0,16,0,1,2,0,52,1,0,2,16,0,49,2,50],"constants":[{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"name"}]}}]}},{"t":"s","v":"freeze-to-sx"},{"t":"code","v":{"bytecode":[20,0,0,20,1,0,16,0,48,1,49,1,50],"constants":[{"t":"s","v":"sx-serialize"},{"t":"s","v":"cek-freeze-scope"}]}},{"t":"s","v":"thaw-from-sx"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,16,1,52,2,0,1,52,1,0,1,33,27,0,16,1,52,3,0,1,17,2,20,4,0,16,2,1,6,0,52,5,0,2,16,2,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"sx-parse"},{"t":"s","v":"not"},{"t":"s","v":"empty?"},{"t":"s","v":"first"},{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"name"}]}}]}}
{"magic":"SXBC","version":1,"hash":"19bca721c37b25b6","module":{"bytecode":[52,1,0,0,128,0,0,5,51,3,0,128,2,0,5,51,5,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,50],"constants":[{"t":"s","v":"freeze-registry"},{"t":"s","v":"dict"},{"t":"s","v":"freeze-signal"},{"t":"code","v":{"bytecode":[1,1,0,2,52,0,0,2,17,2,16,2,33,56,0,20,3,0,16,2,52,2,0,2,6,34,5,0,5,52,4,0,0,17,3,20,5,0,16,3,1,7,0,16,0,1,8,0,16,1,52,6,0,4,48,2,5,20,3,0,16,2,16,3,52,9,0,3,32,1,0,2,50],"constants":[{"t":"s","v":"context"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"append!"},{"t":"s","v":"dict"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"dict-set!"}],"arity":2}},{"t":"s","v":"freeze-scope"},{"t":"code","v":{"bytecode":[1,1,0,16,0,52,0,0,2,5,20,3,0,16,0,52,4,0,0,52,2,0,3,5,20,5,0,16,1,2,48,2,5,1,1,0,52,6,0,1,5,2,50],"constants":[{"t":"s","v":"scope-push!"},{"t":"s","v":"sx-freeze-scope"},{"t":"s","v":"dict-set!"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"cek-call"},{"t":"s","v":"scope-pop!"}],"arity":2}},{"t":"s","v":"cek-freeze-scope"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,6,34,5,0,5,52,2,0,0,17,1,52,3,0,0,17,2,51,5,0,1,2,16,1,52,4,0,2,5,1,6,0,16,0,1,7,0,16,2,52,3,0,4,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"dict"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[18,0,16,0,1,2,0,52,1,0,2,20,3,0,16,0,1,4,0,52,1,0,2,48,1,52,0,0,3,50],"constants":[{"t":"s","v":"dict-set!"},{"t":"s","v":"get"},{"t":"s","v":"name"},{"t":"s","v":"signal-value"},{"t":"s","v":"signal"}],"arity":1,"upvalue-count":1}},{"t":"s","v":"name"},{"t":"s","v":"signals"}],"arity":1}},{"t":"s","v":"cek-freeze-all"},{"t":"code","v":{"bytecode":[51,1,0,20,3,0,52,2,0,1,52,0,0,2,50],"constants":[{"t":"s","v":"map"},{"t":"code","v":{"bytecode":[20,0,0,16,0,49,1,50],"constants":[{"t":"s","v":"cek-freeze-scope"}],"arity":1}},{"t":"s","v":"keys"},{"t":"s","v":"freeze-registry"}]}},{"t":"s","v":"cek-thaw-scope"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,6,34,5,0,5,52,2,0,0,17,2,16,1,1,3,0,52,0,0,2,17,3,16,3,33,14,0,51,5,0,1,3,16,2,52,4,0,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"freeze-registry"},{"t":"s","v":"list"},{"t":"s","v":"signals"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[16,0,1,1,0,52,0,0,2,17,1,16,0,1,2,0,52,0,0,2,17,2,18,0,16,1,52,0,0,2,17,3,16,3,52,4,0,1,52,3,0,1,33,12,0,20,5,0,16,2,16,3,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"get"},{"t":"s","v":"name"},{"t":"s","v":"signal"},{"t":"s","v":"not"},{"t":"s","v":"nil?"},{"t":"s","v":"reset!"}],"arity":1,"upvalue-count":1}}],"arity":2}},{"t":"s","v":"cek-thaw-all"},{"t":"code","v":{"bytecode":[51,1,0,16,0,52,0,0,2,50],"constants":[{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[20,0,0,16,0,1,2,0,52,1,0,2,16,0,49,2,50],"constants":[{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"name"}],"arity":1}}],"arity":1}},{"t":"s","v":"freeze-to-sx"},{"t":"code","v":{"bytecode":[20,0,0,20,1,0,16,0,48,1,49,1,50],"constants":[{"t":"s","v":"sx-serialize"},{"t":"s","v":"cek-freeze-scope"}],"arity":1}},{"t":"s","v":"thaw-from-sx"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,16,1,52,2,0,1,52,1,0,1,33,27,0,16,1,52,3,0,1,17,2,20,4,0,16,2,1,6,0,52,5,0,2,16,2,49,2,32,1,0,2,50],"constants":[{"t":"s","v":"sx-parse"},{"t":"s","v":"not"},{"t":"s","v":"empty?"},{"t":"s","v":"first"},{"t":"s","v":"cek-thaw-scope"},{"t":"s","v":"get"},{"t":"s","v":"name"}],"arity":1}}]}}

View File

@@ -1 +1 @@
{"magic":"SXBC","version":1,"hash":"93780bb9539e858f","module":{"bytecode":[51,1,0,128,0,0,5,51,3,0,128,2,0,5,51,5,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,5,51,19,0,128,18,0,5,51,21,0,128,20,0,50],"constants":[{"t":"s","v":"assert-signal-value"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,2,20,1,0,16,2,16,1,1,3,0,16,1,1,4,0,16,2,52,2,0,4,49,3,50],"constants":[{"t":"s","v":"deref"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected signal value "},{"t":"s","v":", got "}]}},{"t":"s","v":"assert-signal-has-subscribers"},{"t":"code","v":{"bytecode":[20,0,0,20,3,0,16,0,48,1,52,2,0,1,1,4,0,52,1,0,2,1,5,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":">"},{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"n","v":0},{"t":"s","v":"Expected signal to have subscribers"}]}},{"t":"s","v":"assert-signal-no-subscribers"},{"t":"code","v":{"bytecode":[20,0,0,20,3,0,16,0,48,1,52,2,0,1,1,4,0,52,1,0,2,1,5,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":"="},{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"n","v":0},{"t":"s","v":"Expected signal to have no subscribers"}]}},{"t":"s","v":"assert-signal-subscriber-count"},{"t":"code","v":{"bytecode":[20,1,0,16,0,48,1,52,0,0,1,17,2,20,2,0,16,2,16,1,1,4,0,16,1,1,5,0,16,2,52,3,0,4,49,3,50],"constants":[{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" subscribers, got "}]}},{"t":"s","v":"simulate-signal-set!"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,49,2,50],"constants":[{"t":"s","v":"reset!"}]}},{"t":"s","v":"simulate-signal-swap!"},{"t":"code","v":{"bytecode":[20,1,0,16,0,16,1,16,2,52,2,0,2,52,2,0,2,52,0,0,2,50],"constants":[{"t":"s","v":"apply"},{"t":"s","v":"swap!"},{"t":"s","v":"cons"}]}},{"t":"s","v":"assert-computed-dep-count"},{"t":"code","v":{"bytecode":[20,1,0,16,0,48,1,52,0,0,1,17,2,20,2,0,16,2,16,1,1,4,0,16,1,1,5,0,16,2,52,3,0,4,49,3,50],"constants":[{"t":"s","v":"len"},{"t":"s","v":"signal-deps"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" deps, got "}]}},{"t":"s","v":"assert-computed-depends-on"},{"t":"code","v":{"bytecode":[20,0,0,20,2,0,16,0,48,1,16,1,52,1,0,2,1,3,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":"contains?"},{"t":"s","v":"signal-deps"},{"t":"s","v":"Expected computed to depend on the given signal"}]}},{"t":"s","v":"count-effect-runs"},{"t":"code","v":{"bytecode":[20,0,0,1,1,0,48,1,17,1,20,2,0,51,3,0,1,1,48,1,5,1,1,0,17,2,20,2,0,51,4,0,1,2,1,0,48,1,17,3,16,2,50],"constants":[{"t":"s","v":"signal"},{"t":"n","v":0},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,49,1,50],"constants":[{"t":"s","v":"deref"}]}},{"t":"code","v":{"bytecode":[18,0,1,1,0,52,0,0,2,19,0,5,20,2,0,18,1,2,49,2,50],"constants":[{"t":"s","v":"+"},{"t":"n","v":1},{"t":"s","v":"cek-call"}]}}]}},{"t":"s","v":"make-test-signal"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,52,1,0,0,17,2,20,2,0,51,3,0,1,2,1,1,48,1,5,1,0,0,16,1,1,4,0,16,2,65,2,0,50],"constants":[{"t":"s","v":"signal"},{"t":"s","v":"list"},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,20,1,0,18,1,48,1,49,2,50],"constants":[{"t":"s","v":"append!"},{"t":"s","v":"deref"}]}},{"t":"s","v":"history"}]}},{"t":"s","v":"assert-batch-coalesces"},{"t":"code","v":{"bytecode":[1,0,0,17,2,20,1,0,1,0,0,48,1,17,3,20,2,0,51,3,0,1,3,1,2,48,1,5,1,0,0,17,2,5,20,4,0,16,0,48,1,5,20,5,0,16,2,16,1,1,7,0,16,1,1,8,0,16,2,52,6,0,4,49,3,50],"constants":[{"t":"n","v":0},{"t":"s","v":"signal"},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,48,1,5,18,1,1,2,0,52,1,0,2,19,1,50],"constants":[{"t":"s","v":"deref"},{"t":"s","v":"+"},{"t":"n","v":1}]}},{"t":"s","v":"batch"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" notifications, got "}]}}]}}
{"magic":"SXBC","version":1,"hash":"93780bb9539e858f","module":{"bytecode":[51,1,0,128,0,0,5,51,3,0,128,2,0,5,51,5,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,5,51,19,0,128,18,0,5,51,21,0,128,20,0,50],"constants":[{"t":"s","v":"assert-signal-value"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,2,20,1,0,16,2,16,1,1,3,0,16,1,1,4,0,16,2,52,2,0,4,49,3,50],"constants":[{"t":"s","v":"deref"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected signal value "},{"t":"s","v":", got "}],"arity":2}},{"t":"s","v":"assert-signal-has-subscribers"},{"t":"code","v":{"bytecode":[20,0,0,20,3,0,16,0,48,1,52,2,0,1,1,4,0,52,1,0,2,1,5,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":">"},{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"n","v":0},{"t":"s","v":"Expected signal to have subscribers"}],"arity":1}},{"t":"s","v":"assert-signal-no-subscribers"},{"t":"code","v":{"bytecode":[20,0,0,20,3,0,16,0,48,1,52,2,0,1,1,4,0,52,1,0,2,1,5,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":"="},{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"n","v":0},{"t":"s","v":"Expected signal to have no subscribers"}],"arity":1}},{"t":"s","v":"assert-signal-subscriber-count"},{"t":"code","v":{"bytecode":[20,1,0,16,0,48,1,52,0,0,1,17,2,20,2,0,16,2,16,1,1,4,0,16,1,1,5,0,16,2,52,3,0,4,49,3,50],"constants":[{"t":"s","v":"len"},{"t":"s","v":"signal-subscribers"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" subscribers, got "}],"arity":2}},{"t":"s","v":"simulate-signal-set!"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,49,2,50],"constants":[{"t":"s","v":"reset!"}],"arity":2}},{"t":"s","v":"simulate-signal-swap!"},{"t":"code","v":{"bytecode":[20,1,0,16,0,16,1,16,2,52,2,0,2,52,2,0,2,52,0,0,2,50],"constants":[{"t":"s","v":"apply"},{"t":"s","v":"swap!"},{"t":"s","v":"cons"}],"arity":3}},{"t":"s","v":"assert-computed-dep-count"},{"t":"code","v":{"bytecode":[20,1,0,16,0,48,1,52,0,0,1,17,2,20,2,0,16,2,16,1,1,4,0,16,1,1,5,0,16,2,52,3,0,4,49,3,50],"constants":[{"t":"s","v":"len"},{"t":"s","v":"signal-deps"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" deps, got "}],"arity":2}},{"t":"s","v":"assert-computed-depends-on"},{"t":"code","v":{"bytecode":[20,0,0,20,2,0,16,0,48,1,16,1,52,1,0,2,1,3,0,49,2,50],"constants":[{"t":"s","v":"assert"},{"t":"s","v":"contains?"},{"t":"s","v":"signal-deps"},{"t":"s","v":"Expected computed to depend on the given signal"}],"arity":2}},{"t":"s","v":"count-effect-runs"},{"t":"code","v":{"bytecode":[20,0,0,1,1,0,48,1,17,1,20,2,0,51,3,0,1,1,48,1,5,1,1,0,17,2,20,2,0,51,4,0,1,2,1,0,48,1,17,3,16,2,50],"constants":[{"t":"s","v":"signal"},{"t":"n","v":0},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,49,1,50],"constants":[{"t":"s","v":"deref"}],"upvalue-count":1}},{"t":"code","v":{"bytecode":[18,0,1,1,0,52,0,0,2,19,0,5,20,2,0,18,1,2,49,2,50],"constants":[{"t":"s","v":"+"},{"t":"n","v":1},{"t":"s","v":"cek-call"}],"upvalue-count":2}}],"arity":1}},{"t":"s","v":"make-test-signal"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,52,1,0,0,17,2,20,2,0,51,3,0,1,2,1,1,48,1,5,1,0,0,16,1,1,4,0,16,2,65,2,0,50],"constants":[{"t":"s","v":"signal"},{"t":"s","v":"list"},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,20,1,0,18,1,48,1,49,2,50],"constants":[{"t":"s","v":"append!"},{"t":"s","v":"deref"}],"upvalue-count":2}},{"t":"s","v":"history"}],"arity":1}},{"t":"s","v":"assert-batch-coalesces"},{"t":"code","v":{"bytecode":[1,0,0,17,2,20,1,0,1,0,0,48,1,17,3,20,2,0,51,3,0,1,3,1,2,48,1,5,1,0,0,17,2,5,20,4,0,16,0,48,1,5,20,5,0,16,2,16,1,1,7,0,16,1,1,8,0,16,2,52,6,0,4,49,3,50],"constants":[{"t":"n","v":0},{"t":"s","v":"signal"},{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,48,1,5,18,1,1,2,0,52,1,0,2,19,1,50],"constants":[{"t":"s","v":"deref"},{"t":"s","v":"+"},{"t":"n","v":1}],"upvalue-count":2}},{"t":"s","v":"batch"},{"t":"s","v":"assert="},{"t":"s","v":"str"},{"t":"s","v":"Expected "},{"t":"s","v":" notifications, got "}],"arity":2}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"magic":"SXBC","version":1,"hash":"1e908c466d2b8c22","module":{"bytecode":[51,1,0,128,0,0,5,51,3,0,128,2,0,5,52,5,0,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,5,51,19,0,128,18,0,50],"constants":[{"t":"s","v":"with-marsh-scope"},{"t":"code","v":{"bytecode":[52,0,0,0,17,2,20,1,0,51,2,0,1,2,16,1,48,2,5,20,3,0,16,0,1,4,0,16,2,49,3,50],"constants":[{"t":"s","v":"list"},{"t":"s","v":"with-island-scope"},{"t":"code","v":{"bytecode":[20,0,0,18,0,16,0,49,2,50],"constants":[{"t":"s","v":"append!"}]}},{"t":"s","v":"dom-set-data"},{"t":"s","v":"sx-marsh-disposers"}]}},{"t":"s","v":"dispose-marsh-scope"},{"t":"code","v":{"bytecode":[20,0,0,16,0,1,1,0,48,2,17,1,16,1,33,24,0,51,3,0,16,1,52,2,0,2,5,20,4,0,16,0,1,1,0,2,49,3,32,1,0,2,50],"constants":[{"t":"s","v":"dom-get-data"},{"t":"s","v":"sx-marsh-disposers"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[20,0,0,16,0,2,49,2,50],"constants":[{"t":"s","v":"cek-call"}]}},{"t":"s","v":"dom-set-data"}]}},{"t":"s","v":"*store-registry*"},{"t":"s","v":"dict"},{"t":"s","v":"def-store"},{"t":"code","v":{"bytecode":[20,0,0,17,2,16,2,16,0,52,2,0,2,52,1,0,1,33,22,0,16,2,16,0,20,4,0,16,1,2,48,2,52,3,0,3,21,0,0,32,1,0,2,5,20,0,0,16,0,52,5,0,2,50],"constants":[{"t":"s","v":"*store-registry*"},{"t":"s","v":"not"},{"t":"s","v":"has-key?"},{"t":"s","v":"assoc"},{"t":"s","v":"cek-call"},{"t":"s","v":"get"}]}},{"t":"s","v":"use-store"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,33,12,0,20,1,0,16,0,52,2,0,2,32,16,0,1,5,0,16,0,1,6,0,52,4,0,3,52,3,0,1,50],"constants":[{"t":"s","v":"has-key?"},{"t":"s","v":"*store-registry*"},{"t":"s","v":"get"},{"t":"s","v":"error"},{"t":"s","v":"str"},{"t":"s","v":"Store not found: "},{"t":"s","v":". Call (def-store ...) before (use-store ...)."}]}},{"t":"s","v":"clear-stores"},{"t":"code","v":{"bytecode":[52,0,0,0,21,1,0,50],"constants":[{"t":"s","v":"dict"},{"t":"s","v":"*store-registry*"}]}},{"t":"s","v":"emit-event"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,16,2,49,3,50],"constants":[{"t":"s","v":"dom-dispatch"}]}},{"t":"s","v":"on-event"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,16,2,49,3,50],"constants":[{"t":"s","v":"dom-on"}]}},{"t":"s","v":"bridge-event"},{"t":"code","v":{"bytecode":[20,0,0,51,1,0,1,0,1,1,1,3,1,2,49,1,50],"constants":[{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,18,1,51,1,0,0,2,0,3,48,3,17,0,16,0,50],"constants":[{"t":"s","v":"dom-on"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,18,0,33,16,0,20,1,0,18,0,16,1,52,2,0,1,48,2,32,2,0,16,1,17,2,20,3,0,18,1,16,2,49,2,50],"constants":[{"t":"s","v":"event-detail"},{"t":"s","v":"cek-call"},{"t":"s","v":"list"},{"t":"s","v":"reset!"}]}}]}}]}},{"t":"s","v":"resource"},{"t":"code","v":{"bytecode":[20,0,0,1,2,0,3,1,3,0,2,1,4,0,2,52,1,0,6,48,1,17,1,20,5,0,20,6,0,16,0,2,48,2,51,7,0,1,1,51,8,0,1,1,48,3,5,16,1,50],"constants":[{"t":"s","v":"signal"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"},{"t":"s","v":"promise-then"},{"t":"s","v":"cek-call"},{"t":"code","v":{"bytecode":[20,0,0,18,0,1,2,0,4,1,3,0,16,0,1,4,0,2,52,1,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"}]}},{"t":"code","v":{"bytecode":[20,0,0,18,0,1,2,0,4,1,3,0,2,1,4,0,16,0,52,1,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"}]}}]}}]}}
{"magic":"SXBC","version":1,"hash":"1e908c466d2b8c22","module":{"bytecode":[51,1,0,128,0,0,5,51,3,0,128,2,0,5,52,5,0,0,128,4,0,5,51,7,0,128,6,0,5,51,9,0,128,8,0,5,51,11,0,128,10,0,5,51,13,0,128,12,0,5,51,15,0,128,14,0,5,51,17,0,128,16,0,5,51,19,0,128,18,0,50],"constants":[{"t":"s","v":"with-marsh-scope"},{"t":"code","v":{"bytecode":[52,0,0,0,17,2,20,1,0,51,2,0,1,2,16,1,48,2,5,20,3,0,16,0,1,4,0,16,2,49,3,50],"constants":[{"t":"s","v":"list"},{"t":"s","v":"with-island-scope"},{"t":"code","v":{"bytecode":[20,0,0,18,0,16,0,49,2,50],"constants":[{"t":"s","v":"append!"}],"arity":1,"upvalue-count":1}},{"t":"s","v":"dom-set-data"},{"t":"s","v":"sx-marsh-disposers"}],"arity":2}},{"t":"s","v":"dispose-marsh-scope"},{"t":"code","v":{"bytecode":[20,0,0,16,0,1,1,0,48,2,17,1,16,1,33,24,0,51,3,0,16,1,52,2,0,2,5,20,4,0,16,0,1,1,0,2,49,3,32,1,0,2,50],"constants":[{"t":"s","v":"dom-get-data"},{"t":"s","v":"sx-marsh-disposers"},{"t":"s","v":"for-each"},{"t":"code","v":{"bytecode":[20,0,0,16,0,2,49,2,50],"constants":[{"t":"s","v":"cek-call"}],"arity":1}},{"t":"s","v":"dom-set-data"}],"arity":1}},{"t":"s","v":"*store-registry*"},{"t":"s","v":"dict"},{"t":"s","v":"def-store"},{"t":"code","v":{"bytecode":[20,0,0,17,2,16,2,16,0,52,2,0,2,52,1,0,1,33,22,0,16,2,16,0,20,4,0,16,1,2,48,2,52,3,0,3,21,0,0,32,1,0,2,5,20,0,0,16,0,52,5,0,2,50],"constants":[{"t":"s","v":"*store-registry*"},{"t":"s","v":"not"},{"t":"s","v":"has-key?"},{"t":"s","v":"assoc"},{"t":"s","v":"cek-call"},{"t":"s","v":"get"}],"arity":2}},{"t":"s","v":"use-store"},{"t":"code","v":{"bytecode":[20,1,0,16,0,52,0,0,2,33,12,0,20,1,0,16,0,52,2,0,2,32,16,0,1,5,0,16,0,1,6,0,52,4,0,3,52,3,0,1,50],"constants":[{"t":"s","v":"has-key?"},{"t":"s","v":"*store-registry*"},{"t":"s","v":"get"},{"t":"s","v":"error"},{"t":"s","v":"str"},{"t":"s","v":"Store not found: "},{"t":"s","v":". Call (def-store ...) before (use-store ...)."}],"arity":1}},{"t":"s","v":"clear-stores"},{"t":"code","v":{"bytecode":[52,0,0,0,21,1,0,50],"constants":[{"t":"s","v":"dict"},{"t":"s","v":"*store-registry*"}]}},{"t":"s","v":"emit-event"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,16,2,49,3,50],"constants":[{"t":"s","v":"dom-dispatch"}],"arity":3}},{"t":"s","v":"on-event"},{"t":"code","v":{"bytecode":[20,0,0,16,0,16,1,16,2,49,3,50],"constants":[{"t":"s","v":"dom-on"}],"arity":3}},{"t":"s","v":"bridge-event"},{"t":"code","v":{"bytecode":[20,0,0,51,1,0,1,0,1,1,1,3,1,2,49,1,50],"constants":[{"t":"s","v":"effect"},{"t":"code","v":{"bytecode":[20,0,0,18,0,18,1,51,1,0,0,2,0,3,48,3,17,0,16,0,50],"constants":[{"t":"s","v":"dom-on"},{"t":"code","v":{"bytecode":[20,0,0,16,0,48,1,17,1,18,0,33,16,0,20,1,0,18,0,16,1,52,2,0,1,48,2,32,2,0,16,1,17,2,20,3,0,18,1,16,2,49,2,50],"constants":[{"t":"s","v":"event-detail"},{"t":"s","v":"cek-call"},{"t":"s","v":"list"},{"t":"s","v":"reset!"}],"arity":1,"upvalue-count":2}}],"upvalue-count":4}}],"arity":4}},{"t":"s","v":"resource"},{"t":"code","v":{"bytecode":[20,0,0,1,2,0,3,1,3,0,2,1,4,0,2,52,1,0,6,48,1,17,1,20,5,0,20,6,0,16,0,2,48,2,51,7,0,1,1,51,8,0,1,1,48,3,5,16,1,50],"constants":[{"t":"s","v":"signal"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"},{"t":"s","v":"promise-then"},{"t":"s","v":"cek-call"},{"t":"code","v":{"bytecode":[20,0,0,18,0,1,2,0,4,1,3,0,16,0,1,4,0,2,52,1,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"}],"arity":1,"upvalue-count":1}},{"t":"code","v":{"bytecode":[20,0,0,18,0,1,2,0,4,1,3,0,2,1,4,0,16,0,52,1,0,6,49,2,50],"constants":[{"t":"s","v":"reset!"},{"t":"s","v":"dict"},{"t":"s","v":"loading"},{"t":"s","v":"data"},{"t":"s","v":"error"}],"arity":1,"upvalue-count":1}}],"arity":1}}]}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1792,7 +1792,7 @@
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
}
(globalThis))
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-ea36a0db",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-6b9c6428",[2,3,5]],["std_exit-10fb8830",[2]],["start-29cf9a72",0]],"generated":(b=>{var
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-31fbd690",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-c7255f12",[2,3,5]],["std_exit-10fb8830",[2]],["start-29cf9a72",0]],"generated":(b=>{var
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
@@ -1818,4 +1818,4 @@ a()},"Js_of_ocaml__Json.fragments":{"get_JSON":a=>a.JSON,"get_constructor":a=>a.
a(b)},"Js_of_ocaml__Dom_svg.fragments":{"get_SVGElement":a=>a.SVGElement,"get_document":a=>a.document,"get_tagName":a=>a.tagName,"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_getElementById":(a,b)=>a.getElementById(b),"meth_call_2_createElementNS":(a,b,c)=>a.createElementNS(b,c)},"Js_of_ocaml__EventSource.fragments":{"get_EventSource":a=>a.EventSource,"obj_9":()=>({}),"set_withCredentials":(a,b)=>a.withCredentials=b},"Js_of_ocaml__Geolocation.fragments":{"get_geolocation":a=>a.geolocation,"get_navigator":a=>a.navigator,"obj_10":()=>({})},"Js_of_ocaml__IntersectionObserver.fragments":{"get_IntersectionObserver":a=>a.IntersectionObserver,"obj_11":()=>({})},"Js_of_ocaml__Intl.fragments":{"get_Collator":a=>a.Collator,"get_DateTimeFormat":a=>a.DateTimeFormat,"get_Intl":a=>a.Intl,"get_NumberFormat":a=>a.NumberFormat,"get_PluralRules":a=>a.PluralRules,"obj_12":a=>({localeMatcher:a}),"obj_13":(a,b,c,d,e,f)=>({localeMatcher:a,usage:b,sensitivity:c,ignorePunctuation:d,numeric:e,caseFirst:f}),"obj_14":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t)=>({dateStyle:a,timeStyle:b,calendar:c,dayPeriod:d,numberingSystem:e,localeMatcher:f,timeZone:g,hour12:h,hourCycle:i,formatMatcher:j,weekday:k,era:l,year:m,month:n,day:o,hour:p,minute:q,second:r,fractionalSecondDigits:s,timeZoneName:t}),"obj_15":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)=>({compactDisplay:a,currency:b,currencyDisplay:c,currencySign:d,localeMatcher:e,notation:f,numberingSystem:g,signDisplay:h,style:i,unit:j,unitDisplay:k,useGrouping:l,roundingMode:m,roundingPriority:n,roundingIncrement:o,trailingZeroDisplay:p,minimumIntegerDigits:q,minimumFractionDigits:r,maximumFractionDigits:s,minimumSignificantDigits:t,maximumSignificantDigits:u}),"obj_16":(a,b)=>({localeMatcher:a,type:b})},"Dune__exe__Sx_browser.fragments":{"fun_call_1":(a,b)=>a(b),"fun_call_3":(a,b,c,d)=>a(b,c,d),"get_Array":a=>a.Array,"get_Object":a=>a.Object,"get___sx_handle":a=>a.__sx_handle,"get__type":a=>a._type,"get_console":a=>a.console,"get_items":a=>a.items,"get_length":a=>a.length,"get_name":a=>a.name,"js_expr_10d25c5c":()=>function(a){return function(){b.__sxR=undefined;var
c=a.apply(null,arguments);return b.__sxR!==undefined?b.__sxR:c}},"js_expr_1ab4fffb":()=>function(){var
b={},d=0;return{put:function(a){var
c=d++;b[c]=a;return c},get:function(a){return b[a]}}}(),"js_expr_36506fc1":()=>function(a,b,c){a.__sx_handle=b;a._type=c;return a},"meth_call_1_error":(a,b)=>a.error(b),"meth_call_1_get":(a,b)=>a.get(b),"meth_call_1_isArray":(a,b)=>a.isArray(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_1_put":(a,b)=>a.put(b),"obj_0":()=>({}),"obj_1":()=>({}),"obj_2":(a,b)=>({_type:a,items:b}),"obj_3":(a,b)=>({_type:a,name:b}),"obj_4":(a,b)=>({_type:a,name:b}),"obj_5":(a,b)=>({_type:a,__sx_handle:b}),"set_SxKernel":(a,b)=>a.SxKernel=b,"set___sxR":(a,b)=>a.__sxR=b,"set__type":(a,b)=>a._type=b,"set_beginModuleLoad":(a,b)=>a.beginModuleLoad=b,"set_callFn":(a,b)=>a.callFn=b,"set_compileModule":(a,b)=>a.compileModule=b,"set_endModuleLoad":(a,b)=>a.endModuleLoad=b,"set_engine":(a,b)=>a.engine=b,"set_eval":(a,b)=>a.eval=b,"set_evalExpr":(a,b)=>a.evalExpr=b,"set_fnArity":(a,b)=>a.fnArity=b,"set_inspect":(a,b)=>a.inspect=b,"set_isCallable":(a,b)=>a.isCallable=b,"set_load":(a,b)=>a.load=b,"set_loadModule":(a,b)=>a.loadModule=b,"set_loadSource":(a,b)=>a.loadSource=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_stringify":(a,b)=>a.stringify=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx_browser.bc.wasm.assets"});
c=d++;b[c]=a;return c},get:function(a){return b[a]}}}(),"js_expr_36506fc1":()=>function(a,b,c){a.__sx_handle=b;a._type=c;return a},"meth_call_1_error":(a,b)=>a.error(b),"meth_call_1_get":(a,b)=>a.get(b),"meth_call_1_isArray":(a,b)=>a.isArray(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_1_put":(a,b)=>a.put(b),"obj_0":()=>({}),"obj_1":()=>({}),"obj_2":(a,b)=>({_type:a,items:b}),"obj_3":(a,b)=>({_type:a,name:b}),"obj_4":(a,b)=>({_type:a,name:b}),"obj_5":(a,b)=>({_type:a,__sx_handle:b}),"set_SxKernel":(a,b)=>a.SxKernel=b,"set___sxR":(a,b)=>a.__sxR=b,"set__type":(a,b)=>a._type=b,"set_beginModuleLoad":(a,b)=>a.beginModuleLoad=b,"set_callFn":(a,b)=>a.callFn=b,"set_compileModule":(a,b)=>a.compileModule=b,"set_debugEnv":(a,b)=>a.debugEnv=b,"set_endModuleLoad":(a,b)=>a.endModuleLoad=b,"set_engine":(a,b)=>a.engine=b,"set_eval":(a,b)=>a.eval=b,"set_evalExpr":(a,b)=>a.evalExpr=b,"set_fnArity":(a,b)=>a.fnArity=b,"set_inspect":(a,b)=>a.inspect=b,"set_isCallable":(a,b)=>a.isCallable=b,"set_load":(a,b)=>a.load=b,"set_loadModule":(a,b)=>a.loadModule=b,"set_loadSource":(a,b)=>a.loadSource=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_stringify":(a,b)=>a.stringify=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx_browser.bc.wasm.assets"});

View File

@@ -1,45 +1,434 @@
(define HEAD_HOIST_SELECTOR "meta, title, link[rel='canonical'], script[type='application/ld+json']")
(define
HEAD_HOIST_SELECTOR
"meta, title, link[rel='canonical'], script[type='application/ld+json']")
(define hoist-head-elements-full :effects (mutation io) (fn (root) (let ((els (dom-query-all root HEAD_HOIST_SELECTOR))) (for-each (fn (el) (let ((tag (lower (dom-tag-name el)))) (cond (= tag "title") (do (set-document-title (dom-text-content el)) (dom-remove-child (dom-parent el) el)) (= tag "meta") (do (let ((name (dom-get-attr el "name")) (prop (dom-get-attr el "property"))) (when name (remove-head-element (str "meta[name=\"" name "\"]"))) (when prop (remove-head-element (str "meta[property=\"" prop "\"]")))) (dom-remove-child (dom-parent el) el) (dom-append-to-head el)) (and (= tag "link") (= (dom-get-attr el "rel") "canonical")) (do (remove-head-element "link[rel=\"canonical\"]") (dom-remove-child (dom-parent el) el) (dom-append-to-head el)) :else (do (dom-remove-child (dom-parent el) el) (dom-append-to-head el))))) els))))
(define
hoist-head-elements-full
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all root HEAD_HOIST_SELECTOR)))
(for-each
(fn
(el)
(let
((tag (lower (dom-tag-name el))))
(cond
(= tag "title")
(do
(set-document-title (dom-text-content el))
(dom-remove-child (dom-parent el) el))
(= tag "meta")
(do
(let
((name (dom-get-attr el "name"))
(prop (dom-get-attr el "property")))
(when
name
(remove-head-element (str "meta[name=\"" name "\"]")))
(when
prop
(remove-head-element (str "meta[property=\"" prop "\"]"))))
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el))
(and (= tag "link") (= (dom-get-attr el "rel") "canonical"))
(do
(remove-head-element "link[rel=\"canonical\"]")
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el))
:else (do
(dom-remove-child (dom-parent el) el)
(dom-append-to-head el)))))
els))))
(define sx-mount :effects (mutation io) (fn (target (source :as string) (extra-env :as dict)) (let ((el (resolve-mount-target target))) (when el (when (empty? (dom-child-list el)) (let ((node (sx-render-with-env source extra-env))) (dom-set-text-content el "") (dom-append el node) (hoist-head-elements-full el))) (process-elements el) (sx-hydrate-elements el) (sx-hydrate-islands el) (run-post-render-hooks)))))
(define
sx-mount
:effects (mutation io)
(fn
(target (source :as string) (extra-env :as dict))
(let
((el (resolve-mount-target target)))
(when
el
(when
(empty? (dom-child-list el))
(let
((node (sx-render-with-env source extra-env)))
(dom-set-text-content el "")
(dom-append el node)
(hoist-head-elements-full el)))
(process-elements el)
(sx-hydrate-elements el)
(sx-hydrate-islands el)
(run-post-render-hooks)))))
(define resolve-suspense :effects (mutation io) (fn ((id :as string) (sx :as string)) (process-sx-scripts nil) (let ((el (dom-query (str "[data-suspense=\"" id "\"]")))) (if el (do (let ((exprs (parse sx)) (env (get-render-env nil))) (dom-set-text-content el "") (for-each (fn (expr) (dom-append el (render-to-dom expr env nil))) exprs) (process-elements el) (sx-hydrate-elements el) (sx-hydrate-islands el) (run-post-render-hooks) (dom-dispatch el "sx:resolved" {:id id}))) (log-warn (str "resolveSuspense: no element for id=" id))))))
(define
resolve-suspense
:effects (mutation io)
(fn
((id :as string) (sx :as string))
(process-sx-scripts nil)
(let
((el (dom-query (str "[data-suspense=\"" id "\"]"))))
(if
el
(do
(let
((exprs (parse sx)) (env (get-render-env nil)))
(dom-set-text-content el "")
(for-each
(fn (expr) (dom-append el (render-to-dom expr env nil)))
exprs)
(process-elements el)
(sx-hydrate-elements el)
(sx-hydrate-islands el)
(run-post-render-hooks)
(dom-dispatch el "sx:resolved" {:id id})))
(log-warn (str "resolveSuspense: no element for id=" id))))))
(define sx-hydrate-elements :effects (mutation io) (fn (root) (let ((els (dom-query-all (or root (dom-body)) "[data-sx]"))) (for-each (fn (el) (when (not (is-processed? el "hydrated")) (mark-processed! el "hydrated") (sx-update-element el nil))) els))))
(define
sx-hydrate-elements
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all (or root (dom-body)) "[data-sx]")))
(for-each
(fn
(el)
(when
(not (is-processed? el "hydrated"))
(mark-processed! el "hydrated")
(sx-update-element el nil)))
els))))
(define sx-update-element :effects (mutation io) (fn (el new-env) (let ((target (resolve-mount-target el))) (when target (let ((source (dom-get-attr target "data-sx"))) (when source (let ((base-env (parse-env-attr target)) (env (merge-envs base-env new-env))) (let ((node (sx-render-with-env source env))) (dom-set-text-content target "") (dom-append target node) (when new-env (store-env-attr target base-env new-env))))))))))
(define
sx-update-element
:effects (mutation io)
(fn
(el new-env)
(let
((target (resolve-mount-target el)))
(when
target
(let
((source (dom-get-attr target "data-sx")))
(when
source
(let
((base-env (parse-env-attr target))
(env (merge-envs base-env new-env)))
(let
((node (sx-render-with-env source env)))
(dom-set-text-content target "")
(dom-append target node)
(when new-env (store-env-attr target base-env new-env))))))))))
(define sx-render-component :effects (mutation io) (fn ((name :as string) (kwargs :as dict) (extra-env :as dict)) (let ((full-name (if (starts-with? name "~") name (str "~" name)))) (let ((env (get-render-env extra-env)) (comp (env-get env full-name))) (if (not (component? comp)) (error (str "Unknown component: " full-name)) (let ((call-expr (list (make-symbol full-name)))) (for-each (fn ((k :as string)) (append! call-expr (make-keyword (to-kebab k))) (append! call-expr (dict-get kwargs k))) (keys kwargs)) (render-to-dom call-expr env nil)))))))
(define
sx-render-component
:effects (mutation io)
(fn
((name :as string) (kwargs :as dict) (extra-env :as dict))
(let
((full-name (if (starts-with? name "~") name (str "~" name))))
(let
((env (get-render-env extra-env)) (comp (env-get env full-name)))
(if
(not (component? comp))
(error (str "Unknown component: " full-name))
(let
((call-expr (list (make-symbol full-name))))
(for-each
(fn
((k :as string))
(append! call-expr (make-keyword (to-kebab k)))
(append! call-expr (dict-get kwargs k)))
(keys kwargs))
(render-to-dom call-expr env nil)))))))
(define process-sx-scripts :effects (mutation io) (fn (root) (let ((scripts (query-sx-scripts root))) (for-each (fn (s) (when (not (is-processed? s "script")) (mark-processed! s "script") (let ((text (dom-text-content s))) (cond (dom-has-attr? s "data-components") (process-component-script s text) (or (nil? text) (empty? (trim text))) nil (dom-has-attr? s "data-init") (let ((exprs (sx-parse text))) (for-each (fn (expr) (eval-expr expr (env-extend (dict)))) exprs)) (dom-has-attr? s "data-mount") (let ((mount-sel (dom-get-attr s "data-mount")) (target (dom-query mount-sel))) (when target (sx-mount target text nil))) :else (sx-load-components text))))) scripts))))
(define
process-sx-scripts
:effects (mutation io)
(fn
(root)
(let
((scripts (query-sx-scripts root)))
(for-each
(fn
(s)
(when
(not (is-processed? s "script"))
(mark-processed! s "script")
(let
((text (dom-text-content s)))
(cond
(dom-has-attr? s "data-components")
(process-component-script s text)
(or (nil? text) (empty? (trim text)))
nil
(dom-has-attr? s "data-init")
(let
((exprs (sx-parse text)))
(for-each (fn (expr) (cek-eval expr)) exprs))
(dom-has-attr? s "data-mount")
(let
((mount-sel (dom-get-attr s "data-mount"))
(target (dom-query mount-sel)))
(when target (sx-mount target text nil)))
:else (sx-load-components text)))))
scripts))))
(define process-component-script :effects (mutation io) (fn (script (text :as string)) (let ((hash (dom-get-attr script "data-hash"))) (if (nil? hash) (when (and text (not (empty? (trim text)))) (sx-load-components text)) (let ((has-inline (and text (not (empty? (trim text)))))) (let ((cached-hash (local-storage-get "sx-components-hash"))) (if (= cached-hash hash) (if has-inline (do (local-storage-set "sx-components-hash" hash) (local-storage-set "sx-components-src" text) (sx-load-components text) (log-info "components: downloaded (cookie stale)")) (let ((cached (local-storage-get "sx-components-src"))) (if cached (do (sx-load-components cached) (log-info (str "components: cached (" hash ")"))) (do (clear-sx-comp-cookie) (browser-reload))))) (if has-inline (do (local-storage-set "sx-components-hash" hash) (local-storage-set "sx-components-src" text) (sx-load-components text) (log-info (str "components: downloaded (" hash ")"))) (do (local-storage-remove "sx-components-hash") (local-storage-remove "sx-components-src") (clear-sx-comp-cookie) (browser-reload))))) (set-sx-comp-cookie hash))))))
(define
process-component-script
:effects (mutation io)
(fn
(script (text :as string))
(let
((hash (dom-get-attr script "data-hash")))
(if
(nil? hash)
(when
(and text (not (empty? (trim text))))
(sx-load-components text))
(let
((has-inline (and text (not (empty? (trim text))))))
(let
((cached-hash (local-storage-get "sx-components-hash")))
(if
(= cached-hash hash)
(if
has-inline
(do
(local-storage-set "sx-components-hash" hash)
(local-storage-set "sx-components-src" text)
(sx-load-components text)
(log-info "components: downloaded (cookie stale)"))
(let
((cached (local-storage-get "sx-components-src")))
(if
cached
(do
(sx-load-components cached)
(log-info (str "components: cached (" hash ")")))
(do (clear-sx-comp-cookie) (browser-reload)))))
(if
has-inline
(do
(local-storage-set "sx-components-hash" hash)
(local-storage-set "sx-components-src" text)
(sx-load-components text)
(log-info (str "components: downloaded (" hash ")")))
(do
(local-storage-remove "sx-components-hash")
(local-storage-remove "sx-components-src")
(clear-sx-comp-cookie)
(browser-reload)))))
(set-sx-comp-cookie hash))))))
(define _page-routes (list))
(define process-page-scripts :effects (mutation io) (fn () (let ((scripts (query-page-scripts))) (log-info (str "pages: found " (len scripts) " script tags")) (for-each (fn (s) (when (not (is-processed? s "pages")) (mark-processed! s "pages") (let ((text (dom-text-content s))) (log-info (str "pages: script text length=" (if text (len text) 0))) (if (and text (not (empty? (trim text)))) (let ((pages (parse text))) (log-info (str "pages: parsed " (len pages) " entries")) (for-each (fn ((page :as dict)) (append! _page-routes (merge page {:parsed (parse-route-pattern (get page "path"))}))) pages)) (log-warn "pages: script tag is empty"))))) scripts) (log-info (str "pages: " (len _page-routes) " routes loaded")))))
(define
process-page-scripts
:effects (mutation io)
(fn
()
(let
((scripts (query-page-scripts)))
(log-info (str "pages: found " (len scripts) " script tags"))
(for-each
(fn
(s)
(when
(not (is-processed? s "pages"))
(mark-processed! s "pages")
(let
((text (dom-text-content s)))
(log-info
(str "pages: script text length=" (if text (len text) 0)))
(if
(and text (not (empty? (trim text))))
(let
((pages (parse text)))
(log-info (str "pages: parsed " (len pages) " entries"))
(for-each
(fn
((page :as dict))
(append! _page-routes (merge page {:parsed (parse-route-pattern (get page "path"))})))
pages))
(log-warn "pages: script tag is empty")))))
scripts)
(log-info (str "pages: " (len _page-routes) " routes loaded")))))
(define sx-hydrate-islands :effects (mutation io) (fn (root) (let ((els (dom-query-all (or root (dom-body)) "[data-sx-island]"))) (log-info (str "sx-hydrate-islands: " (len els) " island(s) in " (if root "subtree" "document"))) (for-each (fn (el) (if (is-processed? el "island-hydrated") (log-info (str " skip (already hydrated): " (dom-get-attr el "data-sx-island"))) (do (log-info (str " hydrating: " (dom-get-attr el "data-sx-island"))) (mark-processed! el "island-hydrated") (hydrate-island el)))) els))))
(define
sx-hydrate-islands
:effects (mutation io)
(fn
(root)
(let
((els (dom-query-all (or root (dom-body)) "[data-sx-island]")))
(log-info
(str
"sx-hydrate-islands: "
(len els)
" island(s) in "
(if root "subtree" "document")))
(for-each
(fn
(el)
(if
(is-processed? el "island-hydrated")
(log-info
(str
" skip (already hydrated): "
(dom-get-attr el "data-sx-island")))
(do
(log-info
(str " hydrating: " (dom-get-attr el "data-sx-island")))
(mark-processed! el "island-hydrated")
(hydrate-island el))))
els))))
(define hydrate-island :effects (mutation io) (fn (el) (let ((name (dom-get-attr el "data-sx-island")) (state-sx (or (dom-get-attr el "data-sx-state") "{}"))) (let ((comp-name (str "~" name)) (env (get-render-env nil))) (let ((comp (env-get env comp-name))) (if (not (or (component? comp) (island? comp))) (log-warn (str "hydrate-island: unknown island " comp-name)) (let ((kwargs (or (first (sx-parse state-sx)) {})) (disposers (list)) (local (env-merge (component-closure comp) env))) (for-each (fn ((p :as string)) (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (component-params comp)) (let ((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el))))) (dom-set-text-content el "") (dom-append el body-dom) (dom-set-data el "sx-disposers" disposers) (process-elements el) (log-info (str "hydrated island: " comp-name " (" (len disposers) " disposers)"))))))))))
(define
hydrate-island
:effects (mutation io)
(fn
(el)
(let
((name (dom-get-attr el "data-sx-island"))
(state-sx (or (dom-get-attr el "data-sx-state") "{}")))
(let
((comp-name (str "~" name)) (env (get-render-env nil)))
(let
((comp (env-get env comp-name)))
(if
(not (or (component? comp) (island? comp)))
(log-warn (str "hydrate-island: unknown island " comp-name))
(let
((kwargs (or (first (sx-parse state-sx)) {}))
(disposers (list))
(local (env-merge (component-closure comp) env)))
(for-each
(fn
((p :as string))
(env-bind!
local
p
(if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp))
(let
((body-dom (cek-try (fn () (with-island-scope (fn (disposable) (append! disposers disposable)) (fn () (render-to-dom (component-body comp) local nil)))) (fn (err) (log-warn (str "hydrate-island FAILED: " comp-name " — " err)) (let ((error-el (dom-create-element "div" nil))) (dom-set-attr error-el "class" "sx-island-error") (dom-set-attr error-el "style" "padding:8px;margin:4px 0;border:1px solid #ef4444;border-radius:4px;background:#fef2f2;color:#b91c1c;font-family:monospace;font-size:12px;white-space:pre-wrap") (dom-set-text-content error-el (str "Island error: " comp-name "\n" err)) error-el)))))
(dom-set-text-content el "")
(dom-append el body-dom)
(dom-set-data el "sx-disposers" disposers)
(process-elements el)
(log-info
(str
"hydrated island: "
comp-name
" ("
(len disposers)
" disposers)"))))))))))
(define dispose-island :effects (mutation io) (fn (el) (let ((disposers (dom-get-data el "sx-disposers"))) (when disposers (for-each (fn ((d :as lambda)) (when (callable? d) (d))) disposers) (dom-set-data el "sx-disposers" nil))) (clear-processed! el "island-hydrated")))
(define
dispose-island
:effects (mutation io)
(fn
(el)
(let
((disposers (dom-get-data el "sx-disposers")))
(when
disposers
(for-each
(fn ((d :as lambda)) (when (callable? d) (d)))
disposers)
(dom-set-data el "sx-disposers" nil)))
(clear-processed! el "island-hydrated")))
(define dispose-islands-in :effects (mutation io) (fn (root) (when root (let ((islands (dom-query-all root "[data-sx-island]"))) (when (and islands (not (empty? islands))) (let ((to-dispose (filter (fn (el) (not (is-processed? el "island-hydrated"))) islands))) (when (not (empty? to-dispose)) (log-info (str "disposing " (len to-dispose) " island(s)")) (for-each dispose-island to-dispose))))))))
(define
dispose-islands-in
:effects (mutation io)
(fn
(root)
(when
root
(let
((islands (dom-query-all root "[data-sx-island]")))
(when
(and islands (not (empty? islands)))
(let
((to-dispose (filter (fn (el) (not (is-processed? el "island-hydrated"))) islands)))
(when
(not (empty? to-dispose))
(log-info (str "disposing " (len to-dispose) " island(s)"))
(for-each dispose-island to-dispose))))))))
(define force-dispose-islands-in :effects (mutation io) (fn (root) (when root (let ((islands (dom-query-all root "[data-sx-island]"))) (when (and islands (not (empty? islands))) (log-info (str "force-disposing " (len islands) " island(s)")) (for-each dispose-island islands))))))
(define
force-dispose-islands-in
:effects (mutation io)
(fn
(root)
(when
root
(let
((islands (dom-query-all root "[data-sx-island]")))
(when
(and islands (not (empty? islands)))
(log-info (str "force-disposing " (len islands) " island(s)"))
(for-each dispose-island islands))))))
(define *pre-render-hooks* (list))
(define *post-render-hooks* (list))
(define register-pre-render-hook :effects (mutation) (fn ((hook-fn :as lambda)) (append! *pre-render-hooks* hook-fn)))
(define
register-pre-render-hook
:effects (mutation)
(fn ((hook-fn :as lambda)) (append! *pre-render-hooks* hook-fn)))
(define register-post-render-hook :effects (mutation) (fn ((hook-fn :as lambda)) (append! *post-render-hooks* hook-fn)))
(define
register-post-render-hook
:effects (mutation)
(fn ((hook-fn :as lambda)) (append! *post-render-hooks* hook-fn)))
(define run-pre-render-hooks :effects (mutation io) (fn () (for-each (fn (hook) (cek-call hook nil)) *pre-render-hooks*)))
(define
run-pre-render-hooks
:effects (mutation io)
(fn () (for-each (fn (hook) (cek-call hook nil)) *pre-render-hooks*)))
(define run-post-render-hooks :effects (mutation io) (fn () (log-info (str "run-post-render-hooks: " (len *post-render-hooks*) " hooks")) (for-each (fn (hook) (log-info (str " hook type: " (type-of hook) " callable: " (callable? hook) " lambda: " (lambda? hook))) (cek-call hook nil)) *post-render-hooks*)))
(define
run-post-render-hooks
:effects (mutation io)
(fn
()
(log-info
(str "run-post-render-hooks: " (len *post-render-hooks*) " hooks"))
(for-each
(fn
(hook)
(log-info
(str
" hook type: "
(type-of hook)
" callable: "
(callable? hook)
" lambda: "
(lambda? hook)))
(cek-call hook nil))
*post-render-hooks*)))
(define boot-init :effects (mutation io) (fn () (do (log-info (str "sx-browser " SX_VERSION)) (init-css-tracking) (process-page-scripts) (process-sx-scripts nil) (sx-hydrate-elements nil) (sx-hydrate-islands nil) (run-post-render-hooks) (process-elements nil) (dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0))))))
(define
boot-init
:effects (mutation io)
(fn
()
(do
(log-info (str "sx-browser " SX_VERSION))
(init-css-tracking)
(process-page-scripts)
(process-sx-scripts nil)
(sx-hydrate-elements nil)
(sx-hydrate-islands nil)
(run-post-render-hooks)
(process-elements nil)
(dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0))))))

View File

@@ -1,425 +1,418 @@
;; ==========================================================================
;; dom.sx — DOM library functions
;;
;; All DOM operations expressed using the host FFI primitives:
;; host-get — read property from host object
;; host-set! — write property on host object
;; host-call — call method on host object
;; host-new — construct host object
;; host-global — access global (window/document/etc.)
;; host-callback — wrap SX function as host callback
;; host-typeof — check host object type
;;
;; These are LIBRARY FUNCTIONS — portable, auditable, in-band SX.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; Globals
;; --------------------------------------------------------------------------
(define dom-document (fn () (host-global "document")))
(define dom-window (fn () (host-global "window")))
(define dom-body (fn () (host-get (dom-document) "body")))
(define dom-head (fn () (host-get (dom-document) "head")))
(define dom-window (fn () (host-global "window")))
;; --------------------------------------------------------------------------
;; Node creation
;; --------------------------------------------------------------------------
(define dom-body (fn () (host-get (dom-document) "body")))
(define dom-create-element
(fn (tag &rest ns-arg)
(let ((ns (if (and ns-arg (not (empty? ns-arg))) (first ns-arg) nil)))
(if ns
(define dom-head (fn () (host-get (dom-document) "head")))
(define
dom-create-element
(fn
(tag &rest ns-arg)
(let
((ns (if (and ns-arg (not (empty? ns-arg))) (first ns-arg) nil)))
(if
ns
(host-call (dom-document) "createElementNS" ns tag)
(host-call (dom-document) "createElement" tag)))))
(define create-text-node
(fn (s)
(host-call (dom-document) "createTextNode" s)))
(define
create-text-node
(fn (s) (host-call (dom-document) "createTextNode" s)))
(define create-fragment
(fn ()
(host-call (dom-document) "createDocumentFragment")))
(define
create-fragment
(fn () (host-call (dom-document) "createDocumentFragment")))
(define create-comment
(fn (text)
(host-call (dom-document) "createComment" (or text ""))))
(define
create-comment
(fn (text) (host-call (dom-document) "createComment" (or text ""))))
(define
dom-append
(fn
(parent child)
(when (and parent child) (host-call parent "appendChild" child))))
;; --------------------------------------------------------------------------
;; Tree manipulation
;; --------------------------------------------------------------------------
(define
dom-prepend
(fn
(parent child)
(when (and parent child) (host-call parent "prepend" child))))
(define dom-append
(fn (parent child)
(when (and parent child)
(host-call parent "appendChild" child))))
(define
dom-insert-before
(fn
(parent child ref)
(when (and parent child) (host-call parent "insertBefore" child ref))))
(define dom-prepend
(fn (parent child)
(when (and parent child)
(host-call parent "prepend" child))))
(define dom-insert-before
(fn (parent child ref)
(when (and parent child)
(host-call parent "insertBefore" child ref))))
(define dom-insert-after
(fn (ref node)
(define
dom-insert-after
(fn
(ref node)
"Insert node after ref in the same parent."
(let ((parent (host-get ref "parentNode"))
(next (host-get ref "nextSibling")))
(when parent
(if next
(let
((parent (host-get ref "parentNode"))
(next (host-get ref "nextSibling")))
(when
parent
(if
next
(host-call parent "insertBefore" node next)
(host-call parent "appendChild" node))))))
(define dom-remove
(fn (el)
(when el (host-call el "remove"))))
(define dom-remove (fn (el) (when el (host-call el "remove"))))
(define dom-is-active-element?
(fn (el)
(let ((active (host-get (dom-document) "activeElement")))
(if (and active el)
(identical? el active)
false))))
(define
dom-is-active-element?
(fn
(el)
(let
((active (host-get (dom-document) "activeElement")))
(if (and active el) (identical? el active) false))))
(define dom-is-input-element?
(fn (el)
(let ((tag (upper (or (dom-tag-name el) ""))))
(define
dom-is-input-element?
(fn
(el)
(let
((tag (upper (or (dom-tag-name el) ""))))
(or (= tag "INPUT") (= tag "TEXTAREA") (= tag "SELECT")))))
(define dom-is-child-of?
(fn (child parent)
(and child parent (host-call parent "contains" child))))
(define
dom-is-child-of?
(fn (child parent) (and child parent (host-call parent "contains" child))))
(define dom-attr-list
(fn (el)
;; Return list of (name value) pairs for all attributes on the element.
(let ((attrs (host-get el "attributes"))
(result (list)))
(when attrs
(let ((n (host-get attrs "length")))
(let loop ((i 0))
(when (< i n)
(let ((attr (host-call attrs "item" i)))
(append! result (list (host-get attr "name") (host-get attr "value"))))
(define
dom-attr-list
(fn
(el)
(let
((attrs (host-get el "attributes")) (result (list)))
(when
attrs
(let
((n (host-get attrs "length")))
(let
loop
((i 0))
(when
(< i n)
(let
((attr (host-call attrs "item" i)))
(append!
result
(list (host-get attr "name") (host-get attr "value"))))
(loop (+ i 1))))))
result)))
(define dom-remove-child
(fn (parent child)
(when (and parent child)
(host-call parent "removeChild" child))))
(define
dom-remove-child
(fn
(parent child)
(when (and parent child) (host-call parent "removeChild" child))))
(define dom-replace-child
(fn (parent new-child old-child)
(when (and parent new-child old-child)
(define
dom-replace-child
(fn
(parent new-child old-child)
(when
(and parent new-child old-child)
(host-call parent "replaceChild" new-child old-child))))
(define dom-clone
(fn (node deep)
(host-call node "cloneNode" (if (nil? deep) true deep))))
(define
dom-clone
(fn (node deep) (host-call node "cloneNode" (if (nil? deep) true deep))))
;; --------------------------------------------------------------------------
;; Queries
;; --------------------------------------------------------------------------
(define dom-query
(fn (root-or-sel &rest rest)
(if (empty? rest)
;; Single arg: selector on document
(define
dom-query
(fn
(root-or-sel &rest rest)
(if
(empty? rest)
(host-call (dom-document) "querySelector" root-or-sel)
;; Two args: root element + selector
(host-call root-or-sel "querySelector" (first rest)))))
(define dom-query-all
(fn (root sel)
(define
dom-query-all
(fn
(root sel)
"Query DOM and return an SX list (not a host NodeList)."
(let ((node-list (if (nil? sel)
(host-call (dom-document) "querySelectorAll" root)
(host-call root "querySelectorAll" sel))))
;; Convert NodeList → SX list by indexing
(if (nil? node-list)
(let
((node-list (if (nil? sel) (host-call (dom-document) "querySelectorAll" root) (host-call root "querySelectorAll" sel))))
(if
(nil? node-list)
(list)
(let ((n (host-get node-list "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(let
((n (host-get node-list "length")) (result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call node-list "item" i))
(loop (+ i 1))))
result)))))
(define dom-query-by-id
(fn (id)
(host-call (dom-document) "getElementById" id)))
(define
dom-query-by-id
(fn (id) (host-call (dom-document) "getElementById" id)))
(define dom-closest
(fn (el sel)
(when el (host-call el "closest" sel))))
(define dom-closest (fn (el sel) (when el (host-call el "closest" sel))))
(define dom-matches?
(fn (el sel)
(if (and el (host-get el "matches"))
(host-call el "matches" sel)
false)))
(define
dom-matches?
(fn
(el sel)
(if (and el (host-get el "matches")) (host-call el "matches" sel) false)))
;; --------------------------------------------------------------------------
;; Attributes
;; --------------------------------------------------------------------------
(define dom-get-attr
(fn (el name)
(if (and el (host-get el "getAttribute"))
(let ((v (host-call el "getAttribute" name)))
(if (nil? v) nil v))
(define
dom-get-attr
(fn
(el name)
(if
(and el (host-get el "getAttribute"))
(let ((v (host-call el "getAttribute" name))) (if (nil? v) nil v))
nil)))
(define dom-set-attr
(fn (el name val)
(when (and el (host-get el "setAttribute"))
(define
dom-set-attr
(fn
(el name val)
(when
(and el (host-get el "setAttribute"))
(host-call el "setAttribute" name val))))
(define dom-remove-attr
(fn (el name)
(when (and el (host-get el "removeAttribute"))
(define
dom-remove-attr
(fn
(el name)
(when
(and el (host-get el "removeAttribute"))
(host-call el "removeAttribute" name))))
(define dom-has-attr?
(fn (el name)
(if (and el (host-get el "hasAttribute"))
(define
dom-has-attr?
(fn
(el name)
(if
(and el (host-get el "hasAttribute"))
(host-call el "hasAttribute" name)
false)))
(define
dom-add-class
(fn (el cls) (when el (host-call (host-get el "classList") "add" cls))))
;; --------------------------------------------------------------------------
;; Classes
;; --------------------------------------------------------------------------
(define
dom-remove-class
(fn
(el cls)
(when el (host-call (host-get el "classList") "remove" cls))))
(define dom-add-class
(fn (el cls)
(when el
(host-call (host-get el "classList") "add" cls))))
(define
dom-has-class?
(fn
(el cls)
(if el (host-call (host-get el "classList") "contains" cls) false)))
(define dom-remove-class
(fn (el cls)
(when el
(host-call (host-get el "classList") "remove" cls))))
(define dom-text-content (fn (el) (host-get el "textContent")))
(define dom-has-class?
(fn (el cls)
(if el
(host-call (host-get el "classList") "contains" cls)
false)))
(define dom-set-text-content (fn (el val) (host-set! el "textContent" val)))
(define dom-inner-html (fn (el) (host-get el "innerHTML")))
;; --------------------------------------------------------------------------
;; Content
;; --------------------------------------------------------------------------
(define dom-set-inner-html (fn (el val) (host-set! el "innerHTML" val)))
(define dom-text-content
(fn (el) (host-get el "textContent")))
(define dom-outer-html (fn (el) (host-get el "outerHTML")))
(define dom-set-text-content
(fn (el val) (host-set! el "textContent" val)))
(define
dom-insert-adjacent-html
(fn (el position html) (host-call el "insertAdjacentHTML" position html)))
(define dom-inner-html
(fn (el) (host-get el "innerHTML")))
(define dom-get-style (fn (el prop) (host-get (host-get el "style") prop)))
(define dom-set-inner-html
(fn (el val) (host-set! el "innerHTML" val)))
(define dom-outer-html
(fn (el) (host-get el "outerHTML")))
(define dom-insert-adjacent-html
(fn (el position html)
(host-call el "insertAdjacentHTML" position html)))
;; --------------------------------------------------------------------------
;; Style & properties
;; --------------------------------------------------------------------------
(define dom-get-style
(fn (el prop)
(host-get (host-get el "style") prop)))
(define dom-set-style
(fn (el prop val)
(define
dom-set-style
(fn
(el prop val)
(host-call (host-get el "style") "setProperty" prop val)))
(define dom-get-prop
(fn (el name) (host-get el name)))
(define dom-get-prop (fn (el name) (host-get el name)))
(define dom-set-prop
(fn (el name val) (host-set! el name val)))
(define dom-set-prop (fn (el name val) (host-set! el name val)))
(define
dom-tag-name
(fn (el) (if el (lower (or (host-get el "tagName") "")) "")))
;; --------------------------------------------------------------------------
;; Node info
;; --------------------------------------------------------------------------
(define dom-node-type (fn (el) (host-get el "nodeType")))
(define dom-tag-name
(fn (el)
(if el (lower (or (host-get el "tagName") "")) "")))
(define dom-node-name (fn (el) (host-get el "nodeName")))
(define dom-node-type
(fn (el) (host-get el "nodeType")))
(define dom-id (fn (el) (host-get el "id")))
(define dom-node-name
(fn (el) (host-get el "nodeName")))
(define dom-parent (fn (el) (host-get el "parentNode")))
(define dom-id
(fn (el) (host-get el "id")))
(define dom-first-child (fn (el) (host-get el "firstChild")))
(define dom-parent
(fn (el) (host-get el "parentNode")))
(define dom-next-sibling (fn (el) (host-get el "nextSibling")))
(define dom-first-child
(fn (el) (host-get el "firstChild")))
(define dom-next-sibling
(fn (el) (host-get el "nextSibling")))
(define dom-child-list
(fn (el)
(define
dom-child-list
(fn
(el)
"Return child nodes as an SX list."
(if el
(let ((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(if
el
(let
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1))))
result)
(list))))
(define dom-is-fragment?
(fn (el) (= (host-get el "nodeType") 11)))
(define dom-is-fragment? (fn (el) (= (host-get el "nodeType") 11)))
(define dom-child-nodes
(fn (el)
(define
dom-child-nodes
(fn
(el)
"Return child nodes as an SX list."
(if el
(let ((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let loop ((i 0))
(when (< i n)
(if
el
(let
((nl (host-get el "childNodes"))
(n (host-get nl "length"))
(result (list)))
(let
loop
((i 0))
(when
(< i n)
(append! result (host-call nl "item" i))
(loop (+ i 1))))
result)
(list))))
(define dom-remove-children-after
(fn (marker)
(define
dom-remove-children-after
(fn
(marker)
"Remove all siblings after marker node."
(let ((parent (dom-parent marker)))
(when parent
(let loop ()
(let ((next (dom-next-sibling marker)))
(when next
(host-call parent "removeChild" next)
(loop))))))))
(let
((parent (dom-parent marker)))
(when
parent
(let
loop
()
(let
((next (dom-next-sibling marker)))
(when next (host-call parent "removeChild" next) (loop))))))))
(define dom-focus
(fn (el) (when el (host-call el "focus"))))
(define dom-focus (fn (el) (when el (host-call el "focus"))))
(define dom-parse-html
(fn (html)
(let ((parser (host-new "DOMParser"))
(doc (host-call parser "parseFromString" html "text/html")))
(define
dom-parse-html
(fn
(html)
(let
((parser (host-new "DOMParser"))
(doc (host-call parser "parseFromString" html "text/html")))
(host-get (host-get doc "body") "childNodes"))))
;; --------------------------------------------------------------------------
;; Events
;; --------------------------------------------------------------------------
(define dom-listen
(fn (el event-name handler)
(let ((cb (host-callback handler)))
(define
dom-listen
(fn
(el event-name handler)
(let
((cb (host-callback handler)))
(host-call el "addEventListener" event-name cb)
;; Return cleanup function
(fn () (host-call el "removeEventListener" event-name cb)))))
;; dom-add-listener — addEventListener with optional options
;; Used by orchestration.sx: (dom-add-listener el event handler opts)
(define dom-add-listener
(fn (el event-name handler &rest opts)
(let ((cb (host-callback handler)))
(if (and opts (not (empty? opts)))
(define
dom-add-listener
(fn
(el event-name handler &rest opts)
(let
((cb (host-callback handler)))
(if
(and opts (not (empty? opts)))
(host-call el "addEventListener" event-name cb (first opts))
(host-call el "addEventListener" event-name cb))
;; Return cleanup function
(fn () (host-call el "removeEventListener" event-name cb)))))
(define dom-dispatch
(fn (el event-name detail)
(let ((evt (host-new "CustomEvent" event-name
(dict "detail" detail "bubbles" true))))
(define
dom-dispatch
(fn
(el event-name detail)
(let
((evt (host-new "CustomEvent" event-name (dict "detail" detail "bubbles" true))))
(host-call el "dispatchEvent" evt))))
(define event-detail
(fn (evt) (host-get evt "detail")))
(define event-detail (fn (evt) (host-get evt "detail")))
(define prevent-default
(fn (e) (when e (host-call e "preventDefault"))))
(define prevent-default (fn (e) (when e (host-call e "preventDefault"))))
(define stop-propagation
(fn (e) (when e (host-call e "stopPropagation"))))
(define stop-propagation (fn (e) (when e (host-call e "stopPropagation"))))
(define event-modifier-key?
(fn (e)
(and e (or (host-get e "ctrlKey") (host-get e "metaKey")
(host-get e "shiftKey") (host-get e "altKey")))))
(define
event-modifier-key?
(fn
(e)
(and
e
(or
(host-get e "ctrlKey")
(host-get e "metaKey")
(host-get e "shiftKey")
(host-get e "altKey")))))
(define element-value
(fn (el)
(if (and el (not (nil? (host-get el "value"))))
(define
element-value
(fn
(el)
(if
(and el (not (nil? (host-get el "value"))))
(host-get el "value")
nil)))
(define error-message
(fn (e)
(if (and e (host-get e "message"))
(host-get e "message")
(str e))))
(define
error-message
(fn
(e)
(if (and e (host-get e "message")) (host-get e "message") (str e))))
;; --------------------------------------------------------------------------
;; DOM data storage
;; --------------------------------------------------------------------------
(define dom-get-data
(fn (el key)
(let ((store (host-get el "__sx_data")))
(define
dom-get-data
(fn
(el key)
(let
((store (host-get el "__sx_data")))
(if store (host-get store key) nil))))
(define dom-set-data
(fn (el key val)
(when (not (host-get el "__sx_data"))
(define
dom-set-data
(fn
(el key val)
(when
(not (host-get el "__sx_data"))
(host-set! el "__sx_data" (dict)))
(host-set! (host-get el "__sx_data") key val)))
(define
dom-append-to-head
(fn (el) (when (dom-head) (host-call (dom-head) "appendChild" el))))
;; --------------------------------------------------------------------------
;; Head manipulation
;; --------------------------------------------------------------------------
(define dom-append-to-head
(fn (el)
(when (dom-head)
(host-call (dom-head) "appendChild" el))))
(define set-document-title
(fn (title)
(host-set! (dom-document) "title" title)))
(define
set-document-title
(fn (title) (host-set! (dom-document) "title" title)))

File diff suppressed because it is too large Load Diff