Recompile all 26 .sxbc with define-library wrappers + fix eval/JIT

All 26 browser modules recompiled with define-library/import forms.
Compilation works without vm-compile-adapter (JIT pre-compilation
hangs with library wrappers in some JIT paths — skipped for now,
CEK compilation is ~34s total).

Key fixes:
- eval command: import-aware loop that handles define-library/import
  locally without touching the Python bridge pipe (avoids deadlock)
- compile-modules.js: skip vm-compile-adapter, bump timeout

2621/2621 OCaml tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 00:08:00 +00:00
parent ac772ac357
commit 7b4c918773
48 changed files with 8884 additions and 5899 deletions

View File

@@ -50,6 +50,13 @@ let rec deep_equal a b =
deep_equal
(match Hashtbl.find_opt a k with Some v -> v | None -> Nil)
(match Hashtbl.find_opt b k with Some v -> v | None -> Nil)) ka
| Record a, Record b ->
a.r_type.rt_uid = b.r_type.rt_uid &&
Array.length a.r_fields = Array.length b.r_fields &&
(let eq = ref true in
for i = 0 to Array.length a.r_fields - 1 do
if not (deep_equal a.r_fields.(i) b.r_fields.(i)) then eq := false
done; !eq)
| Lambda _, Lambda _ -> a == b (* identity *)
| NativeFn _, NativeFn _ -> a == b
| _ -> false

View File

@@ -1030,9 +1030,29 @@ let rec dispatch env cmd =
(try
let exprs = Sx_parser.parse_all src in
let result = List.fold_left (fun _acc expr ->
(* Use IO-aware eval to handle import suspensions *)
(* Use import-aware eval handles define-library/import locally
but does NOT send other IO to the Python bridge (would deadlock
on stdin which carries batch commands). *)
let state = Sx_ref.make_cek_state expr (Env env) (List []) in
cek_run_with_io state
let s = ref (Sx_ref.cek_step_loop state) in
while Sx_types.sx_truthy (Sx_ref.cek_suspended_p !s) do
let request = Sx_ref.cek_io_request !s in
let op = match request with
| Dict d -> (match Hashtbl.find_opt d "op" with Some (String o) -> o | _ -> "")
| _ -> "" in
let response = if op = "import" then begin
let lib_spec = Sx_runtime.get_val request (String "library") in
let key = Sx_ref.library_name_key lib_spec in
if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then Nil
else begin
(match resolve_library_path lib_spec with
| Some path -> load_library_file path | None -> ());
Nil
end
end else Nil (* non-import IO: resume with nil *) in
s := Sx_ref.cek_resume !s response
done;
Sx_ref.cek_value !s
) Nil exprs in
(* Use ok-raw with natural list serialization — no (list ...) wrapping.
This preserves the SX structure for Python to parse back. *)

View File

@@ -56,8 +56,10 @@ let script = '';
// Load compiler
script += `(epoch ${epoch++})\n(load "lib/compiler.sx")\n`;
// JIT pre-compile the compiler
script += `(epoch ${epoch++})\n(vm-compile-adapter)\n`;
// JIT pre-compile the compiler (skipped: vm-compile-adapter hangs with
// define-library wrappers in some lambda JIT paths. Compilation still
// works via CEK — just ~2x slower per file.)
// script += `(epoch ${epoch++})\n(vm-compile-adapter)\n`;
// Load all modules into env
for (const file of FILES) {

View File

@@ -200,6 +200,14 @@ let () =
(match Hashtbl.find_opt a "__host_handle", Hashtbl.find_opt b "__host_handle" with
| Some (Number ha), Some (Number hb) -> ha = hb
| _ -> false)
(* Records: same type + structurally equal fields *)
| Record a, Record b ->
a.r_type.rt_uid = b.r_type.rt_uid &&
Array.length a.r_fields = Array.length b.r_fields &&
(let eq = ref true in
for i = 0 to Array.length a.r_fields - 1 do
if not (safe_eq a.r_fields.(i) b.r_fields.(i)) then eq := false
done; !eq)
(* Lambda/Component/Island/Signal/NativeFn: physical only *)
| _ -> false
in
@@ -723,6 +731,7 @@ let () =
| [Island i] ->
String (Printf.sprintf "~%s" i.i_name)
| [Lambda _] -> String "<lambda>"
| [Record r] -> String (Printf.sprintf "#<%s>" r.r_type.rt_name)
| [a] -> String (inspect a) (* used for dedup keys in compiler *)
| _ -> raise (Eval_error "serialize: 1 arg"));
register "make-symbol" (fun args ->
@@ -912,6 +921,36 @@ let () =
match args with [Lambda _] -> Bool true | _ -> Bool false);
register "island?" (fun args ->
match args with [Island _] -> Bool true | _ -> Bool false);
(* R7RS records *)
register "record?" (fun args ->
match args with [v] -> record_p v | _ -> Bool false);
register "make-rtd" (fun args ->
match args with [name; fields; ctor_params] -> make_rtd name fields ctor_params
| _ -> raise (Eval_error "make-rtd: expected (name fields ctor-params)"));
register "make-record" (fun args ->
match args with [uid; arg_list] -> make_record uid arg_list
| _ -> raise (Eval_error "make-record: expected (uid args-list)"));
register "record-ref" (fun args ->
match args with [v; idx] -> record_ref v idx
| _ -> raise (Eval_error "record-ref: expected (record index)"));
register "record-set!" (fun args ->
match args with [v; idx; nv] -> record_set_b v idx nv
| _ -> raise (Eval_error "record-set!: expected (record index value)"));
register "record-type?" (fun args ->
match args with [v; uid] -> record_type_p v uid | _ -> Bool false);
register "make-record-constructor" (fun args ->
match args with [uid] -> make_record_constructor uid
| _ -> raise (Eval_error "make-record-constructor: expected (uid)"));
register "make-record-predicate" (fun args ->
match args with [uid] -> make_record_predicate uid
| _ -> raise (Eval_error "make-record-predicate: expected (uid)"));
register "make-record-accessor" (fun args ->
match args with [idx] -> make_record_accessor idx
| _ -> raise (Eval_error "make-record-accessor: expected (index)"));
register "make-record-mutator" (fun args ->
match args with [idx] -> make_record_mutator idx
| _ -> raise (Eval_error "make-record-mutator: expected (index)"));
register "is-else-clause?" (fun args ->
match args with
| [Keyword "else"] -> Bool true

File diff suppressed because one or more lines are too long

View File

@@ -67,6 +67,7 @@ and value =
| CekState of cek_state (** Optimized CEK machine state — avoids Dict allocation. *)
| CekFrame of cek_frame (** Optimized CEK continuation frame. *)
| VmClosure of vm_closure (** VM-compiled closure — callable within the VM without allocating a new VM. *)
| Record of record (** R7RS record — opaque, generative, field-indexed. *)
(** CEK machine state — record instead of Dict for performance.
5 fields × 55K steps/sec = 275K Hashtbl allocations/sec eliminated. *)
@@ -139,6 +140,21 @@ and signal = {
mutable s_deps : signal list;
}
(** R7RS record type descriptor — one per [define-record-type] call.
Stored in [rtd_table]; closures capture only the integer uid. *)
and record_type = {
rt_name : string; (** e.g., "point" *)
rt_uid : int; (** unique identity — generative *)
rt_fields : string array; (** field names in declaration order *)
rt_ctor_map : int array; (** ctor_map[i] = field index for ctor param i *)
}
(** R7RS record instance — opaque, accessed only through generated functions. *)
and record = {
r_type : record_type;
r_fields : value array; (** mutable via Array.set for record-set! *)
}
(** {1 Bytecode VM types}
Defined here (not in sx_vm.ml) because [vm_code.constants] references
@@ -180,6 +196,12 @@ exception Eval_error of string
exception Parse_error of string
(** {1 Record type descriptor table} *)
let rtd_table : (int, record_type) Hashtbl.t = Hashtbl.create 16
let rtd_counter = ref 0
(** {1 Environment operations} *)
let make_env () =
@@ -347,6 +369,7 @@ let type_of = function
| CekState _ -> "dict" (* CEK state behaves as a dict for type checks *)
| CekFrame _ -> "dict"
| VmClosure _ -> "function"
| Record r -> r.r_type.rt_name
let is_nil = function Nil -> true | _ -> false
let is_lambda = function Lambda _ -> true | _ -> false
@@ -359,6 +382,8 @@ let is_signal = function
| Dict d -> Hashtbl.mem d "__signal"
| _ -> false
let is_record = function Record _ -> true | _ -> false
let is_callable = function
| Lambda _ | NativeFn _ | Continuation (_, _) | CallccContinuation _ | VmClosure _ -> true
| _ -> false
@@ -470,6 +495,130 @@ let thunk_env = function
| v -> raise (Eval_error ("Expected thunk, got " ^ type_of v))
(** {1 Record operations} *)
let val_to_int = function
| Number n -> int_of_float n
| v -> raise (Eval_error ("Expected number, got " ^ type_of v))
(** [make_rtd name fields ctor_params] — create a record type descriptor.
Called as [make-rtd] from transpiled evaluator. Takes 3 separate args. *)
let make_rtd name fields ctor_params =
let uid = !rtd_counter in
incr rtd_counter;
let field_names = List.map value_to_string (match fields with List l -> l | _ -> []) in
let ctor_names = List.map value_to_string (match ctor_params with List l -> l | _ -> []) in
let field_arr = Array.of_list field_names in
let ctor_map = Array.of_list (List.map (fun cp ->
let rec find j = function
| [] -> raise (Eval_error (Printf.sprintf "make-rtd: ctor param %s not in fields" cp))
| f :: _ when f = cp -> j
| _ :: rest -> find (j + 1) rest
in find 0 field_names
) ctor_names) in
let rt = { rt_name = value_to_string name; rt_uid = uid; rt_fields = field_arr; rt_ctor_map = ctor_map } in
Hashtbl.add rtd_table uid rt;
Number (float_of_int uid)
(** [make_record uid_val args_list] — create a record from uid + args list.
2-arg direct call: (make-record rtd-uid ctor-args-list). *)
let make_record uid_val args_list =
let uid = val_to_int uid_val in
let ctor_args = match args_list with List l -> l | _ -> [] in
match Hashtbl.find_opt rtd_table uid with
| None -> raise (Eval_error "make-record: unknown rtd")
| Some rt ->
let n_ctor = Array.length rt.rt_ctor_map in
let n_args = List.length ctor_args in
if n_args <> n_ctor then
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d"
rt.rt_name n_ctor n_args));
let fields = Array.make (Array.length rt.rt_fields) Nil in
List.iteri (fun i arg ->
fields.(rt.rt_ctor_map.(i)) <- arg
) ctor_args;
Record { r_type = rt; r_fields = fields }
(** [record_ref v idx] — access field by index. 2-arg direct call. *)
let record_ref v idx =
match v with
| Record r ->
let i = val_to_int idx in
if i < 0 || i >= Array.length r.r_fields then
raise (Eval_error (Printf.sprintf "record-ref: index %d out of bounds for %s" i r.r_type.rt_name));
r.r_fields.(i)
| _ -> raise (Eval_error ("record-ref: not a record, got " ^ type_of v))
(** [record_set_b v idx new_val] — mutate field by index. 3-arg direct call.
Named record_set_b because transpiler mangles record-set! to record_set_b. *)
let record_set_b v idx new_val =
match v with
| Record r ->
let i = val_to_int idx in
if i < 0 || i >= Array.length r.r_fields then
raise (Eval_error (Printf.sprintf "record-set!: index %d out of bounds for %s" i r.r_type.rt_name));
r.r_fields.(i) <- new_val; Nil
| _ -> raise (Eval_error ("record-set!: not a record, got " ^ type_of v))
(** [record_type_p v uid_val] — type predicate. 2-arg direct call.
Named record_type_p because transpiler mangles record-type? to record_type_p. *)
let record_type_p v uid_val =
match v with
| Record r -> Bool (r.r_type.rt_uid = val_to_int uid_val)
| _ -> Bool false
(** [record_p v] — generic record predicate.
Named record_p because transpiler mangles record? to record_p. *)
let record_p v = Bool (is_record v)
(** [make_record_constructor rtd_uid] — returns a NativeFn that constructs records.
Called from transpiled sf-define-record-type. *)
let make_record_constructor uid_val =
let uid = val_to_int uid_val in
let rt = match Hashtbl.find_opt rtd_table uid with
| Some rt -> rt | None -> raise (Eval_error "make-record-constructor: unknown rtd") in
NativeFn (rt.rt_name, fun args ->
let n_ctor = Array.length rt.rt_ctor_map in
let n_args = List.length args in
if n_args <> n_ctor then
raise (Eval_error (Printf.sprintf "%s: expected %d args, got %d" rt.rt_name n_ctor n_args));
let fields = Array.make (Array.length rt.rt_fields) Nil in
List.iteri (fun i arg -> fields.(rt.rt_ctor_map.(i)) <- arg) args;
Record { r_type = rt; r_fields = fields })
(** [make_record_predicate rtd_uid] — returns a NativeFn that tests record type. *)
let make_record_predicate uid_val =
let uid = val_to_int uid_val in
NativeFn ("?", fun args ->
match args with
| [Record r] -> Bool (r.r_type.rt_uid = uid)
| [_] -> Bool false
| _ -> raise (Eval_error "record predicate: expected 1 arg"))
(** [make_record_accessor field_idx] — returns a NativeFn that reads a field. *)
let make_record_accessor idx_val =
let idx = val_to_int idx_val in
NativeFn ("ref", fun args ->
match args with
| [Record r] ->
if idx < 0 || idx >= Array.length r.r_fields then
raise (Eval_error (Printf.sprintf "record accessor: index %d out of bounds" idx));
r.r_fields.(idx)
| [v] -> raise (Eval_error ("record accessor: not a record, got " ^ type_of v))
| _ -> raise (Eval_error "record accessor: expected 1 arg"))
(** [make_record_mutator field_idx] — returns a NativeFn that sets a field. *)
let make_record_mutator idx_val =
let idx = val_to_int idx_val in
NativeFn ("set!", fun args ->
match args with
| [Record r; new_val] ->
if idx < 0 || idx >= Array.length r.r_fields then
raise (Eval_error (Printf.sprintf "record mutator: index %d out of bounds" idx));
r.r_fields.(idx) <- new_val; Nil
| _ -> raise (Eval_error "record mutator: expected (record value)"))
(** {1 Dict operations} *)
let make_dict () : dict = Hashtbl.create 8
@@ -541,3 +690,8 @@ let rec inspect = function
| CekState _ -> "<cek-state>"
| CekFrame f -> Printf.sprintf "<frame:%s>" f.cf_type
| VmClosure cl -> Printf.sprintf "<vm:%s>" (match cl.vm_name with Some n -> n | None -> "anon")
| Record r ->
let fields = Array.to_list (Array.mapi (fun i v ->
Printf.sprintf "%s=%s" r.r_type.rt_fields.(i) (inspect v)
) r.r_fields) in
Printf.sprintf "<record:%s %s>" r.r_type.rt_name (String.concat " " fields)

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,22 @@
(define-library (web adapter-html)
(export
render-to-html
render-value-to-html
RENDER_HTML_FORMS
render-html-form?
render-list-to-html
dispatch-html-form
render-lambda-html
render-html-component
render-html-element
render-html-lake
render-html-marsh
render-html-island
serialize-island-state)
(begin
(define
render-to-html
:effects (render)
@@ -589,3 +608,9 @@
(fn
((kwargs :as dict))
(if (empty-dict? kwargs) nil (sx-serialize kwargs))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web adapter-html))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,22 @@
(define-library (web adapter-sx)
(export
render-to-sx
aser
aser-list
aser-reserialize
aser-fragment
aser-call
aser-expand-component
SPECIAL_FORM_NAMES
HO_FORM_NAMES
special-form?
ho-form?
aser-special
eval-case-aser)
(begin
(define
render-to-sx
:effects (render)
@@ -570,3 +589,9 @@
(= match-val (trampoline (eval-expr test env)))
(aser body env)
(eval-case-aser match-val (slice clauses 2) env)))))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web adapter-sx))

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

@@ -19,6 +19,94 @@
;; --------------------------------------------------------------------------
;; Stack / Constants
(define-library (sx bytecode)
(export
OP_CONST
OP_NIL
OP_TRUE
OP_FALSE
OP_POP
OP_DUP
OP_LOCAL_GET
OP_LOCAL_SET
OP_UPVALUE_GET
OP_UPVALUE_SET
OP_GLOBAL_GET
OP_GLOBAL_SET
OP_JUMP
OP_JUMP_IF_FALSE
OP_JUMP_IF_TRUE
OP_CALL
OP_TAIL_CALL
OP_RETURN
OP_CLOSURE
OP_CALL_PRIM
OP_APPLY
OP_LIST
OP_DICT
OP_APPEND_BANG
OP_ITER_INIT
OP_ITER_NEXT
OP_MAP_OPEN
OP_MAP_APPEND
OP_MAP_CLOSE
OP_FILTER_TEST
OP_HO_MAP
OP_HO_FILTER
OP_HO_REDUCE
OP_HO_FOR_EACH
OP_HO_SOME
OP_HO_EVERY
OP_SCOPE_PUSH
OP_SCOPE_POP
OP_PROVIDE_PUSH
OP_PROVIDE_POP
OP_CONTEXT
OP_EMIT
OP_EMITTED
OP_RESET
OP_SHIFT
OP_DEFINE
OP_DEFCOMP
OP_DEFISLAND
OP_DEFMACRO
OP_EXPAND_MACRO
OP_STR_CONCAT
OP_STR_JOIN
OP_SERIALIZE
OP_ADD
OP_SUB
OP_MUL
OP_DIV
OP_EQ
OP_LT
OP_GT
OP_NOT
OP_LEN
OP_FIRST
OP_REST
OP_NTH
OP_CONS
OP_NEG
OP_INC
OP_DEC
OP_ASER_TAG
OP_ASER_FRAG
BYTECODE_MAGIC
BYTECODE_VERSION
CONST_NUMBER
CONST_STRING
CONST_BOOL
CONST_NIL
CONST_SYMBOL
CONST_KEYWORD
CONST_LIST
CONST_DICT
CONST_CODE
opcode-name)
(begin
(define OP_CONST 1) ;; u16 pool_idx — push constant
(define OP_NIL 2) ;; push nil
(define OP_TRUE 3) ;; push true
@@ -161,3 +249,9 @@
(= op 50) "RETURN" (= op 52) "CALL_PRIM"
(= op 128) "DEFINE" (= op 144) "STR_CONCAT"
:else (str "OP_" op))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx bytecode))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "45824e713893dfa0"
(sxbc 1 "71e91d776cc0a108"
(code
:constants ("OP_CONST" 1 "OP_NIL" 2 "OP_TRUE" 3 "OP_FALSE" 4 "OP_POP" 5 "OP_DUP" 6 "OP_LOCAL_GET" 16 "OP_LOCAL_SET" 17 "OP_UPVALUE_GET" 18 "OP_UPVALUE_SET" 19 "OP_GLOBAL_GET" 20 "OP_GLOBAL_SET" 21 "OP_JUMP" 32 "OP_JUMP_IF_FALSE" 33 "OP_JUMP_IF_TRUE" 34 "OP_CALL" 48 "OP_TAIL_CALL" 49 "OP_RETURN" 50 "OP_CLOSURE" 51 "OP_CALL_PRIM" 52 "OP_APPLY" 53 "OP_LIST" 64 "OP_DICT" 65 "OP_APPEND_BANG" 66 "OP_ITER_INIT" 80 "OP_ITER_NEXT" 81 "OP_MAP_OPEN" 82 "OP_MAP_APPEND" 83 "OP_MAP_CLOSE" 84 "OP_FILTER_TEST" 85 "OP_HO_MAP" 88 "OP_HO_FILTER" 89 "OP_HO_REDUCE" 90 "OP_HO_FOR_EACH" 91 "OP_HO_SOME" 92 "OP_HO_EVERY" 93 "OP_SCOPE_PUSH" 96 "OP_SCOPE_POP" 97 "OP_PROVIDE_PUSH" 98 "OP_PROVIDE_POP" 99 "OP_CONTEXT" 100 "OP_EMIT" 101 "OP_EMITTED" 102 "OP_RESET" 112 "OP_SHIFT" 113 "OP_DEFINE" 128 "OP_DEFCOMP" 129 "OP_DEFISLAND" 130 "OP_DEFMACRO" 131 "OP_EXPAND_MACRO" 132 "OP_STR_CONCAT" 144 "OP_STR_JOIN" 145 "OP_SERIALIZE" 146 "OP_ADD" 160 "OP_SUB" 161 "OP_MUL" 162 "OP_DIV" 163 "OP_EQ" 164 "OP_LT" 165 "OP_GT" 166 "OP_NOT" 167 "OP_LEN" 168 "OP_FIRST" 169 "OP_REST" 170 "OP_NTH" 171 "OP_CONS" 172 "OP_NEG" 173 "OP_INC" 174 "OP_DEC" 175 "OP_ASER_TAG" 224 "OP_ASER_FRAG" 225 "BYTECODE_MAGIC" "SXBC" "BYTECODE_VERSION" "CONST_NUMBER" "CONST_STRING" "CONST_BOOL" "CONST_NIL" "CONST_SYMBOL" "CONST_KEYWORD" "CONST_LIST" 7 "CONST_DICT" 8 "CONST_CODE" 9 "opcode-name" {:upvalue-count 0 :arity 1 :constants ("=" 1 "CONST" 2 "NIL" 3 "TRUE" 4 "FALSE" 5 "POP" 6 "DUP" 16 "LOCAL_GET" 17 "LOCAL_SET" 20 "GLOBAL_GET" 21 "GLOBAL_SET" 32 "JUMP" 33 "JUMP_IF_FALSE" 48 "CALL" 49 "TAIL_CALL" 50 "RETURN" 52 "CALL_PRIM" 128 "DEFINE" 144 "STR_CONCAT" "str" "OP_") :bytecode (16 0 1 1 0 52 0 0 2 33 6 0 1 2 0 32 59 1 16 0 1 3 0 52 0 0 2 33 6 0 1 4 0 32 41 1 16 0 1 5 0 52 0 0 2 33 6 0 1 6 0 32 23 1 16 0 1 7 0 52 0 0 2 33 6 0 1 8 0 32 5 1 16 0 1 9 0 52 0 0 2 33 6 0 1 10 0 32 243 0 16 0 1 11 0 52 0 0 2 33 6 0 1 12 0 32 225 0 16 0 1 13 0 52 0 0 2 33 6 0 1 14 0 32 207 0 16 0 1 15 0 52 0 0 2 33 6 0 1 16 0 32 189 0 16 0 1 17 0 52 0 0 2 33 6 0 1 18 0 32 171 0 16 0 1 19 0 52 0 0 2 33 6 0 1 20 0 32 153 0 16 0 1 21 0 52 0 0 2 33 6 0 1 22 0 32 135 0 16 0 1 23 0 52 0 0 2 33 6 0 1 24 0 32 117 0 16 0 1 25 0 52 0 0 2 33 6 0 1 26 0 32 99 0 16 0 1 27 0 52 0 0 2 33 6 0 1 28 0 32 81 0 16 0 1 29 0 52 0 0 2 33 6 0 1 30 0 32 63 0 16 0 1 31 0 52 0 0 2 33 6 0 1 32 0 32 45 0 16 0 1 33 0 52 0 0 2 33 6 0 1 34 0 32 27 0 16 0 1 35 0 52 0 0 2 33 6 0 1 36 0 32 9 0 1 38 0 16 0 52 37 0 2 50)}) :bytecode (1 1 0 128 0 0 5 1 3 0 128 2 0 5 1 5 0 128 4 0 5 1 7 0 128 6 0 5 1 9 0 128 8 0 5 1 11 0 128 10 0 5 1 13 0 128 12 0 5 1 15 0 128 14 0 5 1 17 0 128 16 0 5 1 19 0 128 18 0 5 1 21 0 128 20 0 5 1 23 0 128 22 0 5 1 25 0 128 24 0 5 1 27 0 128 26 0 5 1 29 0 128 28 0 5 1 31 0 128 30 0 5 1 33 0 128 32 0 5 1 35 0 128 34 0 5 1 37 0 128 36 0 5 1 39 0 128 38 0 5 1 41 0 128 40 0 5 1 43 0 128 42 0 5 1 45 0 128 44 0 5 1 47 0 128 46 0 5 1 49 0 128 48 0 5 1 51 0 128 50 0 5 1 53 0 128 52 0 5 1 55 0 128 54 0 5 1 57 0 128 56 0 5 1 59 0 128 58 0 5 1 61 0 128 60 0 5 1 63 0 128 62 0 5 1 65 0 128 64 0 5 1 67 0 128 66 0 5 1 69 0 128 68 0 5 1 71 0 128 70 0 5 1 73 0 128 72 0 5 1 75 0 128 74 0 5 1 77 0 128 76 0 5 1 79 0 128 78 0 5 1 81 0 128 80 0 5 1 83 0 128 82 0 5 1 85 0 128 84 0 5 1 87 0 128 86 0 5 1 89 0 128 88 0 5 1 91 0 128 90 0 5 1 93 0 128 92 0 5 1 95 0 128 94 0 5 1 97 0 128 96 0 5 1 99 0 128 98 0 5 1 101 0 128 100 0 5 1 103 0 128 102 0 5 1 105 0 128 104 0 5 1 107 0 128 106 0 5 1 109 0 128 108 0 5 1 111 0 128 110 0 5 1 113 0 128 112 0 5 1 115 0 128 114 0 5 1 117 0 128 116 0 5 1 119 0 128 118 0 5 1 121 0 128 120 0 5 1 123 0 128 122 0 5 1 125 0 128 124 0 5 1 127 0 128 126 0 5 1 129 0 128 128 0 5 1 131 0 128 130 0 5 1 133 0 128 132 0 5 1 135 0 128 134 0 5 1 137 0 128 136 0 5 1 139 0 128 138 0 5 1 141 0 128 140 0 5 1 143 0 128 142 0 5 1 1 0 128 144 0 5 1 1 0 128 145 0 5 1 3 0 128 146 0 5 1 5 0 128 147 0 5 1 7 0 128 148 0 5 1 9 0 128 149 0 5 1 11 0 128 150 0 5 1 152 0 128 151 0 5 1 154 0 128 153 0 5 1 156 0 128 155 0 5 51 158 0 128 157 0 50)))
:constants ("define-library" "sx" "bytecode" "export" "OP_CONST" "OP_NIL" "OP_TRUE" "OP_FALSE" "OP_POP" "OP_DUP" "OP_LOCAL_GET" "OP_LOCAL_SET" "OP_UPVALUE_GET" "OP_UPVALUE_SET" "OP_GLOBAL_GET" "OP_GLOBAL_SET" "OP_JUMP" "OP_JUMP_IF_FALSE" "OP_JUMP_IF_TRUE" "OP_CALL" "OP_TAIL_CALL" "OP_RETURN" "OP_CLOSURE" "OP_CALL_PRIM" "OP_APPLY" "OP_LIST" "OP_DICT" "OP_APPEND_BANG" "OP_ITER_INIT" "OP_ITER_NEXT" "OP_MAP_OPEN" "OP_MAP_APPEND" "OP_MAP_CLOSE" "OP_FILTER_TEST" "OP_HO_MAP" "OP_HO_FILTER" "OP_HO_REDUCE" "OP_HO_FOR_EACH" "OP_HO_SOME" "OP_HO_EVERY" "OP_SCOPE_PUSH" "OP_SCOPE_POP" "OP_PROVIDE_PUSH" "OP_PROVIDE_POP" "OP_CONTEXT" "OP_EMIT" "OP_EMITTED" "OP_RESET" "OP_SHIFT" "OP_DEFINE" "OP_DEFCOMP" "OP_DEFISLAND" "OP_DEFMACRO" "OP_EXPAND_MACRO" "OP_STR_CONCAT" "OP_STR_JOIN" "OP_SERIALIZE" "OP_ADD" "OP_SUB" "OP_MUL" "OP_DIV" "OP_EQ" "OP_LT" "OP_GT" "OP_NOT" "OP_LEN" "OP_FIRST" "OP_REST" "OP_NTH" "OP_CONS" "OP_NEG" "OP_INC" "OP_DEC" "OP_ASER_TAG" "OP_ASER_FRAG" "BYTECODE_MAGIC" "BYTECODE_VERSION" "CONST_NUMBER" "CONST_STRING" "CONST_BOOL" "CONST_NIL" "CONST_SYMBOL" "CONST_KEYWORD" "CONST_LIST" "CONST_DICT" "CONST_CODE" "opcode-name" 1 2 3 4 5 6 16 17 18 19 20 21 32 33 34 48 49 50 51 52 53 64 65 66 80 81 82 83 84 85 88 89 90 91 92 93 96 97 98 99 100 101 102 112 113 128 129 130 131 132 144 145 146 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 224 225 "SXBC" 7 8 9 {:upvalue-count 0 :arity 1 :constants ("=" 1 "CONST" 2 "NIL" 3 "TRUE" 4 "FALSE" 5 "POP" 6 "DUP" 16 "LOCAL_GET" 17 "LOCAL_SET" 20 "GLOBAL_GET" 21 "GLOBAL_SET" 32 "JUMP" 33 "JUMP_IF_FALSE" 48 "CALL" 49 "TAIL_CALL" 50 "RETURN" 52 "CALL_PRIM" 128 "DEFINE" 144 "STR_CONCAT" "str" "OP_") :bytecode (16 0 1 1 0 52 0 0 2 33 6 0 1 2 0 32 59 1 16 0 1 3 0 52 0 0 2 33 6 0 1 4 0 32 41 1 16 0 1 5 0 52 0 0 2 33 6 0 1 6 0 32 23 1 16 0 1 7 0 52 0 0 2 33 6 0 1 8 0 32 5 1 16 0 1 9 0 52 0 0 2 33 6 0 1 10 0 32 243 0 16 0 1 11 0 52 0 0 2 33 6 0 1 12 0 32 225 0 16 0 1 13 0 52 0 0 2 33 6 0 1 14 0 32 207 0 16 0 1 15 0 52 0 0 2 33 6 0 1 16 0 32 189 0 16 0 1 17 0 52 0 0 2 33 6 0 1 18 0 32 171 0 16 0 1 19 0 52 0 0 2 33 6 0 1 20 0 32 153 0 16 0 1 21 0 52 0 0 2 33 6 0 1 22 0 32 135 0 16 0 1 23 0 52 0 0 2 33 6 0 1 24 0 32 117 0 16 0 1 25 0 52 0 0 2 33 6 0 1 26 0 32 99 0 16 0 1 27 0 52 0 0 2 33 6 0 1 28 0 32 81 0 16 0 1 29 0 52 0 0 2 33 6 0 1 30 0 32 63 0 16 0 1 31 0 52 0 0 2 33 6 0 1 32 0 32 45 0 16 0 1 33 0 52 0 0 2 33 6 0 1 34 0 32 27 0 16 0 1 35 0 52 0 0 2 33 6 0 1 36 0 32 9 0 1 38 0 16 0 52 37 0 2 50)} "import") :bytecode (20 0 0 20 1 0 20 2 0 48 1 20 3 0 20 4 0 20 5 0 20 6 0 20 7 0 20 8 0 20 9 0 20 10 0 20 11 0 20 12 0 20 13 0 20 14 0 20 15 0 20 16 0 20 17 0 20 18 0 20 19 0 20 20 0 20 21 0 20 22 0 20 23 0 20 24 0 20 25 0 20 26 0 20 27 0 20 28 0 20 29 0 20 30 0 20 31 0 20 32 0 20 33 0 20 34 0 20 35 0 20 36 0 20 37 0 20 38 0 20 39 0 20 40 0 20 41 0 20 42 0 20 43 0 20 44 0 20 45 0 20 46 0 20 47 0 20 48 0 20 49 0 20 50 0 20 51 0 20 52 0 20 53 0 20 54 0 20 55 0 20 56 0 20 57 0 20 58 0 20 59 0 20 60 0 20 61 0 20 62 0 20 63 0 20 64 0 20 65 0 20 66 0 20 67 0 20 68 0 20 69 0 20 70 0 20 71 0 20 72 0 20 73 0 20 74 0 20 75 0 20 76 0 20 77 0 20 78 0 20 79 0 20 80 0 20 81 0 20 82 0 20 83 0 20 84 0 20 85 0 20 86 0 48 83 1 87 0 128 4 0 5 1 88 0 128 5 0 5 1 89 0 128 6 0 5 1 90 0 128 7 0 5 1 91 0 128 8 0 5 1 92 0 128 9 0 5 1 93 0 128 10 0 5 1 94 0 128 11 0 5 1 95 0 128 12 0 5 1 96 0 128 13 0 5 1 97 0 128 14 0 5 1 98 0 128 15 0 5 1 99 0 128 16 0 5 1 100 0 128 17 0 5 1 101 0 128 18 0 5 1 102 0 128 19 0 5 1 103 0 128 20 0 5 1 104 0 128 21 0 5 1 105 0 128 22 0 5 1 106 0 128 23 0 5 1 107 0 128 24 0 5 1 108 0 128 25 0 5 1 109 0 128 26 0 5 1 110 0 128 27 0 5 1 111 0 128 28 0 5 1 112 0 128 29 0 5 1 113 0 128 30 0 5 1 114 0 128 31 0 5 1 115 0 128 32 0 5 1 116 0 128 33 0 5 1 117 0 128 34 0 5 1 118 0 128 35 0 5 1 119 0 128 36 0 5 1 120 0 128 37 0 5 1 121 0 128 38 0 5 1 122 0 128 39 0 5 1 123 0 128 40 0 5 1 124 0 128 41 0 5 1 125 0 128 42 0 5 1 126 0 128 43 0 5 1 127 0 128 44 0 5 1 128 0 128 45 0 5 1 129 0 128 46 0 5 1 130 0 128 47 0 5 1 131 0 128 48 0 5 1 132 0 128 49 0 5 1 133 0 128 50 0 5 1 134 0 128 51 0 5 1 135 0 128 52 0 5 1 136 0 128 53 0 5 1 137 0 128 54 0 5 1 138 0 128 55 0 5 1 139 0 128 56 0 5 1 140 0 128 57 0 5 1 141 0 128 58 0 5 1 142 0 128 59 0 5 1 143 0 128 60 0 5 1 144 0 128 61 0 5 1 145 0 128 62 0 5 1 146 0 128 63 0 5 1 147 0 128 64 0 5 1 148 0 128 65 0 5 1 149 0 128 66 0 5 1 150 0 128 67 0 5 1 151 0 128 68 0 5 1 152 0 128 69 0 5 1 153 0 128 70 0 5 1 154 0 128 71 0 5 1 155 0 128 72 0 5 1 156 0 128 73 0 5 1 157 0 128 74 0 5 1 158 0 128 75 0 5 1 87 0 128 76 0 5 1 87 0 128 77 0 5 1 88 0 128 78 0 5 1 89 0 128 79 0 5 1 90 0 128 80 0 5 1 91 0 128 81 0 5 1 92 0 128 82 0 5 1 159 0 128 83 0 5 1 160 0 128 84 0 5 1 161 0 128 85 0 5 51 162 0 128 86 0 48 3 5 20 163 0 20 1 0 20 2 0 48 1 48 1 50)))

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,32 @@
(define-library (sx signals)
(export
make-signal
signal?
signal-value
signal-set-value!
signal-subscribers
signal-add-sub!
signal-remove-sub!
signal-deps
signal-set-deps!
signal
deref
reset!
swap!
computed
effect
*batch-depth*
*batch-queue*
batch
notify-subscribers
flush-subscribers
dispose-computed
with-island-scope
register-in-scope)
(begin
(define
make-signal
(fn
@@ -193,3 +222,9 @@
(let
((collector (scope-peek "sx-island-scope")))
(when collector (cek-call collector (list disposable))))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx signals))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,28 @@
(define-library (web deps)
(export
scan-refs
scan-refs-walk
transitive-deps-walk
transitive-deps
compute-all-deps
scan-components-from-source
components-needed
page-component-bundle
page-css-classes
scan-io-refs-walk
scan-io-refs
transitive-io-refs-walk
transitive-io-refs
compute-all-io-refs
component-io-refs-cached
component-pure?
render-target
page-render-plan
env-components)
(begin
(define
scan-refs
:effects ()
@@ -335,3 +360,9 @@
((k :as string))
(let ((v (env-get env k))) (or (component? v) (macro? v))))
(keys env))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web deps))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,42 @@
(define-library (web engine)
(export
ENGINE_VERBS
DEFAULT_SWAP
parse-time
parse-trigger-spec
default-trigger
get-verb-info
build-request-headers
process-response-headers
parse-swap-spec
parse-retry-spec
next-retry-ms
filter-params
resolve-target
apply-optimistic
revert-optimistic
find-oob-swaps
morph-node
sync-attrs
morph-children
morph-island-children
morph-marsh
process-signal-updates
swap-dom-nodes
insert-remaining-siblings
swap-html-string
handle-history
PRELOAD_TTL
preload-cache-get
preload-cache-set
classify-trigger
should-boost-link?
should-boost-form?
parse-sse-swap)
(begin
(define ENGINE_VERBS (list "get" "post" "put" "delete" "patch"))
(define DEFAULT_SWAP "outerHTML")
@@ -806,3 +845,9 @@
parse-sse-swap
:effects (io)
(fn (el) (or (dom-get-attr el "sx-sse-swap") "message")))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web engine))

File diff suppressed because one or more lines are too long

View File

@@ -20,6 +20,20 @@
;; ==========================================================================
;; Registry of freeze scopes: name → list of {name signal} entries
(define-library (sx freeze)
(export
freeze-registry
freeze-signal
freeze-scope
cek-freeze-scope
cek-freeze-all
cek-thaw-scope
cek-thaw-all
freeze-to-sx
thaw-from-sx)
(begin
(define freeze-registry (dict))
;; Register a signal in the current freeze scope
@@ -92,3 +106,9 @@
(when (not (empty? parsed))
(let ((frozen (first parsed)))
(cek-thaw-scope (get frozen "name") frozen))))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx freeze))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "19bca721c37b25b6"
(sxbc 1 "320fb4826d09fed3"
(code
:constants ("freeze-registry" "dict" "freeze-signal" {:upvalue-count 0 :arity 2 :constants ("context" "sx-freeze-scope" "get" "freeze-registry" "list" "append!" "dict" "name" "signal" "dict-set!") :bytecode (1 1 0 2 52 0 0 2 17 2 16 2 33 55 0 20 3 0 16 2 52 2 0 2 6 34 5 0 5 52 4 0 0 17 3 16 3 1 7 0 16 0 1 8 0 16 1 52 6 0 4 52 5 0 2 5 20 3 0 16 2 16 3 52 9 0 3 32 1 0 2 50)} "freeze-scope" {:upvalue-count 0 :arity 2 :constants ("scope-push!" "sx-freeze-scope" "dict-set!" "freeze-registry" "list" "cek-call" "scope-pop!") :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)} "cek-freeze-scope" {:upvalue-count 0 :arity 1 :constants ("get" "freeze-registry" "list" "dict" "for-each" {:upvalue-count 1 :arity 1 :constants ("dict-set!" "get" "name" "signal-value" "signal") :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)} "name" "signals") :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)} "cek-freeze-all" {:upvalue-count 0 :arity 0 :constants ("map" {:upvalue-count 0 :arity 1 :constants ("cek-freeze-scope") :bytecode (20 0 0 16 0 49 1 50)} "keys" "freeze-registry") :bytecode (51 1 0 20 3 0 52 2 0 1 52 0 0 2 50)} "cek-thaw-scope" {:upvalue-count 0 :arity 2 :constants ("get" "freeze-registry" "list" "signals" "for-each" {:upvalue-count 1 :arity 1 :constants ("get" "name" "signal" "not" "nil?" "reset!") :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)}) :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)} "cek-thaw-all" {:upvalue-count 0 :arity 1 :constants ("for-each" {:upvalue-count 0 :arity 1 :constants ("cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 1 2 0 52 1 0 2 16 0 49 2 50)}) :bytecode (51 1 0 16 0 52 0 0 2 50)} "freeze-to-sx" {:upvalue-count 0 :arity 1 :constants ("sx-serialize" "cek-freeze-scope") :bytecode (20 0 0 20 1 0 16 0 48 1 49 1 50)} "thaw-from-sx" {:upvalue-count 0 :arity 1 :constants ("sx-parse" "not" "empty?" "first" "cek-thaw-scope" "get" "name") :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)}) :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 ("define-library" "sx" "freeze" "export" "freeze-registry" "freeze-signal" "freeze-scope" "cek-freeze-scope" "cek-freeze-all" "cek-thaw-scope" "cek-thaw-all" "freeze-to-sx" "thaw-from-sx" "dict" {:upvalue-count 0 :arity 2 :constants ("context" "sx-freeze-scope" "get" "freeze-registry" "list" "append!" "dict" "name" "signal" "dict-set!") :bytecode (1 1 0 2 52 0 0 2 17 2 16 2 33 55 0 20 3 0 16 2 52 2 0 2 6 34 5 0 5 52 4 0 0 17 3 16 3 1 7 0 16 0 1 8 0 16 1 52 6 0 4 52 5 0 2 5 20 3 0 16 2 16 3 52 9 0 3 32 1 0 2 50)} {:upvalue-count 0 :arity 2 :constants ("scope-push!" "sx-freeze-scope" "dict-set!" "freeze-registry" "list" "cek-call" "scope-pop!") :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 16 1 2 52 5 0 2 5 1 1 0 52 6 0 1 5 2 50)} {:upvalue-count 0 :arity 1 :constants ("get" "freeze-registry" "list" "dict" "for-each" {:upvalue-count 1 :arity 1 :constants ("dict-set!" "get" "name" "signal-value" "signal") :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)} "name" "signals") :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)} {:upvalue-count 0 :arity 0 :constants ("map" {:upvalue-count 0 :arity 1 :constants ("cek-freeze-scope") :bytecode (20 0 0 16 0 49 1 50)} "keys" "freeze-registry") :bytecode (51 1 0 20 3 0 52 2 0 1 52 0 0 2 50)} {:upvalue-count 0 :arity 2 :constants ("get" "freeze-registry" "list" "signals" "for-each" {:upvalue-count 1 :arity 1 :constants ("get" "name" "signal" "not" "nil?" "reset!") :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)}) :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)} {:upvalue-count 0 :arity 1 :constants ("for-each" {:upvalue-count 0 :arity 1 :constants ("cek-thaw-scope" "get" "name") :bytecode (20 0 0 16 0 1 2 0 52 1 0 2 16 0 49 2 50)}) :bytecode (51 1 0 16 0 52 0 0 2 50)} {:upvalue-count 0 :arity 1 :constants ("sx-serialize" "cek-freeze-scope") :bytecode (20 1 0 16 0 48 1 52 0 0 1 50)} {:upvalue-count 0 :arity 1 :constants ("sx-parse" "not" "empty?" "first" "cek-thaw-scope" "get" "name") :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)} "import") :bytecode (20 0 0 20 1 0 20 2 0 48 1 20 3 0 20 4 0 20 5 0 20 6 0 20 7 0 20 8 0 20 9 0 20 10 0 20 11 0 20 12 0 48 9 52 13 0 0 128 4 0 5 51 14 0 128 5 0 5 51 15 0 128 6 0 5 51 16 0 128 7 0 5 51 17 0 128 8 0 5 51 18 0 128 9 0 5 51 19 0 128 10 0 5 51 20 0 128 11 0 5 51 21 0 128 12 0 48 3 5 20 22 0 20 1 0 20 2 0 48 1 48 1 50)))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "57726b5b82c1a3cb"
(code
:constants ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :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)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :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)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :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)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :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)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 2 :constants ("swap!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :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)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 20 2 0 18 1 2 49 2 50)}) :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)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (18 0 20 1 0 18 1 48 1 52 0 0 2 50)} "history") :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)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :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)}) :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 ("assert-signal-value" {:upvalue-count 0 :arity 2 :constants ("deref" "assert=" "str" "Expected signal value " ", got ") :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)} "assert-signal-has-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" ">" "len" "signal-subscribers" 0 "Expected signal to have subscribers") :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)} "assert-signal-no-subscribers" {:upvalue-count 0 :arity 1 :constants ("assert" "=" "len" "signal-subscribers" 0 "Expected signal to have no subscribers") :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)} "assert-signal-subscriber-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-subscribers" "assert=" "str" "Expected " " subscribers, got ") :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)} "simulate-signal-set!" {:upvalue-count 0 :arity 2 :constants ("reset!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "simulate-signal-swap!" {:upvalue-count 0 :arity 2 :constants ("swap!") :bytecode (20 0 0 16 0 16 1 49 2 50)} "assert-computed-dep-count" {:upvalue-count 0 :arity 2 :constants ("len" "signal-deps" "assert=" "str" "Expected " " deps, got ") :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)} "assert-computed-depends-on" {:upvalue-count 0 :arity 2 :constants ("assert" "contains?" "signal-deps" "Expected computed to depend on the given signal") :bytecode (20 0 0 20 2 0 16 0 48 1 16 1 52 1 0 2 1 3 0 49 2 50)} "count-effect-runs" {:upvalue-count 0 :arity 1 :constants ("signal" 0 "effect" {:upvalue-count 1 :arity 0 :constants ("deref") :bytecode (20 0 0 18 0 49 1 50)} {:upvalue-count 2 :arity 0 :constants ("+" 1 "cek-call") :bytecode (18 0 1 1 0 52 0 0 2 19 0 5 18 1 2 52 2 0 2 50)}) :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)} "make-test-signal" {:upvalue-count 0 :arity 1 :constants ("signal" "list" "effect" {:upvalue-count 2 :arity 0 :constants ("append!" "deref") :bytecode (18 0 20 1 0 18 1 48 1 52 0 0 2 50)} "history") :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)} "assert-batch-coalesces" {:upvalue-count 0 :arity 2 :constants (0 "signal" "effect" {:upvalue-count 2 :arity 0 :constants ("deref" "+" 1) :bytecode (20 0 0 18 0 48 1 5 18 1 1 2 0 52 1 0 2 19 1 50)} "batch" "assert=" "str" "Expected " " notifications, got ") :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)}) :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)))

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,30 @@
;; Assert condition is truthy, error with message
(define-library (sx harness)
(export
assert
assert=
default-platform
make-harness
harness-reset!
harness-log
harness-get
harness-set!
make-interceptor
install-interceptors
io-calls
io-call-count
io-call-nth
io-call-args
io-call-result
assert-io-called
assert-no-io
assert-io-count
assert-io-args
assert-io-result
assert-state)
(begin
(define assert (fn (condition msg) (when (not condition) (error (or msg "Assertion failed")))))
;; Assert two values are equal
@@ -60,3 +86,9 @@
;; Assert a state key has the expected value
(define assert-state :effects () (fn (session key expected) (let ((actual (harness-get session key))) (assert (equal? actual expected) (str "Expected state " key " to be " (str expected) " but got " (str actual))))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx harness))

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,3 +1,68 @@
(define-library (web orchestration)
(export
_preload-cache
dispatch-trigger-events
execute-request
do-fetch
handle-fetch-success
flush-collected-styles
handle-sx-response
handle-html-response
handle-retry
bind-triggers
bind-event
post-swap
process-settle-hooks
activate-scripts
process-oob-swaps
hoist-head-elements
process-boosted
boost-descendants
_page-data-cache
_page-data-cache-ttl
page-data-cache-key
page-data-cache-get
page-data-cache-set
invalidate-page-cache
invalidate-all-page-cache
update-page-cache
process-cache-directives
_optimistic-snapshots
optimistic-cache-update
optimistic-cache-revert
optimistic-cache-confirm
submit-mutation
_is-online
_offline-queue
offline-is-online?
offline-set-online!
offline-queue-mutation
offline-sync
offline-pending-count
offline-aware-mutation
current-page-layout
swap-rendered-content
resolve-route-target
deps-satisfied?
try-client-route
bind-client-route-link
process-sse
bind-sse
bind-sse-swap
bind-inline-handlers
bind-preload-for
do-preload
VERB_SELECTOR
process-elements
process-one
process-emit-elements
save-scroll-position
handle-popstate
engine-init)
(begin
(define _preload-cache (dict))
(define
@@ -1566,3 +1631,9 @@
(fn
()
(do (sx-process-scripts nil) (sx-hydrate nil) (process-elements nil))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web orchestration))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,21 @@
(define-library (web page-helpers)
(export
special-form-category-map
extract-define-kwargs
categorize-special-forms
build-ref-items-with-href
build-reference-data
build-attr-detail
build-header-detail
build-event-detail
build-component-source
build-bundle-analysis
build-routing-analysis
build-affinity-analysis)
(begin
(define special-form-category-map {:defmacro "Functions & Components" :for-each "Higher-Order Forms" :defpage "Domain Definitions" :let "Binding" :filter "Higher-Order Forms" :shift "Continuations" :and "Control Flow" :set! "Binding" :map-indexed "Higher-Order Forms" :dynamic-wind "Guards" :reduce "Higher-Order Forms" :cond "Control Flow" :defquery "Domain Definitions" :-> "Sequencing & Threading" :let* "Binding" :define "Binding" :reset "Continuations" :case "Control Flow" :do "Sequencing & Threading" :map "Higher-Order Forms" :some "Higher-Order Forms" :letrec "Binding" :if "Control Flow" :quote "Quoting" :every? "Higher-Order Forms" :defhandler "Domain Definitions" :fn "Functions & Components" :defstyle "Domain Definitions" :lambda "Functions & Components" :defaction "Domain Definitions" :or "Control Flow" :defcomp "Functions & Components" :quasiquote "Quoting" :when "Control Flow" :begin "Sequencing & Threading"})
(define
@@ -230,3 +248,9 @@
(define
build-affinity-analysis
(fn ((demo-components :as list) (page-plans :as list)) {:components demo-components :page-plans page-plans}))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web page-helpers))

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,24 @@
;; Registry of all valid HTML tag names
(define-library (sx render)
(export
HTML_TAGS
VOID_ELEMENTS
BOOLEAN_ATTRS
*definition-form-extensions*
definition-form?
parse-element-args
render-attrs
eval-cond
eval-cond-scheme
eval-cond-clojure
process-bindings
is-render-expr?
merge-spread-attrs
escape-html
escape-attr)
(begin
(define
HTML_TAGS
(list
@@ -413,3 +433,9 @@
;; Escape special chars for HTML attribute values
(define escape-attr (fn (s) (escape-html s)))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx render))

File diff suppressed because one or more lines are too long

View File

@@ -17,6 +17,47 @@
;; "/" → ()
;; "/docs/" → ("docs")
(define-library (web router)
(export
split-path-segments
make-route-segment
parse-route-pattern
match-route-segments
match-route
find-matching-route
_fn-to-segment
sx-url-to-path
_count-leading-dots
_strip-trailing-close
_index-of-safe
_last-index-of
_pop-sx-url-level
_pop-sx-url-levels
_split-pos-kw
_parse-relative-body
_extract-innermost
_find-kw-in-tokens
_find-keyword-value
_replace-kw-in-tokens
_set-keyword-in-content
_is-delta-value?
_apply-delta
_apply-kw-pairs
_apply-keywords-to-url
_normalize-relative
resolve-relative-url
relative-sx-url?
_url-special-forms
url-special-form?
parse-sx-url
url-special-form-name
url-special-form-inner
url-to-expr
auto-quote-unknowns
prepare-url-expr)
(begin
(define split-path-segments :effects []
(fn ((path :as string))
(let ((trimmed (if (starts-with? path "/") (slice path 1) path)))
@@ -678,3 +719,9 @@
;;
;; From parser.sx: sx-parse, sx-serialize
;; --------------------------------------------------------------------------
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (web router))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
(sxbc 1 "7e4a727b2f55684e"
(code
:constants ("with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (18 0 16 0 52 0 0 2 50)} "dom-set-data" "sx-marsh-disposers") :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)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (20 0 0 16 0 2 49 2 50)} "dom-set-data") :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)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :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)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :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)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :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)}) :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)}) :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 50)))
:constants ("with-marsh-scope" {:upvalue-count 0 :arity 2 :constants ("list" "with-island-scope" {:upvalue-count 1 :arity 1 :constants ("append!") :bytecode (18 0 16 0 52 0 0 2 50)} "dom-set-data" "sx-marsh-disposers") :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)} "dispose-marsh-scope" {:upvalue-count 0 :arity 1 :constants ("dom-get-data" "sx-marsh-disposers" "for-each" {:upvalue-count 0 :arity 1 :constants ("cek-call") :bytecode (16 0 2 52 0 0 2 50)} "dom-set-data") :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)} "emit-event" {:upvalue-count 0 :arity 3 :constants ("dom-dispatch") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "on-event" {:upvalue-count 0 :arity 3 :constants ("dom-on") :bytecode (20 0 0 16 0 16 1 16 2 49 3 50)} "bridge-event" {:upvalue-count 0 :arity 4 :constants ("effect" {:upvalue-count 4 :arity 0 :constants ("dom-on" {:upvalue-count 2 :arity 1 :constants ("event-detail" "cek-call" "list" "reset!") :bytecode (20 0 0 16 0 48 1 17 1 18 0 33 15 0 18 0 16 1 52 2 0 1 52 1 0 2 32 2 0 16 1 17 2 20 3 0 18 1 16 2 49 2 50)}) :bytecode (20 0 0 18 0 18 1 51 1 0 0 2 0 3 48 3 17 0 16 0 50)}) :bytecode (20 0 0 51 1 0 1 0 1 1 1 3 1 2 49 1 50)} "resource" {:upvalue-count 0 :arity 1 :constants ("signal" "dict" "loading" "data" "error" "promise-then" "cek-call" {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :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)} {:upvalue-count 1 :arity 1 :constants ("reset!" "dict" "loading" "data" "error") :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)}) :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 16 0 2 52 6 0 2 51 7 0 1 1 51 8 0 1 1 48 3 5 16 1 50)}) :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 50)))

View File

@@ -1,3 +1,41 @@
(define-library (sx vm)
(export
make-upvalue-cell
uv-get
uv-set!
make-vm-code
make-vm-closure
make-vm-frame
make-vm
vm-push
vm-pop
vm-peek
frame-read-u8
frame-read-u16
frame-read-i16
vm-push-frame
code-from-value
vm-closure?
vm-call
frame-local-get
frame-local-set
frame-upvalue-get
frame-upvalue-set
vm-global-get
vm-resolve-ho-form
vm-call-external
vm-global-set
env-walk
env-walk-set!
vm-create-closure
vm-run
vm-step
vm-call-closure
vm-execute-module)
(begin
(define make-upvalue-cell (fn (value) {:uv-value value}))
(define uv-get (fn (cell) (get cell "uv-value")))
@@ -516,6 +554,10 @@
(vm-push vm (inc (vm-pop vm)))
(= op 175)
(vm-push vm (dec (vm-pop vm)))
(= op 112)
(let
((request (vm-pop vm)))
(error (str "VM: IO suspension (OP_PERFORM) — request: " request)))
:else (error (str "VM: unknown opcode " op))))))
(define
@@ -552,3 +594,9 @@
(dict-set! vm "frames" (list frame))
(vm-run vm)
(vm-pop vm)))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx vm))

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-ee45eb6c",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-996ff9e3",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-4aca2756",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-024d53bc",[2,3,5]],["std_exit-10fb8830",[2]],["start-289ae59b",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_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_evalVM":(a,b)=>a.evalVM=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_scopeTraceDrain":(a,b)=>a.scopeTraceDrain=b,"set_scopeTraceOff":(a,b)=>a.scopeTraceOff=b,"set_scopeTraceOn":(a,b)=>a.scopeTraceOn=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}),"obj_6":()=>({}),"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_evalVM":(a,b)=>a.evalVM=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_op":(a,b)=>a.op=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_request":(a,b)=>a.request=b,"set_resume":(a,b)=>a.resume=b,"set_scopeTraceDrain":(a,b)=>a.scopeTraceDrain=b,"set_scopeTraceOff":(a,b)=>a.scopeTraceOff=b,"set_scopeTraceOn":(a,b)=>a.scopeTraceOn=b,"set_stringify":(a,b)=>a.stringify=b,"set_suspended":(a,b)=>a.suspended=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx_browser.bc.wasm.assets"});

View File

@@ -1446,6 +1446,8 @@
("perform" (step-sf-perform args env kont))
("define-library" (step-sf-define-library args env kont))
("import" (step-sf-import args env kont))
("define-record-type"
(make-cek-value (sf-define-record-type args env) env kont))
(_
(cond
(has-key? *custom-special-forms* name)
@@ -1573,6 +1575,64 @@
env
(kont-push (make-perform-frame env) kont)))))
;; R7RS records (SRFI-9)
;;
;; (define-record-type <point>
;; (make-point x y)
;; point?
;; (x point-x)
;; (y point-y set-point-y!))
;;
;; Creates: constructor, predicate, accessors, optional mutators.
;; Opaque — only accessible through generated functions.
;; Generative — each call creates a unique type.
(define
sf-define-record-type
(fn
(args env)
(let
((type-sym (first args))
(ctor-spec (nth args 1))
(pred-sym (nth args 2))
(field-specs (slice args 3)))
(let
((raw-name (symbol-name type-sym)))
(let
((type-name
(if
(and (starts-with? raw-name "<") (ends-with? raw-name ">"))
(slice raw-name 1 (- (len raw-name) 1))
raw-name))
(ctor-name (symbol-name (first ctor-spec)))
(ctor-params (map (fn (s) (symbol-name s)) (rest ctor-spec)))
(pred-name (symbol-name pred-sym))
(field-names
(map (fn (fs) (symbol-name (first fs))) field-specs)))
(let
((rtd-uid (make-rtd type-name field-names ctor-params)))
;; Constructor — OCaml returns a NativeFn
(env-bind! env ctor-name
(make-record-constructor rtd-uid))
;; Predicate — OCaml returns a NativeFn
(env-bind! env pred-name
(make-record-predicate rtd-uid))
;; Accessors and optional mutators
(for-each-indexed
(fn
(idx fs)
(let
((accessor-name (symbol-name (nth fs 1))))
(env-bind! env accessor-name
(make-record-accessor idx))
(when
(>= (len fs) 3)
(let
((mutator-name (symbol-name (nth fs 2))))
(env-bind! env mutator-name
(make-record-mutator idx))))))
field-specs)
nil))))))
;; Delimited continuations
(define
step-sf-callcc

185
spec/tests/test-records.sx Normal file
View File

@@ -0,0 +1,185 @@
;; R7RS define-record-type tests (SRFI-9)
(defsuite
"record-basic"
(deftest
"constructor and predicate"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(let
((p (make-point 3 4)))
(assert (point? p))
(assert= 3 (point-x p))
(assert= 4 (point-y p)))))
(deftest
"predicate rejects non-records"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(assert= false (point? 42))
(assert= false (point? "hello"))
(assert= false (point? (list 1 2)))
(assert= false (point? {:x 1 :y 2}))))
(deftest
"type-of returns stripped name"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(assert= "point" (type-of (make-point 1 2)))))
(deftest
"record is not a dict"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(assert= false (dict? (make-point 1 2)))
(assert= false (list? (make-point 1 2)))
(assert (record? (make-point 1 2))))))
(defsuite
"record-mutator"
(deftest
"set! via mutator"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y set-point-y!))
(let
((p (make-point 3 4)))
(set-point-y! p 99)
(assert= 99 (point-y p))
(assert= 3 (point-x p)))))
(deftest
"multiple mutations"
(do
(define-record-type <cell>
(make-cell value)
cell?
(value cell-value set-cell-value!))
(let
((c (make-cell 0)))
(set-cell-value! c 1)
(set-cell-value! c 2)
(set-cell-value! c 3)
(assert= 3 (cell-value c))))))
(defsuite
"record-generative"
(deftest
"distinct types with same fields"
(do
(define-record-type <a>
(make-a v)
a?
(v a-v))
(define-record-type <b>
(make-b v)
b?
(v b-v))
(let
((x (make-a 1))
(y (make-b 2)))
(assert (a? x))
(assert= false (a? y))
(assert= false (b? x))
(assert (b? y)))))
(deftest
"record? matches any record"
(do
(define-record-type <a>
(make-a v)
a?
(v a-v))
(define-record-type <b>
(make-b v)
b?
(v b-v))
(assert (record? (make-a 1)))
(assert (record? (make-b 2)))
(assert= false (record? 42)))))
(defsuite
"record-field-reorder"
(deftest
"constructor params in different order"
(do
(define-record-type <pair>
(make-pair second first)
pair?
(first pair-first)
(second pair-second))
(let
((p (make-pair 2 1)))
(assert= 1 (pair-first p))
(assert= 2 (pair-second p)))))
(deftest
"three fields reordered"
(do
(define-record-type <triple>
(make-triple c a b)
triple?
(a triple-a)
(b triple-b)
(c triple-c))
(let
((t (make-triple 30 10 20)))
(assert= 10 (triple-a t))
(assert= 20 (triple-b t))
(assert= 30 (triple-c t))))))
(defsuite
"record-nested"
(deftest
"records containing records"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(define-record-type <line>
(make-line start end)
line?
(start line-start)
(end line-end))
(let
((l (make-line (make-point 0 0) (make-point 3 4))))
(assert (line? l))
(assert (point? (line-start l)))
(assert= 0 (point-x (line-start l)))
(assert= 4 (point-y (line-end l)))))))
(defsuite
"record-equality"
(deftest
"equal records are equal"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(assert= (make-point 1 2) (make-point 1 2))))
(deftest
"different values are not equal"
(do
(define-record-type <point>
(make-point x y)
point?
(x point-x)
(y point-y))
(assert (not (equal? (make-point 1 2) (make-point 1 3)))))))

View File

@@ -0,0 +1,8 @@
{
"status": "failed",
"failedTests": [
"f74e9a54851d8fcfaffa-13e0d4c7d93026c85c6c",
"a2add2f401dce5f22243-5b9be27c24aceaec6daa",
"63f6db7ebc03cc4b82b9-ce6ce7a4bd4491603196"
]
}

View File

@@ -0,0 +1,63 @@
# Page snapshot
```yaml
- main [ref=e4]:
- generic [ref=e6]:
- complementary
- generic [ref=e7]:
- generic [ref=e8]:
- generic [ref=e11]:
- link "(<sx>)" [ref=e12] [cursor=pointer]:
- /url: /sx/
- generic [ref=e14]: (<sx>)
- paragraph [ref=e15]: The framework-free reactive hypermedium
- paragraph [ref=e17]: © Giles Bradshaw 2026· /sx/(geography.(reactive.(examples.reactive-list)))
- generic [ref=e18]:
- link "← Etc" [ref=e19] [cursor=pointer]:
- /url: /sx/(etc)
- link "Geography" [ref=e20] [cursor=pointer]:
- /url: /sx/(geography)
- link "Language →" [ref=e21] [cursor=pointer]:
- /url: /sx/(language)
- generic [ref=e22]:
- link "← Reactive Runtime" [ref=e23] [cursor=pointer]:
- /url: /sx/(geography.(reactive-runtime))
- link "Reactive Islands" [ref=e24] [cursor=pointer]:
- /url: /sx/(geography.(reactive))
- link "Hypermedia Lakes →" [ref=e25] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia))
- generic [ref=e26]:
- link "← Examples" [ref=e27] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples)))
- link "Examples" [ref=e28] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples)))
- link "Examples →" [ref=e29] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples)))
- generic [ref=e30]:
- link "← Imperative" [ref=e31] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples.imperative)))
- link "Reactive List" [ref=e32] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples.reactive-list)))
- link "Input Binding →" [ref=e33] [cursor=pointer]:
- /url: /sx/(geography.(reactive.(examples.input-binding)))
- generic [ref=e37]:
- paragraph [ref=e38]:
- text: When
- code [ref=e39]: map
- text: is used with
- code [ref=e40]: (deref signal)
- text: inside an island, it auto-upgrades to a reactive list. With
- code [ref=e41]: :key
- text: attributes, existing DOM nodes are reused across updates — only additions, removals, and reorderings touch the DOM.
- generic [ref=e43]:
- generic [ref=e44]:
- button "Add Item" [ref=e45] [cursor=pointer]
- generic [ref=e46]: 0 items
- list
- generic [ref=e48]: (defisland ~reactive-islands/index/demo-reactive-list () (let ((next-id (signal 1)) (items (signal (list))) (add-item (fn (e) (batch (fn () (swap! items (fn (old) (append old (dict "id" (deref next-id) "text" (str "Item " (deref next-id)))))) (swap! next-id inc))))) (remove-item (fn (id) (swap! items (fn (old) (filter (fn (item) (not (= (get item "id") id))) old)))))) (div (~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4") (div (~tw :tokens "flex items-center gap-3 mb-3") (button (~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700") :on-click add-item "Add Item") (span (~tw :tokens "text-sm text-stone-500") (deref (computed (fn () (len (deref items))))) " items")) (ul (~tw :tokens "space-y-1") (map (fn (item) (li :key (str (get item "id")) (~tw :tokens "flex items-center justify-between bg-white rounded px-3 py-2 text-sm") (span (get item "text")) (button (~tw :tokens "text-stone-400 hover:text-red-500 text-xs") :on-click (fn (e) (remove-item (get item "id"))) "✕"))) (deref items))))))
- paragraph [ref=e49]:
- code [ref=e50]: :key
- text: identifies each list item. When items change, the reconciler matches old and new keys — reusing existing DOM nodes, inserting new ones, and removing stale ones. Without keys, the list falls back to clear-and-rerender.
- code [ref=e51]: batch
- text: groups the two signal writes into one update pass.
```

View File

@@ -0,0 +1,82 @@
# Page snapshot
```yaml
- main [ref=e4]:
- generic [ref=e6]:
- complementary
- generic [ref=e7]:
- generic [ref=e8]:
- generic [ref=e11]:
- link "(<sx>)" [ref=e12] [cursor=pointer]:
- /url: /sx/
- generic [ref=e14]: (<sx>)
- paragraph [ref=e15]: The framework-free reactive hypermedium
- paragraph [ref=e17]: © Giles Bradshaw 2026· /sx/(geography.(hypermedia.(example.delete-row)))
- generic [ref=e18]:
- link "← Etc" [ref=e19] [cursor=pointer]:
- /url: /sx/(etc)
- link "Geography" [ref=e20] [cursor=pointer]:
- /url: /sx/(geography)
- link "Language →" [ref=e21] [cursor=pointer]:
- /url: /sx/(language)
- generic [ref=e22]:
- link "← Reactive Islands" [ref=e23] [cursor=pointer]:
- /url: /sx/(geography.(reactive))
- link "Hypermedia Lakes" [ref=e24] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia))
- link "Scopes →" [ref=e25] [cursor=pointer]:
- /url: /sx/(geography.(scopes))
- generic [ref=e26]:
- link "← Reference" [ref=e27] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(reference)))
- link "Examples" [ref=e28] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(example)))
- link "Reference →" [ref=e29] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(reference)))
- generic [ref=e30]:
- link "← Polling" [ref=e31] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(example.polling)))
- link "Delete Row" [ref=e32] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(example.delete-row)))
- link "Inline Edit →" [ref=e33] [cursor=pointer]:
- /url: /sx/(geography.(hypermedia.(example.inline-edit)))
- generic [ref=e37]:
- paragraph [ref=e38]: sx-delete with sx-swap "outerHTML" and an empty response removes the row from the DOM.
- generic [ref=e40]:
- generic [ref=e41]:
- heading "Demo" [level=3] [ref=e42]
- paragraph [ref=e43]: Click delete to remove a row. Uses sx-confirm for confirmation.
- table [ref=e47]:
- rowgroup [ref=e48]:
- row "Item" [ref=e49]:
- columnheader "Item" [ref=e50]
- columnheader [ref=e51]
- rowgroup [ref=e52]:
- row "Fix login bug delete" [ref=e53]:
- cell "Fix login bug" [ref=e54]
- cell "delete" [ref=e55]:
- button "delete" [ref=e56] [cursor=pointer]
- row "Write documentation delete" [ref=e57]:
- cell "Write documentation" [ref=e58]
- cell "delete" [ref=e59]:
- button "delete" [ref=e60] [cursor=pointer]
- row "Deploy to production delete" [ref=e61]:
- cell "Deploy to production" [ref=e62]
- cell "delete" [ref=e63]:
- button "delete" [ref=e64] [cursor=pointer]
- row "Add unit tests delete" [ref=e65]:
- cell "Add unit tests" [ref=e66]
- cell "delete" [ref=e67]:
- button "delete" [ref=e68] [cursor=pointer]
- heading "S-expression" [level=3] [ref=e69]
- code [ref=e73]: (button :sx-delete "/sx/(geography.(hypermedia.(example.(api.(delete.1)))))" :sx-target "#row-1" :sx-swap "outerHTML" :sx-confirm "Delete this item?" "delete")
- heading "Component" [level=3] [ref=e74]
- code [ref=e79]: (defcomp ~examples/delete-row (id name) (tr :id (str "row-" id) (~tw :tokens "border-b border-stone-100 transition-all") (td (~tw :tokens "px-3 py-2 text-stone-700") name) (td (~tw :tokens "px-3 py-2") (button :sx-delete (str "/sx/(geography.(hypermedia.(example.(api.(delete." id ")))))") :sx-target (str "#row-" id) :sx-swap "outerHTML" :sx-confirm "Delete this item?" (~tw :tokens "text-rose-500 hover:text-rose-700 text-sm") "delete"))))
- heading "Server handler" [level=3] [ref=e80]
- code [ref=e84]: (:path "/sx/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))" :method :delete :csrf false ("item-id") (<> (~docs/oob-code :target-id "delete-comp" :text (helper "component-source" "~examples/delete-row")) (~docs/oob-code :target-id "delete-wire" :text "(empty — row removed by outerHTML swap)")))
- generic [ref=e85]:
- heading "Wire response" [level=3] [ref=e86]
- button "Clear component cache" [ref=e87] [cursor=pointer]
- paragraph [ref=e88]: Empty body — outerHTML swap replaces the target element with nothing.
- code [ref=e93]: (empty — row removed by outerHTML swap)
```