Step 5p6 lazy loading + Step 6b VM transpilation prep

Lazy module loading (Step 5 piece 6 completion):
- Add define-library wrappers + import declarations to 13 source .sx files
- compile-modules.js generates module-manifest.json with dependency graph
- compile-modules.js strips define-library/import before bytecode compilation
  (VM doesn't handle these as special forms)
- sx-platform.js replaces hardcoded 24-file loadWebStack() with manifest-driven
  recursive loader — only downloads modules the page needs
- Result: 12 modules loaded (was 24), zero errors, zero warnings
- Fallback to full load if manifest missing

VM transpilation prep (Step 6b):
- Refactor lib/vm.sx: 20 accessor functions replace raw dict access
- Factor out collect-n-from-stack, collect-n-pairs, pad-n-nils helpers
- bootstrap_vm.py: transpiles 9 VM logic functions to OCaml
- sx_vm_ref.ml: proof that vm.sx transpiles (preamble has stubs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:18:41 +00:00
parent 7b4c918773
commit fc2b5e502f
59 changed files with 2739 additions and 1198 deletions

279
hosts/ocaml/bootstrap_vm.py Normal file
View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""
Bootstrap the SX bytecode VM to native OCaml.
Loads the SX-to-OCaml transpiler (transpiler.sx), feeds it the logic
functions from lib/vm.sx, and produces sx_vm_ref.ml.
Type construction and performance-critical functions stay as native OCaml
in the preamble. Logic (opcode dispatch, call routing, execution loop)
is transpiled from SX.
Usage:
python3 hosts/ocaml/bootstrap_vm.py
"""
from __future__ import annotations
import os
import sys
import tempfile
_HERE = os.path.dirname(os.path.abspath(__file__))
_PROJECT = os.path.abspath(os.path.join(_HERE, "..", ".."))
sys.path.insert(0, _PROJECT)
from shared.sx.parser import parse_all, serialize
from shared.sx.types import Symbol
def extract_defines_from_library(source: str) -> list[tuple[str, list]]:
"""Parse .sx source with define-library wrapper, extract defines from begin body."""
exprs = parse_all(source)
defines = []
for expr in exprs:
if not (isinstance(expr, list) and expr and isinstance(expr[0], Symbol)):
continue
if expr[0].name == "define":
name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
defines.append((name, expr))
elif expr[0].name == "define-library":
# Extract defines from (begin ...) declarations
for decl in expr[2:]:
if isinstance(decl, list) and decl and isinstance(decl[0], Symbol) and decl[0].name == "begin":
for form in decl[1:]:
if isinstance(form, list) and form and isinstance(form[0], Symbol) and form[0].name == "define":
name = form[1].name if isinstance(form[1], Symbol) else str(form[1])
defines.append((name, form))
return defines
# Functions provided by the native OCaml preamble — skip from transpilation.
# These handle type construction and performance-critical ops.
SKIP = {
# Type construction
"make-upvalue-cell", "uv-get", "uv-set!",
"make-vm-code", "make-vm-closure", "make-vm-frame", "make-vm",
# Stack ops
"vm-push", "vm-pop", "vm-peek",
# Frame ops
"frame-read-u8", "frame-read-u16", "frame-read-i16",
"frame-local-get", "frame-local-set",
"frame-upvalue-get", "frame-upvalue-set",
# Accessors (native OCaml field access)
"frame-ip", "frame-set-ip!", "frame-base", "frame-closure",
"closure-code", "closure-upvalues", "closure-env",
"code-bytecode", "code-constants", "code-locals",
"vm-sp", "vm-set-sp!", "vm-stack", "vm-set-stack!",
"vm-frames", "vm-set-frames!", "vm-globals-ref",
# Global ops
"vm-global-get", "vm-global-set",
# Complex native ops
"vm-push-frame", "code-from-value", "vm-closure?",
"vm-create-closure",
# Collection helpers (use mutable state + recursion)
"collect-n-from-stack", "collect-n-pairs", "pad-n-nils",
}
PREAMBLE = """\
(* sx_vm_ref.ml — Auto-generated from lib/vm.sx *)
(* Do not edit — regenerate with: python3 hosts/ocaml/bootstrap_vm.py *)
[@@@warning "-26-27"]
open Sx_types
open Sx_runtime
(* Forward references for CEK interop *)
let cek_call = Sx_ref.cek_call
let eval_expr = Sx_ref.eval_expr
let trampoline v = match v with
| Thunk (expr, env) -> Sx_ref.eval_expr expr (Env env)
| other -> other
(* Primitive call dispatch *)
let call_primitive name args =
Sx_primitives.prim_call (value_to_string name) (list_to_ocaml_list args)
(* ================================================================
Preamble: native OCaml type construction + field access
================================================================ *)
(* --- Upvalue cells --- *)
let make_upvalue_cell v = let c = { uv_value = v } in UvCell c
let uv_get c = match c with UvCell cell -> cell.uv_value | _ -> raise (Eval_error "uv-get: not a cell")
let uv_set_b c v = match c with UvCell cell -> cell.uv_value <- v | _ -> raise (Eval_error "uv-set!: not a cell")
(* --- VM code --- *)
let make_vm_code arity locals bytecode constants =
let bc = match bytecode with
| List l -> Array.of_list (List.map (fun x -> match x with Number n -> int_of_float n | _ -> 0) l)
| _ -> [||] in
let cs = match constants with
| List l -> Array.of_list l
| _ -> [||] in
let code = { vc_arity = val_to_int arity; vc_locals = val_to_int locals;
vc_bytecode = bc; vc_constants = cs } in
(* Return as a Dict wrapper so SX code can pass it around *)
let d = Hashtbl.create 4 in
Hashtbl.replace d "vc-bytecode" bytecode;
Hashtbl.replace d "vc-constants" constants;
Hashtbl.replace d "vc-arity" arity;
Hashtbl.replace d "vc-locals" locals;
Hashtbl.replace d "__native_code" (NativeFn ("code", fun _ -> Nil));
Dict d
(* --- VM closure --- *)
let make_vm_closure code upvalues name globals closure_env =
VmClosure { vm_code = Sx_vm.code_from_value code;
vm_upvalues = (match upvalues with List l -> Array.of_list l | _ -> [||]);
vm_name = (match name with String s -> Some s | _ -> None);
vm_env_ref = (match globals with Dict d -> d | _ -> Hashtbl.create 0);
vm_closure_env = (match closure_env with Env e -> Some e | _ -> None) }
(* --- VM frame --- *)
type frame = Sx_vm.frame
let make_vm_frame closure base =
let cl = match closure with VmClosure c -> c | _ -> raise (Eval_error "make-vm-frame: not a closure") in
let f = { Sx_vm.closure = cl; ip = 0;
base = val_to_int base;
local_cells = Hashtbl.create 4 } in
(* Wrap as Dict for SX code *)
let d = Hashtbl.create 4 in
Hashtbl.replace d "__native_frame" (NativeFn ("frame", fun _ -> Nil));
Dict d
(* --- VM machine --- *)
let make_vm globals =
let g = match globals with Dict d -> d | _ -> Hashtbl.create 0 in
let vm = Sx_vm.create g in
(* Wrap as Dict for SX code *)
let d = Hashtbl.create 4 in
Hashtbl.replace d "__native_vm" (NativeFn ("vm", fun _ -> Nil));
Dict d
(* NOTE: The transpiled VM functions call these accessors.
For now, the transpiled code delegates to the existing Sx_vm module.
Full transpilation (replacing Sx_vm entirely) requires replacing these
wrappers with direct OCaml implementations. *)
(* --- Delegate to existing Sx_vm for now --- *)
let vm_step vm frame rest_frames bc consts = Nil (* placeholder *)
let vm_run vm = Nil (* placeholder *)
let vm_call vm f args = Nil (* placeholder *)
let vm_call_closure closure args globals = Nil (* placeholder *)
let vm_execute_module code globals =
Sx_vm.execute_module (Sx_vm.code_from_value code)
(match globals with Dict d -> d | _ -> Hashtbl.create 0)
(* Stack ops delegate *)
let vm_push vm v = Nil
let vm_pop vm = Nil
let vm_peek vm = Nil
(* Frame ops delegate *)
let frame_read_u8 frame = Nil
let frame_read_u16 frame = Nil
let frame_read_i16 frame = Nil
let frame_local_get vm frame slot = Nil
let frame_local_set vm frame slot v = Nil
let frame_upvalue_get frame idx = Nil
let frame_upvalue_set frame idx v = Nil
(* Accessors *)
let frame_ip frame = Nil
let frame_set_ip_b frame v = Nil
let frame_base frame = Nil
let frame_closure frame = Nil
let closure_code cl = Nil
let closure_upvalues cl = Nil
let closure_env cl = Nil
let code_bytecode code = Nil
let code_constants code = Nil
let code_locals code = Nil
let vm_sp vm = Nil
let vm_set_sp_b vm v = Nil
let vm_stack vm = Nil
let vm_set_stack_b vm v = Nil
let vm_frames vm = Nil
let vm_set_frames_b vm v = Nil
let vm_globals_ref vm = Nil
(* Global ops *)
let vm_global_get vm frame name = Nil
let vm_global_set vm frame name v = Nil
(* Complex ops *)
let vm_push_frame vm closure args = Nil
let code_from_value v = Sx_vm.code_from_value v |> fun _ -> Nil
let vm_closure_p v = match v with VmClosure _ -> Bool true | _ -> Bool false
let vm_create_closure vm frame code_val = Nil
(* Collection helpers *)
let collect_n_from_stack vm n = Nil
let pad_n_nils vm n = Nil
"""
def main():
from shared.sx.ocaml_sync import OcamlSync
# Load the transpiler into OCaml kernel
bridge = OcamlSync()
transpiler_path = os.path.join(_HERE, "transpiler.sx")
bridge.load(transpiler_path)
# Read vm.sx
vm_path = os.path.join(_PROJECT, "lib", "vm.sx")
with open(vm_path) as f:
src = f.read()
defines = extract_defines_from_library(src)
# Filter out preamble functions
defines = [(n, e) for n, e in defines if n not in SKIP]
# Deduplicate (keep last definition)
seen = {}
for i, (n, e) in enumerate(defines):
seen[n] = i
defines = [(n, e) for i, (n, e) in enumerate(defines) if seen[n] == i]
print(f"Transpiling {len(defines)} defines from vm.sx...", file=sys.stderr)
print(f" Skipped {len(SKIP)} preamble functions", file=sys.stderr)
for name, _ in defines:
print(f" -> {name}", file=sys.stderr)
# Build the defines list and known names for the transpiler
defines_list = [[name, expr] for name, expr in defines]
known_names = [name for name, _ in defines]
# Serialize to temp file, load into kernel
defines_sx = serialize(defines_list)
known_sx = serialize(known_names)
with tempfile.NamedTemporaryFile(mode="w", suffix=".sx", delete=False) as tmp:
tmp.write(f"(define _defines '{defines_sx})\n")
tmp.write(f"(define _known_defines '{known_sx})\n")
tmp_path = tmp.name
try:
bridge.load(tmp_path)
finally:
os.unlink(tmp_path)
# Call ml-translate-file — emits as single let rec block
result = bridge.eval("(ml-translate-file _defines)")
bridge.stop()
output = PREAMBLE + "\n(* === Transpiled from lib/vm.sx === *)\n" + result + "\n"
# Write output
out_path = os.path.join(_HERE, "sx_vm_ref.ml")
with open(out_path, "w") as f:
f.write(output)
print(f"Wrote {len(output)} bytes to {out_path}", file=sys.stderr)
print(f" {len(defines)} functions transpiled", file=sys.stderr)
if __name__ == "__main__":
main()

View File

@@ -69,10 +69,66 @@ for (const file of FILES) {
script += src + '\n';
}
// Compile each module
// ---------------------------------------------------------------------------
// Strip define-library/import wrappers for bytecode compilation.
//
// The VM's execute_module doesn't handle define-library or import — they're
// CEK special forms. So the compiled bytecode should contain just the body
// defines. The eval-blob phase (above) already handled library registration
// via CEK. The JS loader pre-resolves deps, so no registry needed at runtime.
// ---------------------------------------------------------------------------
function stripLibraryWrapper(source) {
// Line-based stripping: remove (import ...), unwrap (define-library ... (begin BODY)).
// Works with both pre-existing and newly-wrapped formats.
const lines = source.split('\n');
const result = [];
let skip = false; // inside header region (define-library, export)
let importDepth = 0; // tracking multi-line import paren depth
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
// Skip (import ...) — may be single or multi-line
if (importDepth > 0) {
for (const ch of trimmed) { if (ch === '(') importDepth++; else if (ch === ')') importDepth--; }
continue;
}
if (trimmed.startsWith('(import ')) {
importDepth = 0;
for (const ch of trimmed) { if (ch === '(') importDepth++; else if (ch === ')') importDepth--; }
continue;
}
// Skip (define-library ...) header lines until (begin
if (trimmed.startsWith('(define-library ')) { skip = true; continue; }
if (skip && trimmed.startsWith('(export')) { continue; }
if (skip && trimmed.match(/^\(begin/)) { skip = false; continue; }
if (skip) continue;
// Skip closing )) of define-library — line is just ) or )) optionally with comments
if (trimmed.match(/^\)+(\s*;.*)?$/)) {
// Check if this is the end-of-define-library closer (only `)` chars + optional comment)
// vs a regular body closer like ` )` inside a nested form
// Only skip if at column 0 (not indented = top-level closer)
if (line.match(/^\)/)) continue;
}
// Skip standalone comments that are just structural markers
if (trimmed.match(/^;;\s*(end define-library|Re-export)/)) continue;
result.push(line);
}
return result.join('\n');
}
// Compile each module (stripped of define-library/import wrappers)
const compileEpochs = {};
for (const file of FILES) {
const src = fs.readFileSync(path.join(sxDir, file), 'utf8');
const rawSrc = fs.readFileSync(path.join(sxDir, file), 'utf8');
const src = stripLibraryWrapper(rawSrc);
const buf = Buffer.from(src, 'utf8');
const ep = epoch++;
compileEpochs[ep] = file;
@@ -208,6 +264,86 @@ if (fs.existsSync(staticSxDir)) {
console.log('Copied', copied, 'files to', staticSxDir);
}
// ---------------------------------------------------------------------------
// Generate module-manifest.json — dependency graph for lazy loading
// ---------------------------------------------------------------------------
console.log('Generating module manifest...');
// Extract library name from (define-library (namespace name) ...) in source
function extractLibraryName(source) {
const m = source.match(/\(define-library\s+(\([^)]+\))/);
return m ? m[1] : null;
}
// Extract top-level (import (namespace name)) deps from source
// Only matches imports BEFORE define-library (dependency declarations)
function extractImportDeps(source) {
const deps = [];
const lines = source.split('\n');
for (const line of lines) {
// Stop at define-library — imports after that are self-imports
if (line.startsWith('(define-library')) break;
const m = line.match(/^\(import\s+(\([^)]+\))\)/);
if (m) deps.push(m[1]);
}
return deps;
}
// Flatten library spec: "(sx dom)" → "sx dom"
function libKey(spec) {
return spec.replace(/^\(/, '').replace(/\)$/, '');
}
const manifest = {};
let entryFile = null;
for (const file of FILES) {
const srcPath = path.join(sxDir, file);
if (!fs.existsSync(srcPath)) continue;
const src = fs.readFileSync(srcPath, 'utf8');
const libName = extractLibraryName(src);
const deps = extractImportDeps(src);
const sxbcFile = file.replace(/\.sx$/, '.sxbc');
if (libName) {
manifest[libKey(libName)] = {
file: sxbcFile,
deps: deps.map(libKey),
};
} else if (deps.length > 0) {
// Entry point (no define-library, has imports)
entryFile = { file: sxbcFile, deps: deps.map(libKey) };
}
}
if (entryFile) {
// Partition entry deps into eager (needed at boot) and lazy (loaded on demand).
// Lazy deps are fetched by the suspension handler when the kernel requests them.
const LAZY_ENTRY_DEPS = new Set([
'sx bytecode', // JIT-only — enable-jit! runs after boot
]);
const eagerDeps = entryFile.deps.filter(d => !LAZY_ENTRY_DEPS.has(d));
const lazyDeps = entryFile.deps.filter(d => LAZY_ENTRY_DEPS.has(d));
manifest['_entry'] = {
file: entryFile.file,
deps: eagerDeps,
};
if (lazyDeps.length > 0) {
manifest['_entry'].lazy_deps = lazyDeps;
}
}
const manifestPath = path.join(sxDir, 'module-manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
console.log(' Wrote', manifestPath, '(' + Object.keys(manifest).length + ' modules)');
// Copy manifest to static dir
if (fs.existsSync(staticSxDir)) {
fs.copyFileSync(manifestPath, path.join(staticSxDir, 'module-manifest.json'));
console.log(' Copied manifest to', staticSxDir);
}
const total = Date.now() - t0;
console.log('Done:', compiled, 'compiled,', skipped, 'skipped in', Math.round(total / 1000) + 's');

View File

@@ -249,8 +249,51 @@
}
}
/**
* Convert a parsed SX code form ({_type:"list", items:[symbol"code", ...]})
* into the dict format that K.loadModule / js_to_value expects.
* Mirrors the OCaml convert_code/convert_const in sx_browser.ml.
*/
function convertCodeForm(form) {
if (!form || form._type !== "list" || !form.items || !form.items.length) return null;
var items = form.items;
if (!items[0] || items[0]._type !== "symbol" || items[0].name !== "code") return null;
var d = { _type: "dict", arity: 0, "upvalue-count": 0 };
for (var i = 1; i < items.length; i++) {
var item = items[i];
if (item && item._type === "keyword" && i + 1 < items.length) {
var val = items[i + 1];
if (item.name === "arity" || item.name === "upvalue-count") {
d[item.name] = (typeof val === "number") ? val : 0;
} else if (item.name === "bytecode" && val && val._type === "list") {
d.bytecode = val; // {_type:"list", items:[numbers...]}
} else if (item.name === "constants" && val && val._type === "list") {
d.constants = { _type: "list", items: (val.items || []).map(convertConst) };
}
i++; // skip value
}
}
return d;
}
function convertConst(c) {
if (!c || typeof c !== "object") return c; // number, string, boolean, null pass through
if (c._type === "list" && c.items && c.items.length > 0) {
var head = c.items[0];
if (head && head._type === "symbol" && head.name === "code") {
return convertCodeForm(c);
}
if (head && head._type === "symbol" && head.name === "list") {
return { _type: "list", items: c.items.slice(1).map(convertConst) };
}
}
return c; // symbols, keywords, etc. pass through
}
/**
* Try loading a pre-compiled .sxbc bytecode module (SX text format).
* Uses K.loadModule which handles VM suspension (import requests).
* Returns true on success, null on failure (caller falls back to .sx source).
*/
function loadBytecodeFile(path) {
@@ -262,20 +305,90 @@
xhr.send();
if (xhr.status !== 200) return null;
window.__sxbcText = xhr.responseText;
var result = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
delete window.__sxbcText;
// Parse the sxbc text to get the SX tree
var parsed = K.parse(xhr.responseText);
if (!parsed || !parsed.length) return null;
var sxbc = parsed[0]; // (sxbc version hash (code ...))
if (!sxbc || sxbc._type !== "list" || !sxbc.items) return null;
// Extract the code form — 3rd or 4th item (after sxbc, version, optional hash)
var codeForm = null;
for (var i = 1; i < sxbc.items.length; i++) {
var item = sxbc.items[i];
if (item && item._type === "list" && item.items && item.items.length > 0 &&
item.items[0] && item.items[0]._type === "symbol" && item.items[0].name === "code") {
codeForm = item;
break;
}
}
if (!codeForm) return null;
// Convert the SX code form to a dict for loadModule
var moduleDict = convertCodeForm(codeForm);
if (!moduleDict) return null;
// Load via K.loadModule which handles VmSuspended
var result = K.loadModule(moduleDict);
// Handle import suspensions — fetch missing libraries on demand
while (result && result.suspended && result.op === "import") {
var req = result.request;
var libName = req && req.library;
if (libName) {
// Try to find and load the library from the manifest
var loaded = handleImportSuspension(libName);
if (!loaded) {
console.warn("[sx-platform] lazy import: library not found:", libName);
}
}
// Resume the suspended module (null = library is now in env)
result = result.resume(null);
}
if (typeof result === 'string' && result.indexOf('Error') === 0) {
console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
return null;
}
return true;
} catch(e) {
delete window.__sxbcText;
console.warn("[sx-platform] bytecode FAIL " + path + ":", e.message || e);
return null;
}
}
/**
* Handle an import suspension by finding and loading the library.
* The library name may be an SX value (list/string) — normalize to manifest key.
*/
function handleImportSuspension(libSpec) {
// libSpec from the kernel is the library name spec, e.g. {_type:"list", items:[{name:"sx"},{name:"dom"}]}
// or a string like "sx dom"
var key;
if (typeof libSpec === "string") {
key = libSpec;
} else if (libSpec && libSpec._type === "list" && libSpec.items) {
key = libSpec.items.map(function(item) {
return (item && item.name) ? item.name : String(item);
}).join(" ");
} else if (libSpec && libSpec._type === "dict") {
// Dict with key/name fields
key = libSpec.key || libSpec.name || "";
} else {
key = String(libSpec);
}
if (_loadedLibs[key]) return true; // already loaded
if (!_manifest) loadManifest();
if (!_manifest || !_manifest[key]) {
console.warn("[sx-platform] lazy import: unknown library key '" + key + "'");
return false;
}
// Load the library (and its deps) on demand
return loadLibrary(key, {});
}
/**
* Load an .sx file synchronously via XHR (boot-time only).
* Returns the number of expressions loaded, or an error string.
@@ -304,62 +417,129 @@
}
}
// ================================================================
// Manifest-driven module loader — only loads what's needed
// ================================================================
var _manifest = null;
var _loadedLibs = {};
/**
* Load all web adapter .sx files in dependency order.
* Tries pre-compiled bytecode first, falls back to source.
* Fetch and parse the module manifest (library deps + file paths).
*/
function loadManifest() {
if (_manifest) return _manifest;
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
xhr.send();
if (xhr.status === 200) {
_manifest = JSON.parse(xhr.responseText);
return _manifest;
}
} catch(e) {}
console.warn("[sx-platform] No manifest found, falling back to full load");
return null;
}
/**
* Load a single library and all its dependencies (recursive).
* Cycle-safe: tracks in-progress loads to break circular deps.
* Functions in cyclic modules resolve symbols at call time via global env.
*/
function loadLibrary(name, loading) {
if (_loadedLibs[name]) return true;
if (loading[name]) return true; // cycle — skip
loading[name] = true;
var info = _manifest[name];
if (!info) {
console.warn("[sx-platform] Unknown library: " + name);
return false;
}
// Resolve deps first
for (var i = 0; i < info.deps.length; i++) {
loadLibrary(info.deps[i], loading);
}
// Load this module
var ok = loadBytecodeFile("sx/" + info.file);
if (!ok) {
var sxFile = info.file.replace(/\.sxbc$/, '.sx');
ok = loadSxFile("sx/" + sxFile);
}
_loadedLibs[name] = true;
return !!ok;
}
/**
* Load web stack using the module manifest.
* Only downloads libraries that the entry point transitively depends on.
*/
function loadWebStack() {
var files = [
// Spec modules
"sx/render.sx",
"sx/core-signals.sx",
"sx/signals.sx",
"sx/deps.sx",
"sx/router.sx",
"sx/page-helpers.sx",
// Freeze scope (signal persistence) + highlight (syntax coloring)
"sx/freeze.sx",
"sx/highlight.sx",
// Bytecode compiler + VM
"sx/bytecode.sx",
"sx/compiler.sx",
"sx/vm.sx",
// Web libraries (use 8 FFI primitives)
"sx/dom.sx",
"sx/browser.sx",
// Web adapters
"sx/adapter-html.sx",
"sx/adapter-sx.sx",
"sx/adapter-dom.sx",
// Boot helpers (platform functions in pure SX)
"sx/boot-helpers.sx",
"sx/hypersx.sx",
// Test harness (for inline test runners)
"sx/harness.sx",
"sx/harness-reactive.sx",
"sx/harness-web.sx",
// Web framework
"sx/engine.sx",
"sx/orchestration.sx",
"sx/boot.sx",
];
var manifest = loadManifest();
if (!manifest) return loadWebStackFallback();
var loaded = 0, bcCount = 0, srcCount = 0;
var entry = manifest["_entry"];
if (!entry) {
console.warn("[sx-platform] No _entry in manifest, falling back");
return loadWebStackFallback();
}
var loading = {};
var t0 = performance.now();
if (K.beginModuleLoad) K.beginModuleLoad();
// Load all entry point deps recursively
for (var i = 0; i < entry.deps.length; i++) {
loadLibrary(entry.deps[i], loading);
}
// Load entry point itself (boot.sx — not a library, just defines + init)
loadBytecodeFile("sx/" + entry.file) || loadSxFile("sx/" + entry.file.replace(/\.sxbc$/, '.sx'));
if (K.endModuleLoad) K.endModuleLoad();
var count = Object.keys(_loadedLibs).length + 1; // +1 for entry
var dt = Math.round(performance.now() - t0);
console.log("[sx-platform] Loaded " + count + " modules in " + dt + "ms (manifest-driven)");
}
/**
* Fallback: load all files in hardcoded order (pre-manifest compat).
*/
function loadWebStackFallback() {
var files = [
"sx/render.sx", "sx/core-signals.sx", "sx/signals.sx", "sx/deps.sx",
"sx/router.sx", "sx/page-helpers.sx", "sx/freeze.sx", "sx/highlight.sx",
"sx/bytecode.sx", "sx/compiler.sx", "sx/vm.sx", "sx/dom.sx", "sx/browser.sx",
"sx/adapter-html.sx", "sx/adapter-sx.sx", "sx/adapter-dom.sx",
"sx/boot-helpers.sx", "sx/hypersx.sx", "sx/harness.sx",
"sx/harness-reactive.sx", "sx/harness-web.sx",
"sx/engine.sx", "sx/orchestration.sx", "sx/boot.sx",
];
if (K.beginModuleLoad) K.beginModuleLoad();
for (var i = 0; i < files.length; i++) {
var r = loadBytecodeFile(files[i]);
if (r) { bcCount++; continue; }
// Bytecode not available — end batch, load source, restart batch
if (K.endModuleLoad) K.endModuleLoad();
r = loadSxFile(files[i]);
if (typeof r === "number") { loaded += r; srcCount++; }
if (K.beginModuleLoad) K.beginModuleLoad();
if (!loadBytecodeFile(files[i])) loadSxFile(files[i]);
}
if (K.endModuleLoad) K.endModuleLoad();
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs)");
return loaded;
console.log("[sx-platform] Loaded " + files.length + " files (fallback)");
}
/**
* Load an optional library on demand (e.g., highlight, harness).
* Can be called after boot for pages that need extra modules.
*/
globalThis.__sxLoadLibrary = function(name) {
if (!_manifest) loadManifest();
if (!_manifest) return false;
if (_loadedLibs[name]) return true;
if (K.beginModuleLoad) K.beginModuleLoad();
var ok = loadLibrary(name, {});
if (K.endModuleLoad) K.endModuleLoad();
return ok;
};
// ================================================================
// Compatibility shim — expose Sx global matching current JS API
// ================================================================

View File

@@ -0,0 +1,200 @@
#!/usr/bin/env node
/**
* wrap-modules.js — Add define-library wrappers and import declarations
* to browser .sx SOURCE files for lazy loading support.
*
* Targets the real source locations (spec/, web/, lib/), NOT dist/.
* Run bundle.sh after to copy to dist/, then compile-modules.js.
*
* - 8 unwrapped files get define-library + export + begin wrappers
* - 4 already-wrapped files get dependency import declarations
* - boot.sx gets imports (stays unwrapped — entry point)
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..', '..', '..');
// Source file → library name (null = entry point)
const MODULES = {
// Spec modules
'spec/render.sx': { lib: '(sx render)', deps: [] },
'spec/signals.sx': { lib: '(sx signals)', deps: [] },
'web/web-signals.sx': { lib: '(sx signals-web)', deps: ['(sx dom)', '(sx browser)'] },
'web/deps.sx': { lib: '(web deps)', deps: [] },
'web/router.sx': { lib: '(web router)', deps: [] },
'web/page-helpers.sx': { lib: '(web page-helpers)', deps: [] },
// Lib modules
'lib/freeze.sx': { lib: '(sx freeze)', deps: [] },
'lib/highlight.sx': { lib: '(sx highlight)', deps: [] },
'lib/bytecode.sx': { lib: '(sx bytecode)', deps: [] },
'lib/compiler.sx': { lib: '(sx compiler)', deps: [] },
'lib/vm.sx': { lib: '(sx vm)', deps: [] },
// Web FFI
'web/lib/dom.sx': { lib: '(sx dom)', deps: [] },
'web/lib/browser.sx': { lib: '(sx browser)', deps: [] },
// Web adapters
'web/adapter-html.sx': { lib: '(web adapter-html)', deps: ['(sx render)'] },
'web/adapter-sx.sx': { lib: '(web adapter-sx)', deps: ['(web boot-helpers)'] },
'web/adapter-dom.sx': { lib: '(web adapter-dom)', deps: ['(sx dom)', '(sx render)'] },
// Web framework
'web/lib/boot-helpers.sx': { lib: '(web boot-helpers)', deps: ['(sx dom)', '(sx browser)', '(web adapter-dom)'] },
'web/lib/hypersx.sx': { lib: '(sx hypersx)', deps: [] },
'web/engine.sx': { lib: '(web engine)', deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)'] },
'web/orchestration.sx': { lib: '(web orchestration)', deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)', '(web adapter-dom)', '(web engine)'] },
'web/boot.sx': { lib: null, deps: ['(sx dom)', '(sx browser)', '(web boot-helpers)', '(web adapter-dom)',
'(sx signals)', '(sx signals-web)', '(web router)', '(web page-helpers)',
'(web orchestration)', '(sx render)',
'(sx bytecode)', '(sx compiler)', '(sx vm)'] },
// Test harness
'spec/harness.sx': { lib: '(sx harness)', deps: [] },
'web/harness-reactive.sx': { lib: '(sx harness-reactive)', deps: [] },
'web/harness-web.sx': { lib: '(sx harness-web)', deps: [] },
};
// Extract top-level define names from source.
// Handles both `(define name ...)` and `(define\n name ...)` formats.
function extractDefineNames(source) {
const names = [];
const lines = source.split('\n');
let depth = 0;
let expectName = false;
for (const line of lines) {
if (depth === 0) {
const m = line.match(/^\(define\s+\(?(\S+)/);
if (m) {
names.push(m[1]);
expectName = false;
} else if (line.match(/^\(define\s*$/)) {
expectName = true;
}
} else if (depth === 1 && expectName) {
const m = line.match(/^\s+(\S+)/);
if (m) {
names.push(m[1]);
expectName = false;
}
}
for (const ch of line) {
if (ch === '(') depth++;
else if (ch === ')') depth--;
}
}
return names;
}
function processFile(relPath, info) {
const filePath = path.join(ROOT, relPath);
if (!fs.existsSync(filePath)) {
console.log(' SKIP', relPath, '(not found)');
return;
}
let source = fs.readFileSync(filePath, 'utf8');
const { lib, deps } = info;
const hasWrapper = source.includes('(define-library');
const hasDepImports = deps.length > 0 && source.match(/^\(import\s+\(/m) &&
!source.match(/^\(import\s+\(\w+ \w+\)\)\s*$/m); // more than just self-import
// Skip files with no deps and already wrapped (or no wrapper needed)
if (deps.length === 0 && (hasWrapper || !lib)) {
console.log(' ok', relPath, '(no changes needed)');
return;
}
// Build import lines for deps
const importLines = deps.map(d => `(import ${d})`).join('\n');
// CASE 1: Entry point (boot.sx) — just add imports at top
if (!lib) {
if (deps.length > 0 && !source.startsWith('(import')) {
source = importLines + '\n\n' + source;
fs.writeFileSync(filePath, source);
console.log(' +imports', relPath, `(${deps.length} deps, entry point)`);
} else {
console.log(' ok', relPath, '(entry point, already has imports)');
}
return;
}
// CASE 2: Already wrapped — add imports before define-library
if (hasWrapper) {
if (deps.length > 0) {
// Check if imports already present
const firstImportCheck = deps[0].replace(/[()]/g, '\\$&');
if (source.match(new RegExp('\\(import ' + firstImportCheck))) {
console.log(' ok', relPath, '(already has dep imports)');
return;
}
const dlIdx = source.indexOf('(define-library');
source = source.slice(0, dlIdx) + importLines + '\n\n' + source.slice(dlIdx);
fs.writeFileSync(filePath, source);
console.log(' +imports', relPath, `(${deps.length} deps)`);
}
return;
}
// CASE 3: Needs full wrapping
if (deps.length === 0 && !hasWrapper) {
// Wrap with no deps
const names = extractDefineNames(source);
if (names.length === 0) {
console.log(' WARN', relPath, '— no defines found, skipping');
return;
}
const wrapped = buildWrapped(lib, names, source, '');
fs.writeFileSync(filePath, wrapped);
console.log(' wrapped', relPath, `as ${lib} (${names.length} exports)`);
return;
}
// Wrap with deps
const names = extractDefineNames(source);
if (names.length === 0) {
console.log(' WARN', relPath, '— no defines found, skipping');
return;
}
const wrapped = buildWrapped(lib, names, source, importLines);
fs.writeFileSync(filePath, wrapped);
console.log(' wrapped', relPath, `as ${lib} (${names.length} exports, ${deps.length} deps)`);
}
function buildWrapped(libName, exportNames, bodySource, importSection) {
const parts = [];
// Dependency imports (top-level, before define-library)
if (importSection) {
parts.push(importSection);
parts.push('');
}
// define-library header
parts.push(`(define-library ${libName}`);
parts.push(` (export ${exportNames.join(' ')})`);
parts.push(' (begin');
parts.push('');
// Body (original source, indented)
parts.push(bodySource);
parts.push('');
// Close begin + define-library
parts.push('))');
parts.push('');
// Self-import for backward compat
parts.push(`;; Re-export to global env`);
parts.push(`(import ${libName})`);
parts.push('');
return parts.join('\n');
}
console.log('Processing source .sx files...\n');
for (const [relPath, info] of Object.entries(MODULES)) {
processFile(relPath, info);
}
console.log('\nDone! Now run:');
console.log(' bash hosts/ocaml/browser/bundle.sh');
console.log(' node hosts/ocaml/browser/compile-modules.js');

175
hosts/ocaml/sx_vm_ref.ml Normal file

File diff suppressed because one or more lines are too long

1068
lib/vm.sx

File diff suppressed because it is too large Load Diff

View File

@@ -249,8 +249,51 @@
}
}
/**
* Convert a parsed SX code form ({_type:"list", items:[symbol"code", ...]})
* into the dict format that K.loadModule / js_to_value expects.
* Mirrors the OCaml convert_code/convert_const in sx_browser.ml.
*/
function convertCodeForm(form) {
if (!form || form._type !== "list" || !form.items || !form.items.length) return null;
var items = form.items;
if (!items[0] || items[0]._type !== "symbol" || items[0].name !== "code") return null;
var d = { _type: "dict", arity: 0, "upvalue-count": 0 };
for (var i = 1; i < items.length; i++) {
var item = items[i];
if (item && item._type === "keyword" && i + 1 < items.length) {
var val = items[i + 1];
if (item.name === "arity" || item.name === "upvalue-count") {
d[item.name] = (typeof val === "number") ? val : 0;
} else if (item.name === "bytecode" && val && val._type === "list") {
d.bytecode = val; // {_type:"list", items:[numbers...]}
} else if (item.name === "constants" && val && val._type === "list") {
d.constants = { _type: "list", items: (val.items || []).map(convertConst) };
}
i++; // skip value
}
}
return d;
}
function convertConst(c) {
if (!c || typeof c !== "object") return c; // number, string, boolean, null pass through
if (c._type === "list" && c.items && c.items.length > 0) {
var head = c.items[0];
if (head && head._type === "symbol" && head.name === "code") {
return convertCodeForm(c);
}
if (head && head._type === "symbol" && head.name === "list") {
return { _type: "list", items: c.items.slice(1).map(convertConst) };
}
}
return c; // symbols, keywords, etc. pass through
}
/**
* Try loading a pre-compiled .sxbc bytecode module (SX text format).
* Uses K.loadModule which handles VM suspension (import requests).
* Returns true on success, null on failure (caller falls back to .sx source).
*/
function loadBytecodeFile(path) {
@@ -262,20 +305,90 @@
xhr.send();
if (xhr.status !== 200) return null;
window.__sxbcText = xhr.responseText;
var result = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
delete window.__sxbcText;
// Parse the sxbc text to get the SX tree
var parsed = K.parse(xhr.responseText);
if (!parsed || !parsed.length) return null;
var sxbc = parsed[0]; // (sxbc version hash (code ...))
if (!sxbc || sxbc._type !== "list" || !sxbc.items) return null;
// Extract the code form — 3rd or 4th item (after sxbc, version, optional hash)
var codeForm = null;
for (var i = 1; i < sxbc.items.length; i++) {
var item = sxbc.items[i];
if (item && item._type === "list" && item.items && item.items.length > 0 &&
item.items[0] && item.items[0]._type === "symbol" && item.items[0].name === "code") {
codeForm = item;
break;
}
}
if (!codeForm) return null;
// Convert the SX code form to a dict for loadModule
var moduleDict = convertCodeForm(codeForm);
if (!moduleDict) return null;
// Load via K.loadModule which handles VmSuspended
var result = K.loadModule(moduleDict);
// Handle import suspensions — fetch missing libraries on demand
while (result && result.suspended && result.op === "import") {
var req = result.request;
var libName = req && req.library;
if (libName) {
// Try to find and load the library from the manifest
var loaded = handleImportSuspension(libName);
if (!loaded) {
console.warn("[sx-platform] lazy import: library not found:", libName);
}
}
// Resume the suspended module (null = library is now in env)
result = result.resume(null);
}
if (typeof result === 'string' && result.indexOf('Error') === 0) {
console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
return null;
}
return true;
} catch(e) {
delete window.__sxbcText;
console.warn("[sx-platform] bytecode FAIL " + path + ":", e.message || e);
return null;
}
}
/**
* Handle an import suspension by finding and loading the library.
* The library name may be an SX value (list/string) — normalize to manifest key.
*/
function handleImportSuspension(libSpec) {
// libSpec from the kernel is the library name spec, e.g. {_type:"list", items:[{name:"sx"},{name:"dom"}]}
// or a string like "sx dom"
var key;
if (typeof libSpec === "string") {
key = libSpec;
} else if (libSpec && libSpec._type === "list" && libSpec.items) {
key = libSpec.items.map(function(item) {
return (item && item.name) ? item.name : String(item);
}).join(" ");
} else if (libSpec && libSpec._type === "dict") {
// Dict with key/name fields
key = libSpec.key || libSpec.name || "";
} else {
key = String(libSpec);
}
if (_loadedLibs[key]) return true; // already loaded
if (!_manifest) loadManifest();
if (!_manifest || !_manifest[key]) {
console.warn("[sx-platform] lazy import: unknown library key '" + key + "'");
return false;
}
// Load the library (and its deps) on demand
return loadLibrary(key, {});
}
/**
* Load an .sx file synchronously via XHR (boot-time only).
* Returns the number of expressions loaded, or an error string.
@@ -304,62 +417,129 @@
}
}
// ================================================================
// Manifest-driven module loader — only loads what's needed
// ================================================================
var _manifest = null;
var _loadedLibs = {};
/**
* Load all web adapter .sx files in dependency order.
* Tries pre-compiled bytecode first, falls back to source.
* Fetch and parse the module manifest (library deps + file paths).
*/
function loadManifest() {
if (_manifest) return _manifest;
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
xhr.send();
if (xhr.status === 200) {
_manifest = JSON.parse(xhr.responseText);
return _manifest;
}
} catch(e) {}
console.warn("[sx-platform] No manifest found, falling back to full load");
return null;
}
/**
* Load a single library and all its dependencies (recursive).
* Cycle-safe: tracks in-progress loads to break circular deps.
* Functions in cyclic modules resolve symbols at call time via global env.
*/
function loadLibrary(name, loading) {
if (_loadedLibs[name]) return true;
if (loading[name]) return true; // cycle — skip
loading[name] = true;
var info = _manifest[name];
if (!info) {
console.warn("[sx-platform] Unknown library: " + name);
return false;
}
// Resolve deps first
for (var i = 0; i < info.deps.length; i++) {
loadLibrary(info.deps[i], loading);
}
// Load this module
var ok = loadBytecodeFile("sx/" + info.file);
if (!ok) {
var sxFile = info.file.replace(/\.sxbc$/, '.sx');
ok = loadSxFile("sx/" + sxFile);
}
_loadedLibs[name] = true;
return !!ok;
}
/**
* Load web stack using the module manifest.
* Only downloads libraries that the entry point transitively depends on.
*/
function loadWebStack() {
var files = [
// Spec modules
"sx/render.sx",
"sx/core-signals.sx",
"sx/signals.sx",
"sx/deps.sx",
"sx/router.sx",
"sx/page-helpers.sx",
// Freeze scope (signal persistence) + highlight (syntax coloring)
"sx/freeze.sx",
"sx/highlight.sx",
// Bytecode compiler + VM
"sx/bytecode.sx",
"sx/compiler.sx",
"sx/vm.sx",
// Web libraries (use 8 FFI primitives)
"sx/dom.sx",
"sx/browser.sx",
// Web adapters
"sx/adapter-html.sx",
"sx/adapter-sx.sx",
"sx/adapter-dom.sx",
// Boot helpers (platform functions in pure SX)
"sx/boot-helpers.sx",
"sx/hypersx.sx",
// Test harness (for inline test runners)
"sx/harness.sx",
"sx/harness-reactive.sx",
"sx/harness-web.sx",
// Web framework
"sx/engine.sx",
"sx/orchestration.sx",
"sx/boot.sx",
];
var manifest = loadManifest();
if (!manifest) return loadWebStackFallback();
var loaded = 0, bcCount = 0, srcCount = 0;
var entry = manifest["_entry"];
if (!entry) {
console.warn("[sx-platform] No _entry in manifest, falling back");
return loadWebStackFallback();
}
var loading = {};
var t0 = performance.now();
if (K.beginModuleLoad) K.beginModuleLoad();
// Load all entry point deps recursively
for (var i = 0; i < entry.deps.length; i++) {
loadLibrary(entry.deps[i], loading);
}
// Load entry point itself (boot.sx — not a library, just defines + init)
loadBytecodeFile("sx/" + entry.file) || loadSxFile("sx/" + entry.file.replace(/\.sxbc$/, '.sx'));
if (K.endModuleLoad) K.endModuleLoad();
var count = Object.keys(_loadedLibs).length + 1; // +1 for entry
var dt = Math.round(performance.now() - t0);
console.log("[sx-platform] Loaded " + count + " modules in " + dt + "ms (manifest-driven)");
}
/**
* Fallback: load all files in hardcoded order (pre-manifest compat).
*/
function loadWebStackFallback() {
var files = [
"sx/render.sx", "sx/core-signals.sx", "sx/signals.sx", "sx/deps.sx",
"sx/router.sx", "sx/page-helpers.sx", "sx/freeze.sx", "sx/highlight.sx",
"sx/bytecode.sx", "sx/compiler.sx", "sx/vm.sx", "sx/dom.sx", "sx/browser.sx",
"sx/adapter-html.sx", "sx/adapter-sx.sx", "sx/adapter-dom.sx",
"sx/boot-helpers.sx", "sx/hypersx.sx", "sx/harness.sx",
"sx/harness-reactive.sx", "sx/harness-web.sx",
"sx/engine.sx", "sx/orchestration.sx", "sx/boot.sx",
];
if (K.beginModuleLoad) K.beginModuleLoad();
for (var i = 0; i < files.length; i++) {
var r = loadBytecodeFile(files[i]);
if (r) { bcCount++; continue; }
// Bytecode not available — end batch, load source, restart batch
if (K.endModuleLoad) K.endModuleLoad();
r = loadSxFile(files[i]);
if (typeof r === "number") { loaded += r; srcCount++; }
if (K.beginModuleLoad) K.beginModuleLoad();
if (!loadBytecodeFile(files[i])) loadSxFile(files[i]);
}
if (K.endModuleLoad) K.endModuleLoad();
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs)");
return loaded;
console.log("[sx-platform] Loaded " + files.length + " files (fallback)");
}
/**
* Load an optional library on demand (e.g., highlight, harness).
* Can be called after boot for pages that need extra modules.
*/
globalThis.__sxLoadLibrary = function(name) {
if (!_manifest) loadManifest();
if (!_manifest) return false;
if (_loadedLibs[name]) return true;
if (K.beginModuleLoad) K.beginModuleLoad();
var ok = loadLibrary(name, {});
if (K.endModuleLoad) K.endModuleLoad();
return ok;
};
// ================================================================
// Compatibility shim — expose Sx global matching current JS API
// ================================================================

View File

@@ -1,3 +1,10 @@
(import (sx dom))
(import (sx render))
(define-library (web adapter-dom)
(export SVG_NS MATH_NS island-scope? contains-deref? dom-on render-to-dom render-dom-list render-dom-element render-dom-component render-dom-fragment render-dom-raw render-dom-unknown-component RENDER_DOM_FORMS render-dom-form? dispatch-render-form render-lambda-dom render-dom-island render-dom-lake render-dom-marsh reactive-text reactive-attr reactive-spread reactive-fragment render-list-item extract-key reactive-list bind-input *use-cek-reactive* enable-cek-reactive! cek-reactive-text cek-reactive-attr render-dom-portal render-dom-error-boundary)
(begin
(define SVG_NS "http://www.w3.org/2000/svg")
(define MATH_NS "http://www.w3.org/1998/Math/MathML")
@@ -1336,3 +1343,9 @@
((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns)))))
(dom-append container fallback-dom)))))))
container)))
))
;; Re-export to global env
(import (web adapter-dom))

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,7 @@
(import (sx render))
(define-library (web adapter-html)
(export
render-to-html

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,7 @@
(import (web boot-helpers))
(define-library (web adapter-sx)
(export
render-to-sx

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,11 @@
(import (sx dom))
(import (sx browser))
(import (web adapter-dom))
(define-library (web boot-helpers)
(export _sx-bound-prefix mark-processed! is-processed? clear-processed! callable? to-kebab sx-load-components call-expr base-env get-render-env merge-envs sx-render-with-env parse-env-attr store-env-attr resolve-mount-target remove-head-element set-sx-comp-cookie clear-sx-comp-cookie log-parse-error loaded-component-names csrf-token validate-for-request build-request-body abort-previous-target abort-previous track-controller track-controller-target new-abort-controller abort-signal apply-optimistic revert-optimistic dom-has-attr? show-indicator disable-elements clear-loading-state abort-error? promise-catch fetch-request fetch-location fetch-and-restore fetch-preload fetch-streaming dom-parse-html-document dom-body-inner-html create-script-clone cross-origin? browser-scroll-to with-transition event-source-connect event-source-listen bind-boost-link bind-boost-form bind-client-route-click sw-post-message try-parse-json strip-component-scripts extract-response-css sx-render sx-hydrate sx-process-scripts select-from-container children-to-fragment select-html-from-doc register-io-deps resolve-page-data parse-sx-data try-eval-content try-async-eval-content try-rerender-page execute-action bind-preload persist-offline-data retrieve-offline-data)
(begin
(define _sx-bound-prefix "_sxBound")
(define
@@ -764,3 +772,9 @@
(define persist-offline-data (fn () nil))
(define retrieve-offline-data (fn () nil))
))
;; Re-export to global env
(import (web boot-helpers))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,16 @@
(import (sx dom))
(import (sx bytecode))
(import (sx browser))
(import (web boot-helpers))
(import (web adapter-dom))
(import (sx signals))
(import (sx signals-web))
(import (web router))
(import (web page-helpers))
(import (web orchestration))
(import (sx render))
(define
HEAD_HOIST_SELECTOR
"meta, title, link[rel='canonical'], script[type='application/ld+json']")

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,7 @@
(define-library (sx browser)
(export browser-location-href browser-location-pathname browser-location-origin browser-same-origin? url-pathname browser-push-state browser-replace-state browser-reload browser-navigate local-storage-get local-storage-set local-storage-remove set-timeout set-interval clear-timeout clear-interval request-animation-frame fetch-request new-abort-controller controller-signal controller-abort promise-then promise-resolve promise-delayed browser-confirm browser-prompt browser-media-matches? json-parse log-info log-warn console-log now-ms schedule-idle set-cookie get-cookie)
(begin
(define
browser-location-href
(fn () (host-get (host-get (dom-window) "location") "href")))
@@ -219,3 +223,9 @@
"match"
(host-new "RegExp" (str "(?:^|;\\s*)" name "=([^;]*)")))))
(if match (host-call nil "decodeURIComponent" (host-get match 1)) nil))))
))
;; Re-export to global env
(import (sx browser))

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
(sxbc 1 "71e91d776cc0a108"
(code
: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)))
: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)))

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,3 +1,7 @@
(define-library (sx dom)
(export dom-document dom-window dom-body dom-head dom-create-element create-text-node create-fragment create-comment dom-append dom-prepend dom-insert-before dom-insert-after dom-remove dom-is-active-element? dom-is-input-element? dom-is-child-of? dom-attr-list dom-remove-child dom-replace-child dom-clone dom-query dom-query-all dom-query-by-id dom-closest dom-matches? dom-get-attr dom-set-attr dom-remove-attr dom-has-attr? dom-add-class dom-remove-class dom-has-class? dom-text-content dom-set-text-content dom-inner-html dom-set-inner-html dom-outer-html dom-insert-adjacent-html dom-get-style dom-set-style dom-get-prop dom-set-prop dom-tag-name dom-node-type dom-node-name dom-id dom-parent dom-first-child dom-next-sibling dom-child-list dom-is-fragment? dom-child-nodes dom-remove-children-after dom-focus dom-parse-html dom-listen dom-add-listener dom-dispatch event-detail prevent-default stop-propagation event-modifier-key? element-value error-message dom-get-data dom-set-data dom-append-to-head set-document-title)
(begin
(define dom-document (fn () (host-global "document")))
(define dom-window (fn () (host-global "window")))
@@ -414,3 +418,9 @@
(define
set-document-title
(fn (title) (host-set! (dom-document) "title" title)))
))
;; Re-export to global env
(import (sx dom))

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,9 @@
(import (web boot-helpers))
(import (sx dom))
(import (sx browser))
(define-library (web engine)
(export
ENGINE_VERBS

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
(sxbc 1 "320fb4826d09fed3"
(code
: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)))
: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 16 1 2 52 5 0 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 1 0 16 0 48 1 52 0 0 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)))

View File

@@ -1,3 +1,7 @@
(define-library (sx harness-reactive)
(export assert-signal-value assert-signal-has-subscribers assert-signal-no-subscribers assert-signal-subscriber-count simulate-signal-set! simulate-signal-swap! assert-computed-dep-count assert-computed-depends-on count-effect-runs make-test-signal assert-batch-coalesces)
(begin
(define
assert-signal-value
:effects ()
@@ -108,3 +112,9 @@
expected-notify-count
" notifications, got "
notify-count)))))
))
;; Re-export to global env
(import (sx harness-reactive))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "57726b5b82c1a3cb"
(sxbc 1 "0bc2cc2f659d5a90"
(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 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)))

View File

@@ -1,3 +1,7 @@
(define-library (sx harness-web)
(export mock-element mock-set-text! mock-append-child! mock-set-attr! mock-get-attr mock-add-listener! simulate-click simulate-input simulate-event assert-text assert-attr assert-class assert-no-class assert-child-count assert-event-fired assert-no-event event-fire-count make-web-harness is-renderable? is-render-leak? assert-renderable render-body-audit assert-render-body-clean mock-render mock-render-fragment assert-single-render-root assert-tag)
(begin
(define
mock-element
:effects ()
@@ -318,3 +322,9 @@
(assert
(= (get el "tag") expected-tag)
(str "Expected <" expected-tag "> but got <" (get el "tag") ">"))))
))
;; Re-export to global env
(import (sx harness-web))

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,22 @@
(define-library (sx highlight)
(export
sx-specials
sx-special?
hl-digit?
hl-alpha?
hl-sym-char?
hl-ws?
hl-escape
hl-span
tokenize-sx
sx-token-classes
render-sx-tokens
highlight-sx
highlight)
(begin
(define
sx-specials
(list
@@ -298,3 +317,9 @@
(or (= lang "lisp") (= lang "sx") (= lang "sexp") (= lang "scheme"))
(highlight-sx code)
(list (quote code) code))))
)) ;; end define-library
;; Re-export to global namespace for backward compatibility
(import (sx highlight))

View File

@@ -1,3 +1,7 @@
(define-library (sx hypersx)
(export hsx-indent hsx-sym-name hsx-kw-name hsx-is-element? hsx-is-component? hsx-extract-css hsx-tag-str hsx-atom hsx-inline hsx-attrs-str hsx-children sx->hypersx-node sx->hypersx)
(begin
(define hsx-indent (fn (depth) (let ((result "")) (for-each (fn (_) (set! result (str result " "))) (range 0 depth)) result)))
(define hsx-sym-name (fn (node) (if (= (type-of node) "symbol") (symbol-name node) nil)))
@@ -23,3 +27,9 @@
(define sx->hypersx-node (fn (node depth) (let ((pad (hsx-indent depth))) (cond (nil? node) (str pad "nil") (not (list? node)) (str pad (hsx-atom node)) (empty? node) (str pad "()") :else (let ((hd (hsx-sym-name (first node)))) (cond (= hd "str") (str pad (hsx-inline node)) (= hd "deref") (str pad (hsx-inline node)) (= hd "reset!") (str pad (hsx-inline node)) (= hd "swap!") (str pad (hsx-inline node)) (= hd "signal") (str pad (hsx-inline node)) (or (= hd "defcomp") (= hd "defisland")) (str pad hd " " (sx-serialize (nth node 1)) " " (sx-serialize (nth node 2)) "\n" (sx->hypersx-node (last node) (+ depth 1))) (= hd "when") (str pad "when " (hsx-inline (nth node 1)) "\n" (join "\n" (map (fn (c) (sx->hypersx-node c (+ depth 1))) (slice node 2)))) (= hd "if") (let ((test (nth node 1)) (then-b (nth node 2)) (else-b (if (> (len node) 3) (nth node 3) nil))) (if (and (not (list? then-b)) (or (nil? else-b) (not (list? else-b)))) (str pad "if " (hsx-inline test) " " (hsx-atom then-b) (if else-b (str " " (hsx-atom else-b)) "")) (str pad "if " (hsx-inline test) "\n" (sx->hypersx-node then-b (+ depth 1)) (if else-b (str "\n" pad "else\n" (sx->hypersx-node else-b (+ depth 1))) "")))) (or (= hd "let") (= hd "letrec") (= hd "let*")) (let ((binds (nth node 1)) (body (slice node 2))) (str pad hd " " (join ", " (map (fn (b) (if (and (list? b) (>= (len b) 2)) (str (sx-serialize (first b)) " = " (hsx-inline (nth b 1))) (sx-serialize b))) (if (and (list? binds) (not (empty? binds)) (list? (first binds))) binds (list binds)))) "\n" (join "\n" (map (fn (b) (sx->hypersx-node b (+ depth 1))) body)))) (and (= hd "map") (= (len node) 3) (list? (nth node 1)) (= (hsx-sym-name (first (nth node 1))) "fn")) (let ((fn-node (nth node 1)) (coll (nth node 2))) (str pad "map " (hsx-inline coll) " -> " (sx-serialize (nth fn-node 1)) "\n" (sx->hypersx-node (last fn-node) (+ depth 1)))) (and (= hd "for-each") (= (len node) 3) (list? (nth node 1)) (= (hsx-sym-name (first (nth node 1))) "fn")) (let ((fn-node (nth node 1)) (coll (nth node 2))) (str pad "for " (sx-serialize (nth fn-node 1)) " in " (hsx-inline coll) "\n" (sx->hypersx-node (last fn-node) (+ depth 1)))) (hsx-is-element? hd) (let ((css (hsx-extract-css (rest node)))) (hsx-children (str pad (hsx-tag-str hd css) (hsx-attrs-str (get css "attrs"))) (get css "children") depth)) (hsx-is-component? hd) (let ((css (hsx-extract-css (rest node)))) (hsx-children (str pad hd (hsx-attrs-str (get css "attrs"))) (get css "children") depth)) :else (str pad (sx-serialize node))))))))
(define sx->hypersx (fn (tree) (join "\n\n" (map (fn (expr) (sx->hypersx-node expr 0)) tree))))
))
;; Re-export to global env
(import (sx hypersx))

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,132 @@
{
"sx render": {
"file": "render.sxbc",
"deps": []
},
"sx signals": {
"file": "core-signals.sxbc",
"deps": []
},
"sx signals-web": {
"file": "signals.sxbc",
"deps": [
"sx dom",
"sx browser"
]
},
"web deps": {
"file": "deps.sxbc",
"deps": []
},
"web router": {
"file": "router.sxbc",
"deps": []
},
"web page-helpers": {
"file": "page-helpers.sxbc",
"deps": []
},
"sx freeze": {
"file": "freeze.sxbc",
"deps": []
},
"sx bytecode": {
"file": "bytecode.sxbc",
"deps": []
},
"sx compiler": {
"file": "compiler.sxbc",
"deps": []
},
"sx vm": {
"file": "vm.sxbc",
"deps": []
},
"sx dom": {
"file": "dom.sxbc",
"deps": []
},
"sx browser": {
"file": "browser.sxbc",
"deps": []
},
"web adapter-html": {
"file": "adapter-html.sxbc",
"deps": [
"sx render"
]
},
"web adapter-sx": {
"file": "adapter-sx.sxbc",
"deps": [
"web boot-helpers"
]
},
"web adapter-dom": {
"file": "adapter-dom.sxbc",
"deps": [
"sx dom",
"sx render"
]
},
"web boot-helpers": {
"file": "boot-helpers.sxbc",
"deps": [
"sx dom",
"sx browser",
"web adapter-dom"
]
},
"sx hypersx": {
"file": "hypersx.sxbc",
"deps": []
},
"sx harness": {
"file": "harness.sxbc",
"deps": []
},
"sx harness-reactive": {
"file": "harness-reactive.sxbc",
"deps": []
},
"sx harness-web": {
"file": "harness-web.sxbc",
"deps": []
},
"web engine": {
"file": "engine.sxbc",
"deps": [
"web boot-helpers",
"sx dom",
"sx browser"
]
},
"web orchestration": {
"file": "orchestration.sxbc",
"deps": [
"web boot-helpers",
"sx dom",
"sx browser",
"web adapter-dom",
"web engine"
]
},
"_entry": {
"file": "boot.sxbc",
"deps": [
"sx dom",
"sx browser",
"web boot-helpers",
"web adapter-dom",
"sx signals",
"sx signals-web",
"web router",
"web page-helpers",
"web orchestration",
"sx render"
],
"lazy_deps": [
"sx bytecode"
]
}
}

View File

@@ -1,5 +1,11 @@
(import (web boot-helpers))
(import (sx dom))
(import (sx browser))
(import (web adapter-dom))
(import (web engine))
(define-library (web orchestration)
(export
_preload-cache

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,3 +1,10 @@
(import (sx dom))
(import (sx browser))
(define-library (sx signals-web)
(export with-marsh-scope dispose-marsh-scope emit-event on-event bridge-event resource)
(begin
;; ==========================================================================
;; web/signals.sx — Web platform signal extensions
;;
@@ -79,3 +86,9 @@
(fn (err)
(reset! state (dict "loading" false "data" nil "error" err))))
state)))
))
;; Re-export to global env
(import (sx signals-web))

View File

@@ -1,3 +1,3 @@
(sxbc 1 "7e4a727b2f55684e"
(sxbc 1 "232c1519553b1d5f"
(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 (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)))

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,10 @@
(import (sx dom))
(import (sx render))
(define-library (web adapter-dom)
(export SVG_NS MATH_NS island-scope? contains-deref? dom-on render-to-dom render-dom-list render-dom-element render-dom-component render-dom-fragment render-dom-raw render-dom-unknown-component RENDER_DOM_FORMS render-dom-form? dispatch-render-form render-lambda-dom render-dom-island render-dom-lake render-dom-marsh reactive-text reactive-attr reactive-spread reactive-fragment render-list-item extract-key reactive-list bind-input *use-cek-reactive* enable-cek-reactive! cek-reactive-text cek-reactive-attr render-dom-portal render-dom-error-boundary)
(begin
(define SVG_NS "http://www.w3.org/2000/svg")
(define MATH_NS "http://www.w3.org/1998/Math/MathML")
@@ -1336,3 +1343,9 @@
((fallback-dom (if (nil? fallback-fn) (let ((el (dom-create-element "div" nil))) (dom-set-attr el "class" "sx-render-error") (dom-set-attr el "style" "color:red;font-size:0.875rem;padding:0.5rem;border:1px solid red;border-radius:0.25rem;margin:0.5rem 0;") (dom-set-text-content el (str "Render error: " err)) el) (if (lambda? fallback-fn) (render-lambda-dom fallback-fn (list err retry-fn) env ns) (render-to-dom (apply fallback-fn (list err retry-fn)) env ns)))))
(dom-append container fallback-dom)))))))
container)))
))
;; Re-export to global env
(import (web adapter-dom))

View File

@@ -1,5 +1,7 @@
(import (sx render))
(define-library (web adapter-html)
(export
render-to-html

View File

@@ -1,5 +1,7 @@
(import (web boot-helpers))
(define-library (web adapter-sx)
(export
render-to-sx

View File

@@ -1,3 +1,16 @@
(import (sx dom))
(import (sx bytecode))
(import (sx browser))
(import (web boot-helpers))
(import (web adapter-dom))
(import (sx signals))
(import (sx signals-web))
(import (web router))
(import (web page-helpers))
(import (web orchestration))
(import (sx render))
(define
HEAD_HOIST_SELECTOR
"meta, title, link[rel='canonical'], script[type='application/ld+json']")

View File

@@ -1,5 +1,9 @@
(import (web boot-helpers))
(import (sx dom))
(import (sx browser))
(define-library (web engine)
(export
ENGINE_VERBS

View File

@@ -1,3 +1,7 @@
(define-library (sx harness-reactive)
(export assert-signal-value assert-signal-has-subscribers assert-signal-no-subscribers assert-signal-subscriber-count simulate-signal-set! simulate-signal-swap! assert-computed-dep-count assert-computed-depends-on count-effect-runs make-test-signal assert-batch-coalesces)
(begin
(define
assert-signal-value
:effects ()
@@ -108,3 +112,9 @@
expected-notify-count
" notifications, got "
notify-count)))))
))
;; Re-export to global env
(import (sx harness-reactive))

View File

@@ -1,3 +1,7 @@
(define-library (sx harness-web)
(export mock-element mock-set-text! mock-append-child! mock-set-attr! mock-get-attr mock-add-listener! simulate-click simulate-input simulate-event assert-text assert-attr assert-class assert-no-class assert-child-count assert-event-fired assert-no-event event-fire-count make-web-harness is-renderable? is-render-leak? assert-renderable render-body-audit assert-render-body-clean mock-render mock-render-fragment assert-single-render-root assert-tag)
(begin
(define
mock-element
:effects ()
@@ -318,3 +322,9 @@
(assert
(= (get el "tag") expected-tag)
(str "Expected <" expected-tag "> but got <" (get el "tag") ">"))))
))
;; Re-export to global env
(import (sx harness-web))

View File

@@ -1,3 +1,11 @@
(import (sx dom))
(import (sx browser))
(import (web adapter-dom))
(define-library (web boot-helpers)
(export _sx-bound-prefix mark-processed! is-processed? clear-processed! callable? to-kebab sx-load-components call-expr base-env get-render-env merge-envs sx-render-with-env parse-env-attr store-env-attr resolve-mount-target remove-head-element set-sx-comp-cookie clear-sx-comp-cookie log-parse-error loaded-component-names csrf-token validate-for-request build-request-body abort-previous-target abort-previous track-controller track-controller-target new-abort-controller abort-signal apply-optimistic revert-optimistic dom-has-attr? show-indicator disable-elements clear-loading-state abort-error? promise-catch fetch-request fetch-location fetch-and-restore fetch-preload fetch-streaming dom-parse-html-document dom-body-inner-html create-script-clone cross-origin? browser-scroll-to with-transition event-source-connect event-source-listen bind-boost-link bind-boost-form bind-client-route-click sw-post-message try-parse-json strip-component-scripts extract-response-css sx-render sx-hydrate sx-process-scripts select-from-container children-to-fragment select-html-from-doc register-io-deps resolve-page-data parse-sx-data try-eval-content try-async-eval-content try-rerender-page execute-action bind-preload persist-offline-data retrieve-offline-data)
(begin
(define _sx-bound-prefix "_sxBound")
(define
@@ -764,3 +772,9 @@
(define persist-offline-data (fn () nil))
(define retrieve-offline-data (fn () nil))
))
;; Re-export to global env
(import (web boot-helpers))

View File

@@ -1,3 +1,7 @@
(define-library (sx browser)
(export browser-location-href browser-location-pathname browser-location-origin browser-same-origin? url-pathname browser-push-state browser-replace-state browser-reload browser-navigate local-storage-get local-storage-set local-storage-remove set-timeout set-interval clear-timeout clear-interval request-animation-frame fetch-request new-abort-controller controller-signal controller-abort promise-then promise-resolve promise-delayed browser-confirm browser-prompt browser-media-matches? json-parse log-info log-warn console-log now-ms schedule-idle set-cookie get-cookie)
(begin
(define
browser-location-href
(fn () (host-get (host-get (dom-window) "location") "href")))
@@ -219,3 +223,9 @@
"match"
(host-new "RegExp" (str "(?:^|;\\s*)" name "=([^;]*)")))))
(if match (host-call nil "decodeURIComponent" (host-get match 1)) nil))))
))
;; Re-export to global env
(import (sx browser))

View File

@@ -1,3 +1,7 @@
(define-library (sx dom)
(export dom-document dom-window dom-body dom-head dom-create-element create-text-node create-fragment create-comment dom-append dom-prepend dom-insert-before dom-insert-after dom-remove dom-is-active-element? dom-is-input-element? dom-is-child-of? dom-attr-list dom-remove-child dom-replace-child dom-clone dom-query dom-query-all dom-query-by-id dom-closest dom-matches? dom-get-attr dom-set-attr dom-remove-attr dom-has-attr? dom-add-class dom-remove-class dom-has-class? dom-text-content dom-set-text-content dom-inner-html dom-set-inner-html dom-outer-html dom-insert-adjacent-html dom-get-style dom-set-style dom-get-prop dom-set-prop dom-tag-name dom-node-type dom-node-name dom-id dom-parent dom-first-child dom-next-sibling dom-child-list dom-is-fragment? dom-child-nodes dom-remove-children-after dom-focus dom-parse-html dom-listen dom-add-listener dom-dispatch event-detail prevent-default stop-propagation event-modifier-key? element-value error-message dom-get-data dom-set-data dom-append-to-head set-document-title)
(begin
(define dom-document (fn () (host-global "document")))
(define dom-window (fn () (host-global "window")))
@@ -414,3 +418,9 @@
(define
set-document-title
(fn (title) (host-set! (dom-document) "title" title)))
))
;; Re-export to global env
(import (sx dom))

View File

@@ -1,3 +1,7 @@
(define-library (sx hypersx)
(export hsx-indent hsx-sym-name hsx-kw-name hsx-is-element? hsx-is-component? hsx-extract-css hsx-tag-str hsx-atom hsx-inline hsx-attrs-str hsx-children sx->hypersx-node sx->hypersx)
(begin
(define hsx-indent (fn (depth) (let ((result "")) (for-each (fn (_) (set! result (str result " "))) (range 0 depth)) result)))
(define hsx-sym-name (fn (node) (if (= (type-of node) "symbol") (symbol-name node) nil)))
@@ -23,3 +27,9 @@
(define sx->hypersx-node (fn (node depth) (let ((pad (hsx-indent depth))) (cond (nil? node) (str pad "nil") (not (list? node)) (str pad (hsx-atom node)) (empty? node) (str pad "()") :else (let ((hd (hsx-sym-name (first node)))) (cond (= hd "str") (str pad (hsx-inline node)) (= hd "deref") (str pad (hsx-inline node)) (= hd "reset!") (str pad (hsx-inline node)) (= hd "swap!") (str pad (hsx-inline node)) (= hd "signal") (str pad (hsx-inline node)) (or (= hd "defcomp") (= hd "defisland")) (str pad hd " " (sx-serialize (nth node 1)) " " (sx-serialize (nth node 2)) "\n" (sx->hypersx-node (last node) (+ depth 1))) (= hd "when") (str pad "when " (hsx-inline (nth node 1)) "\n" (join "\n" (map (fn (c) (sx->hypersx-node c (+ depth 1))) (slice node 2)))) (= hd "if") (let ((test (nth node 1)) (then-b (nth node 2)) (else-b (if (> (len node) 3) (nth node 3) nil))) (if (and (not (list? then-b)) (or (nil? else-b) (not (list? else-b)))) (str pad "if " (hsx-inline test) " " (hsx-atom then-b) (if else-b (str " " (hsx-atom else-b)) "")) (str pad "if " (hsx-inline test) "\n" (sx->hypersx-node then-b (+ depth 1)) (if else-b (str "\n" pad "else\n" (sx->hypersx-node else-b (+ depth 1))) "")))) (or (= hd "let") (= hd "letrec") (= hd "let*")) (let ((binds (nth node 1)) (body (slice node 2))) (str pad hd " " (join ", " (map (fn (b) (if (and (list? b) (>= (len b) 2)) (str (sx-serialize (first b)) " = " (hsx-inline (nth b 1))) (sx-serialize b))) (if (and (list? binds) (not (empty? binds)) (list? (first binds))) binds (list binds)))) "\n" (join "\n" (map (fn (b) (sx->hypersx-node b (+ depth 1))) body)))) (and (= hd "map") (= (len node) 3) (list? (nth node 1)) (= (hsx-sym-name (first (nth node 1))) "fn")) (let ((fn-node (nth node 1)) (coll (nth node 2))) (str pad "map " (hsx-inline coll) " -> " (sx-serialize (nth fn-node 1)) "\n" (sx->hypersx-node (last fn-node) (+ depth 1)))) (and (= hd "for-each") (= (len node) 3) (list? (nth node 1)) (= (hsx-sym-name (first (nth node 1))) "fn")) (let ((fn-node (nth node 1)) (coll (nth node 2))) (str pad "for " (sx-serialize (nth fn-node 1)) " in " (hsx-inline coll) "\n" (sx->hypersx-node (last fn-node) (+ depth 1)))) (hsx-is-element? hd) (let ((css (hsx-extract-css (rest node)))) (hsx-children (str pad (hsx-tag-str hd css) (hsx-attrs-str (get css "attrs"))) (get css "children") depth)) (hsx-is-component? hd) (let ((css (hsx-extract-css (rest node)))) (hsx-children (str pad hd (hsx-attrs-str (get css "attrs"))) (get css "children") depth)) :else (str pad (sx-serialize node))))))))
(define sx->hypersx (fn (tree) (join "\n\n" (map (fn (expr) (sx->hypersx-node expr 0)) tree))))
))
;; Re-export to global env
(import (sx hypersx))

View File

@@ -1,5 +1,11 @@
(import (web boot-helpers))
(import (sx dom))
(import (sx browser))
(import (web adapter-dom))
(import (web engine))
(define-library (web orchestration)
(export
_preload-cache

View File

@@ -1,3 +1,10 @@
(import (sx dom))
(import (sx browser))
(define-library (sx signals-web)
(export with-marsh-scope dispose-marsh-scope emit-event on-event bridge-event resource)
(begin
;; ==========================================================================
;; web/signals.sx — Web platform signal extensions
;;
@@ -79,3 +86,9 @@
(fn (err)
(reset! state (dict "loading" false "data" nil "error" err))))
state)))
))
;; Re-export to global env
(import (sx signals-web))