From 3df8c41ca17afefe3ddf444bc2ffaade68c58589 Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Mar 2026 17:23:09 +0000 Subject: [PATCH] Split make_server_env, eliminate all runtime sx_ref imports, fix auth-menu tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make_server_env split into 7 focused setup functions: - setup_browser_stubs (22 DOM no-ops) - setup_scope_env (18 scope primitives from sx_scope.ml) - setup_evaluator_bridge (CEK eval-expr, trampoline, expand-macro, etc.) - setup_introspection (type predicates, component/lambda accessors) - setup_type_operations (string/env/dict/equality/parser helpers) - setup_html_tags (~100 HTML tag functions) - setup_io_env (query, action, helper IO bridge) Eliminate ALL runtime sx_ref.py imports: - sx/sxc/pages/helpers.py: 24 imports → _ocaml_helpers.py bridge - sx/sxc/pages/sx_router.py: remove SX_USE_REF fallback - shared/sx/query_registry.py: use register_components instead of eval Unify JIT compilation: pre-compile list derived from allowlist (no manual duplication), only compiler internals pre-compiled. Fix test_components auth-menu: ~auth-menu → ~shared:fragments/auth-menu Tests: 1114 OCaml, 29/29 components, 35/35 regression, 6/6 Playwright Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/bin/sx_server.ml | 577 +++++++++-------------------- shared/sx/query_registry.py | 26 +- shared/sx/tests/test_components.py | 8 +- sx/sxc/pages/_ocaml_helpers.py | 222 +++++++++++ sx/sxc/pages/helpers.py | 46 +-- sx/sxc/pages/sx_router.py | 5 +- 6 files changed, 439 insertions(+), 445 deletions(-) create mode 100644 sx/sxc/pages/_ocaml_helpers.py diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index cbdd77f..8402fe5 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -313,44 +313,9 @@ let setup_io_env env = (* Environment setup *) (* ====================================================================== *) -let make_server_env () = - let env = make_env () in - - (* Evaluator bindings — same as run_tests.ml's make_test_env, - but only the ones needed for rendering (not test helpers). *) - let bind name fn = - ignore (env_bind env name (NativeFn (name, fn))) - in - - bind "assert" (fun args -> - match args with - | [cond] -> - if not (sx_truthy cond) then raise (Eval_error "Assertion failed"); - Bool true - | [cond; String msg] -> - if not (sx_truthy cond) then raise (Eval_error ("Assertion error: " ^ msg)); - Bool true - | [cond; msg] -> - if not (sx_truthy cond) then - raise (Eval_error ("Assertion error: " ^ value_to_string msg)); - Bool true - | _ -> raise (Eval_error "assert: expected 1-2 args")); - - bind "append!" (fun args -> - match args with - | [ListRef r; v] -> r := !r @ [v]; ListRef r - | [List items; v] -> List (items @ [v]) - | _ -> raise (Eval_error "append!: expected list and value")); - - (* HTML renderer — OCaml render module provides the shell renderer; - adapter-html.sx provides the SX-level render-to-html *) - Sx_render.setup_render_env env; - - (* Render-mode flags *) - bind "set-render-active!" (fun _args -> Nil); - bind "render-active?" (fun _args -> Bool true); - - (* Browser APIs — no-op stubs for SSR *) +(* ---- Browser API stubs (no-op for SSR) ---- *) +let setup_browser_stubs env = + let bind name fn = ignore (env_bind env name (NativeFn (name, fn))) in bind "local-storage-get" (fun _args -> Nil); bind "local-storage-set" (fun _args -> Nil); bind "dom-listen" (fun _args -> NativeFn ("noop", fun _ -> Nil)); @@ -359,9 +324,6 @@ let make_server_env () = bind "dom-get-data" (fun _args -> Nil); bind "event-detail" (fun _args -> Nil); bind "promise-then" (fun _args -> Nil); - bind "thunk?" (fun args -> match args with [Thunk _] -> Bool true | _ -> Bool false); - bind "thunk-expr" (fun args -> match args with [v] -> thunk_expr v | _ -> Nil); - bind "thunk-env" (fun args -> match args with [v] -> thunk_env v | _ -> Nil); bind "schedule-idle" (fun _args -> Nil); bind "dom-query" (fun _args -> Nil); bind "dom-query-all" (fun _args -> List []); @@ -375,28 +337,11 @@ let make_server_env () = bind "dom-append" (fun _args -> Nil); bind "create-text-node" (fun _args -> Nil); bind "render-to-dom" (fun _args -> Nil); + bind "set-render-active!" (fun _args -> Nil); + bind "render-active?" (fun _args -> Bool true) - (* Raw HTML — platform primitives for adapter-html.sx *) - bind "make-raw-html" (fun args -> - match args with [String s] -> RawHTML s | [v] -> RawHTML (value_to_string v) | _ -> Nil); - bind "raw-html-content" (fun args -> - match args with [RawHTML s] -> String s | [String s] -> String s | _ -> String ""); - bind "empty-dict?" (fun args -> - match args with [Dict d] -> Bool (Hashtbl.length d = 0) | _ -> Bool true); - bind "for-each-indexed" (fun args -> - match args with - | [fn_val; List items] | [fn_val; ListRef { contents = items }] -> - List.iteri (fun i item -> - ignore (Sx_ref.eval_expr (List [fn_val; Number (float_of_int i); item]) (Env env)) - ) items; Nil - | _ -> Nil); - - (* Scope stack — platform primitives for render-time dynamic scope. - Used by aser for spread/provide/emit patterns. - Module-level so step-sf-context can check it via get-primitive. *) - (* Scope primitives are registered globally in sx_scope.ml. - Bind them into the env so the JIT VM can find them via vm.globals - (OP_GLOBAL_GET checks env.bindings before the primitives table). *) +(* ---- Scope primitives: bind into env for VM visibility ---- *) +let setup_scope_env env = List.iter (fun name -> try ignore (env_bind env name (Sx_primitives.get_primitive name)) with _ -> () @@ -406,23 +351,11 @@ let make_server_env () = "scope-collected"; "scope-clear-collected!"; "provide-push!"; "provide-pop!"; "get-cookie"; "set-cookie"]; - (* sx-context is an env alias for context *) - let context_prim = Sx_primitives.get_primitive "context" in - ignore (env_bind env "sx-context" context_prim); + ignore (env_bind env "sx-context" (Sx_primitives.get_primitive "context")) - (* qq-expand-runtime — quasiquote expansion at runtime. - The bytecode compiler emits CALL_PRIM "qq-expand-runtime" for - backtick expressions. Expands the template with the calling env. *) - bind "qq-expand-runtime" (fun args -> - match args with - | [template] -> - (* qq_expand needs an env — use the kernel env *) - Sx_ref.qq_expand template (Env env) - | [template; Env e] -> Sx_ref.qq_expand template (Env e) - | _ -> Nil); - - (* Evaluator bridge — aser calls these spec functions. - Route to the OCaml CEK machine. *) +(* ---- CEK evaluator bridge ---- *) +let setup_evaluator_bridge env = + let bind name fn = ignore (env_bind env name (NativeFn (name, fn))) in bind "eval-expr" (fun args -> match args with | [expr; e] -> Sx_ref.eval_expr expr (Env (Sx_runtime.unwrap_env e)) @@ -431,7 +364,6 @@ let make_server_env () = bind "trampoline" (fun args -> match args with | [v] -> - (* sf-letrec returns thunks — resolve them *) let rec resolve v = match v with | Thunk (expr, env) -> resolve (Sx_ref.eval_expr expr (Env env)) | _ -> v @@ -460,152 +392,151 @@ let make_server_env () = ) m.m_params; Sx_ref.eval_expr m.m_body (Env body_env) | _ -> raise (Eval_error "expand-macro: expected (macro args env)")); - - (* Expose register-special-form! and *custom-special-forms* to SX code - (used by web-forms.sx and adapter form-classification functions) *) + bind "qq-expand-runtime" (fun args -> + match args with + | [template] -> Sx_ref.qq_expand template (Env env) + | [template; Env e] -> Sx_ref.qq_expand template (Env e) + | _ -> Nil); bind "register-special-form!" (fun args -> match args with | [String name; handler] -> - ignore (Sx_ref.register_special_form (String name) handler); - Nil + ignore (Sx_ref.register_special_form (String name) handler); Nil | _ -> raise (Eval_error "register-special-form!: expected (name handler)")); ignore (env_bind env "*custom-special-forms*" Sx_ref.custom_special_forms); - - (* Register <> as a special form — evaluates all children, returns list *) ignore (Sx_ref.register_special_form (String "<>") (NativeFn ("<>", fun args -> - List (List.map (fun a -> Sx_ref.eval_expr a (Env env)) args)))); + List (List.map (fun a -> Sx_ref.eval_expr a (Env env)) args)))) - - (* Missing primitives that may be referenced *) - bind "upcase" (fun args -> - match args with - | [String s] -> String (String.uppercase_ascii s) - | _ -> raise (Eval_error "upcase: expected string")); - - bind "downcase" (fun args -> - match args with - | [String s] -> String (String.lowercase_ascii s) - | _ -> raise (Eval_error "downcase: expected string")); - - bind "make-keyword" (fun args -> - match args with - | [String s] -> Keyword s - | _ -> raise (Eval_error "make-keyword: expected string")); - - (* Type predicates and accessors — platform interface for aser *) - bind "lambda?" (fun args -> - match args with [Lambda _] -> Bool true | _ -> Bool false); - bind "macro?" (fun args -> - match args with [Macro _] -> Bool true | _ -> Bool false); - bind "island?" (fun args -> - match args with [Island _] -> Bool true | _ -> Bool false); +(* ---- Type predicates and introspection ---- *) +let setup_introspection env = + let bind name fn = ignore (env_bind env name (NativeFn (name, fn))) in + bind "thunk?" (fun args -> match args with [Thunk _] -> Bool true | _ -> Bool false); + bind "thunk-expr" (fun args -> match args with [v] -> thunk_expr v | _ -> Nil); + bind "thunk-env" (fun args -> match args with [v] -> thunk_env v | _ -> Nil); + bind "lambda?" (fun args -> match args with [Lambda _] -> Bool true | _ -> Bool false); + bind "macro?" (fun args -> match args with [Macro _] -> Bool true | _ -> Bool false); + bind "island?" (fun args -> match args with [Island _] -> Bool true | _ -> Bool false); bind "component?" (fun args -> match args with [Component _] | [Island _] -> Bool true | _ -> Bool false); bind "callable?" (fun args -> - match args with - | [NativeFn _] | [Lambda _] | [Component _] | [Island _] -> Bool true - | _ -> Bool false); + match args with [NativeFn _] | [Lambda _] | [Component _] | [Island _] -> Bool true | _ -> Bool false); + bind "spread?" (fun args -> match args with [Spread _] -> Bool true | _ -> Bool false); + bind "continuation?" (fun args -> + match args with [Continuation _] -> Bool true | [_] -> Bool false | _ -> Bool false); bind "lambda-params" (fun args -> - match args with - | [Lambda l] -> List (List.map (fun s -> String s) l.l_params) - | _ -> List []); - bind "lambda-body" (fun args -> - match args with - | [Lambda l] -> l.l_body - | _ -> Nil); + match args with [Lambda l] -> List (List.map (fun s -> String s) l.l_params) | _ -> List []); + bind "lambda-body" (fun args -> match args with [Lambda l] -> l.l_body | _ -> Nil); bind "lambda-closure" (fun args -> - match args with - | [Lambda l] -> Env l.l_closure - | _ -> Dict (Hashtbl.create 0)); + match args with [Lambda l] -> Env l.l_closure | _ -> Dict (Hashtbl.create 0)); bind "component-name" (fun args -> - match args with - | [Component c] -> String c.c_name - | [Island i] -> String i.i_name - | _ -> String ""); + match args with [Component c] -> String c.c_name | [Island i] -> String i.i_name | _ -> String ""); bind "component-closure" (fun args -> + match args with [Component c] -> Env c.c_closure | [Island i] -> Env i.i_closure | _ -> Dict (Hashtbl.create 0)); + bind "component-params" (fun args -> match args with - | [Component c] -> Env c.c_closure - | [Island i] -> Env i.i_closure - | _ -> Dict (Hashtbl.create 0)); - bind "spread?" (fun args -> - match args with [Spread _] -> Bool true | _ -> Bool false); + | [Component c] -> List (List.map (fun s -> String s) c.c_params) + | [Island i] -> List (List.map (fun s -> String s) i.i_params) + | _ -> Nil); + bind "component-body" (fun args -> + match args with [Component c] -> c.c_body | [Island i] -> i.i_body | _ -> Nil); + let has_children_impl = NativeFn ("component-has-children?", fun args -> + match args with [Component c] -> Bool c.c_has_children | [Island i] -> Bool i.i_has_children | _ -> Bool false) in + ignore (env_bind env "component-has-children" has_children_impl); + ignore (env_bind env "component-has-children?" has_children_impl); + bind "component-affinity" (fun args -> + match args with [Component c] -> String c.c_affinity | [Island _] -> String "client" | _ -> String "auto"); bind "spread-attrs" (fun args -> match args with | [Spread pairs] -> - let d = Hashtbl.create 4 in - List.iter (fun (k, v) -> Hashtbl.replace d k v) pairs; - Dict d + let d = Hashtbl.create 4 in List.iter (fun (k, v) -> Hashtbl.replace d k v) pairs; Dict d | _ -> Dict (Hashtbl.create 0)); bind "make-spread" (fun args -> match args with - | [Dict d] -> - let pairs = Hashtbl.fold (fun k v acc -> (k, v) :: acc) d [] in - Spread pairs - | _ -> Nil); - bind "is-html-tag?" (fun args -> + | [Dict d] -> Spread (Hashtbl.fold (fun k v acc -> (k, v) :: acc) d []) + | _ -> Nil) + +(* ---- Type operations, string/number/env helpers ---- *) +let setup_type_operations env = + let bind name fn = ignore (env_bind env name (NativeFn (name, fn))) in + bind "assert" (fun args -> match args with - | [String s] -> Bool (Sx_render.is_html_tag s) + | [cond] -> if not (sx_truthy cond) then raise (Eval_error "Assertion failed"); Bool true + | [cond; String msg] -> if not (sx_truthy cond) then raise (Eval_error ("Assertion error: " ^ msg)); Bool true + | [cond; msg] -> if not (sx_truthy cond) then raise (Eval_error ("Assertion error: " ^ value_to_string msg)); Bool true + | _ -> raise (Eval_error "assert: expected 1-2 args")); + bind "append!" (fun args -> + match args with + | [ListRef r; v] -> r := !r @ [v]; ListRef r + | [List items; v] -> List (items @ [v]) + | _ -> raise (Eval_error "append!: expected list and value")); + bind "make-raw-html" (fun args -> + match args with [String s] -> RawHTML s | [v] -> RawHTML (value_to_string v) | _ -> Nil); + bind "raw-html-content" (fun args -> + match args with [RawHTML s] -> String s | [String s] -> String s | _ -> String ""); + bind "empty-dict?" (fun args -> + match args with [Dict d] -> Bool (Hashtbl.length d = 0) | _ -> Bool true); + bind "for-each-indexed" (fun args -> + match args with + | [fn_val; List items] | [fn_val; ListRef { contents = items }] -> + List.iteri (fun i item -> + ignore (Sx_ref.eval_expr (List [fn_val; Number (float_of_int i); item]) (Env env)) + ) items; Nil + | _ -> Nil); + bind "upcase" (fun args -> match args with [String s] -> String (String.uppercase_ascii s) | _ -> raise (Eval_error "upcase: expected string")); + bind "downcase" (fun args -> match args with [String s] -> String (String.lowercase_ascii s) | _ -> raise (Eval_error "downcase: expected string")); + bind "make-keyword" (fun args -> match args with [String s] -> Keyword s | _ -> raise (Eval_error "make-keyword: expected string")); + bind "keyword-name" (fun args -> match args with [Keyword k] -> String k | _ -> raise (Eval_error "keyword-name: expected keyword")); + bind "symbol-name" (fun args -> match args with [Symbol s] -> String s | _ -> raise (Eval_error "symbol-name: expected symbol")); + bind "make-symbol" (fun args -> match args with [String s] -> Symbol s | [v] -> Symbol (value_to_string v) | _ -> raise (Eval_error "make-symbol: expected 1 arg")); + bind "make-sx-expr" (fun args -> match args with [String s] -> SxExpr s | _ -> raise (Eval_error "make-sx-expr: expected string")); + bind "sx-expr-source" (fun args -> match args with [SxExpr s] -> String s | [String s] -> String s | _ -> raise (Eval_error "sx-expr-source: expected sx-expr or string")); + bind "make-continuation" (fun args -> + match args with [f] -> Continuation ((fun v -> Sx_runtime.sx_call f [v]), None) | _ -> raise (Eval_error "make-continuation: expected 1 arg")); + bind "sx-serialize" (fun args -> match args with [v] -> String (inspect v) | _ -> raise (Eval_error "sx-serialize: expected 1 arg")); + bind "is-html-tag?" (fun args -> match args with [String s] -> Bool (Sx_render.is_html_tag s) | _ -> Bool false); + bind "equal?" (fun args -> match args with [a; b] -> Bool (a = b) | _ -> raise (Eval_error "equal?: expected 2 args")); + bind "identical?" (fun args -> match args with [a; b] -> Bool (a == b) | _ -> raise (Eval_error "identical?: expected 2 args")); + bind "apply" (fun args -> + match args with + | f :: rest -> + let all_args = match List.rev rest with List last :: prefix -> List.rev prefix @ last | _ -> rest in + Sx_runtime.sx_call f all_args + | _ -> raise (Eval_error "apply: expected function and args")); + bind "cond-scheme?" (fun args -> match args with [clauses] -> Sx_ref.cond_scheme_p clauses | _ -> Bool false); + bind "is-else-clause?" (fun args -> match args with [test] -> Sx_ref.is_else_clause test | _ -> Bool false); + bind "primitive?" (fun args -> + match args with + | [String name] -> Bool (Sx_primitives.is_primitive name || + (try (match env_get env name with NativeFn _ -> true | _ -> false) with _ -> false)) | _ -> Bool false); - - (* HTML tag functions — when eval-expr encounters (div :class "foo" ...), - it calls the tag function which returns the list as-is. The render path - then handles it. Same approach as the DOM adapter in the browser. *) - List.iter (fun tag -> - ignore (env_bind env tag - (NativeFn ("html:" ^ tag, fun args -> List (Symbol tag :: args)))) - ) Sx_render.html_tags; - - (* Spec evaluator helpers needed by render.sx when loaded at runtime *) + bind "get-primitive" (fun args -> + match args with + | [String name] -> (try Sx_primitives.get_primitive name with _ -> try env_get env name with _ -> Nil) + | _ -> Nil); bind "random-int" (fun args -> match args with | [Number lo; Number hi] -> let lo = int_of_float lo and hi = int_of_float hi in Number (float_of_int (lo + Random.int (max 1 (hi - lo + 1)))) | _ -> raise (Eval_error "random-int: expected (low high)")); - bind "parse-int" (fun args -> match args with | [String s] -> (try Number (float_of_int (int_of_string s)) with _ -> Nil) - | [String s; default_val] -> - (try Number (float_of_int (int_of_string s)) with _ -> default_val) + | [String s; default_val] -> (try Number (float_of_int (int_of_string s)) with _ -> default_val) | [Number n] | [Number n; _] -> Number (Float.round n) - | [_; default_val] -> default_val - | _ -> Nil); - - bind "json-encode" (fun args -> io_request "helper" (String "json-encode" :: args)); - bind "into" (fun args -> io_request "helper" (String "into" :: args)); - - bind "sleep" (fun args -> io_request "sleep" args); - bind "set-response-status" (fun args -> io_request "set-response-status" args); - bind "set-response-header" (fun args -> io_request "set-response-header" args); - - (* defhandler/defpage/defquery/defaction/defrelation are registered by - web-forms.sx via register-special-form!, no longer hardcoded here. *) - - bind "cond-scheme?" (fun args -> + | [_; default_val] -> default_val | _ -> Nil); + bind "parse-number" (fun args -> match args with [String s] -> (try Number (float_of_string s) with _ -> Nil) | _ -> Nil); + bind "escape-string" (fun args -> match args with - | [clauses] -> Sx_ref.cond_scheme_p clauses - | _ -> Bool false); - bind "is-else-clause?" (fun args -> - match args with - | [test] -> Sx_ref.is_else_clause test - | _ -> Bool false); - bind "primitive?" (fun args -> - match args with - | [String name] -> - (* Check both the primitives table and the env *) - Bool (Sx_primitives.is_primitive name || - (try (match env_get env name with NativeFn _ -> true | _ -> false) - with _ -> false)) - | _ -> Bool false); - bind "get-primitive" (fun args -> - match args with - | [String name] -> - (* Check primitives table first, then env *) - (try Sx_primitives.get_primitive name - with _ -> try env_get env name with _ -> Nil) - | _ -> Nil); - + | [String s] -> + let buf = Buffer.create (String.length s) in + String.iter (fun c -> match c with + | '"' -> Buffer.add_string buf "\\\"" | '\\' -> Buffer.add_string buf "\\\\" + | '\n' -> Buffer.add_string buf "\\n" | '\r' -> Buffer.add_string buf "\\r" + | '\t' -> Buffer.add_string buf "\\t" | c -> Buffer.add_char buf c) s; + String (Buffer.contents buf) + | _ -> raise (Eval_error "escape-string: expected string")); + bind "string-length" (fun args -> match args with [String s] -> Number (float_of_int (String.length s)) | _ -> raise (Eval_error "string-length: expected string")); + bind "dict-get" (fun args -> match args with [Dict d; String k] -> dict_get d k | [Dict d; Keyword k] -> dict_get d k | _ -> raise (Eval_error "dict-get: expected dict and key")); (* Character classification — platform primitives for spec/parser.sx *) bind "ident-start?" (fun args -> match args with @@ -627,201 +558,58 @@ let make_server_env () = || c >= '0' && c <= '9' || c = '.' || c = ':') | _ -> Bool false); bind "char-numeric?" (fun args -> - match args with - | [String s] when String.length s = 1 -> - Bool (s.[0] >= '0' && s.[0] <= '9') - | _ -> Bool false); - bind "parse-number" (fun args -> - match args with - | [String s] -> - (try Number (float_of_string s) - with _ -> Nil) - | _ -> Nil); - - bind "escape-string" (fun args -> - match args with - | [String s] -> - let buf = Buffer.create (String.length s) in - String.iter (fun c -> match c with - | '"' -> Buffer.add_string buf "\\\"" - | '\\' -> Buffer.add_string buf "\\\\" - | '\n' -> Buffer.add_string buf "\\n" - | '\r' -> Buffer.add_string buf "\\r" - | '\t' -> Buffer.add_string buf "\\t" - | c -> Buffer.add_char buf c) s; - String (Buffer.contents buf) - | _ -> raise (Eval_error "escape-string: expected string")); - - bind "string-length" (fun args -> - match args with - | [String s] -> Number (float_of_int (String.length s)) - | _ -> raise (Eval_error "string-length: expected string")); - - bind "dict-get" (fun args -> - match args with - | [Dict d; String k] -> dict_get d k - | [Dict d; Keyword k] -> dict_get d k - | _ -> raise (Eval_error "dict-get: expected dict and key")); - - bind "apply" (fun args -> - match args with - | f :: rest -> - let all_args = match List.rev rest with - | List last :: prefix -> List.rev prefix @ last - | _ -> rest - in - Sx_runtime.sx_call f all_args - | _ -> raise (Eval_error "apply: expected function and args")); - - bind "equal?" (fun args -> - match args with - | [a; b] -> Bool (a = b) - | _ -> raise (Eval_error "equal?: expected 2 args")); - - bind "identical?" (fun args -> - match args with - | [a; b] -> Bool (a == b) - | _ -> raise (Eval_error "identical?: expected 2 args")); - - bind "make-sx-expr" (fun args -> - match args with - | [String s] -> SxExpr s - | _ -> raise (Eval_error "make-sx-expr: expected string")); - - bind "sx-expr-source" (fun args -> - match args with - | [SxExpr s] -> String s - | [String s] -> String s - | _ -> raise (Eval_error "sx-expr-source: expected sx-expr or string")); - - bind "make-continuation" (fun args -> - match args with - | [f] -> - let k v = Sx_runtime.sx_call f [v] in - Continuation (k, None) - | _ -> raise (Eval_error "make-continuation: expected 1 arg")); - - bind "continuation?" (fun args -> - match args with - | [Continuation _] -> Bool true - | [_] -> Bool false - | _ -> raise (Eval_error "continuation?: expected 1 arg")); - - bind "make-symbol" (fun args -> - match args with - | [String s] -> Symbol s - | [v] -> Symbol (value_to_string v) - | _ -> raise (Eval_error "make-symbol: expected 1 arg")); - - bind "sx-serialize" (fun args -> - match args with - | [v] -> String (inspect v) - | _ -> raise (Eval_error "sx-serialize: expected 1 arg")); - - (* Env operations — accept both Env and Dict (adapter-html.sx passes dicts) *) + match args with [String s] when String.length s = 1 -> Bool (s.[0] >= '0' && s.[0] <= '9') | _ -> Bool false); + (* Env operations *) let uw = Sx_runtime.unwrap_env in - bind "env-get" (fun args -> - match args with - | [e; String k] -> Sx_types.env_get (uw e) k - | [e; Keyword k] -> Sx_types.env_get (uw e) k - | _ -> raise (Eval_error "env-get: expected env and string")); - - bind "env-has?" (fun args -> - match args with - | [e; String k] -> Bool (Sx_types.env_has (uw e) k) - | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) - | _ -> raise (Eval_error "env-has?: expected env and string")); - - bind "env-bind!" (fun args -> - match args with - | [e; String k; v] -> Sx_types.env_bind (uw e) k v - | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v - | _ -> raise (Eval_error "env-bind!: expected env, key, value")); - - bind "env-set!" (fun args -> - match args with - | [e; String k; v] -> Sx_types.env_set (uw e) k v - | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v - | _ -> raise (Eval_error "env-set!: expected env, key, value")); - - bind "env-extend" (fun args -> - match args with - | [e] -> Env (Sx_types.env_extend (uw e)) - | _ -> raise (Eval_error "env-extend: expected env")); - - bind "env-merge" (fun args -> - match args with - | [a; b] -> Sx_runtime.env_merge a b - | _ -> raise (Eval_error "env-merge: expected 2 envs")); - - (* Strict mode state *) + bind "env-get" (fun args -> match args with [e; String k] -> Sx_types.env_get (uw e) k | [e; Keyword k] -> Sx_types.env_get (uw e) k | _ -> raise (Eval_error "env-get: expected env and string")); + bind "env-has?" (fun args -> match args with [e; String k] -> Bool (Sx_types.env_has (uw e) k) | [e; Keyword k] -> Bool (Sx_types.env_has (uw e) k) | _ -> raise (Eval_error "env-has?: expected env and string")); + bind "env-bind!" (fun args -> match args with [e; String k; v] -> Sx_types.env_bind (uw e) k v | [e; Keyword k; v] -> Sx_types.env_bind (uw e) k v | _ -> raise (Eval_error "env-bind!: expected env, key, value")); + bind "env-set!" (fun args -> match args with [e; String k; v] -> Sx_types.env_set (uw e) k v | [e; Keyword k; v] -> Sx_types.env_set (uw e) k v | _ -> raise (Eval_error "env-set!: expected env, key, value")); + bind "env-extend" (fun args -> match args with [e] -> Env (Sx_types.env_extend (uw e)) | _ -> raise (Eval_error "env-extend: expected env")); + bind "env-merge" (fun args -> match args with [a; b] -> Sx_runtime.env_merge a b | _ -> raise (Eval_error "env-merge: expected 2 envs")); + (* Strict mode *) ignore (env_bind env "*strict*" (Bool false)); ignore (env_bind env "*prim-param-types*" Nil); - - bind "set-strict!" (fun args -> - match args with - | [v] -> - Sx_ref._strict_ref := v; - ignore (env_set env "*strict*" v); Nil - | _ -> raise (Eval_error "set-strict!: expected 1 arg")); - - bind "set-prim-param-types!" (fun args -> - match args with - | [v] -> - Sx_ref._prim_param_types_ref := v; - ignore (env_set env "*prim-param-types*" v); Nil - | _ -> raise (Eval_error "set-prim-param-types!: expected 1 arg")); - + bind "set-strict!" (fun args -> match args with [v] -> Sx_ref._strict_ref := v; ignore (env_set env "*strict*" v); Nil | _ -> raise (Eval_error "set-strict!: expected 1 arg")); + bind "set-prim-param-types!" (fun args -> match args with [v] -> Sx_ref._prim_param_types_ref := v; ignore (env_set env "*prim-param-types*" v); Nil | _ -> raise (Eval_error "set-prim-param-types!: expected 1 arg")); bind "component-param-types" (fun _args -> Nil); bind "component-set-param-types!" (fun _args -> Nil); + (* IO helpers routed to Python bridge *) + bind "json-encode" (fun args -> io_request "helper" (String "json-encode" :: args)); + bind "into" (fun args -> io_request "helper" (String "into" :: args)); + bind "sleep" (fun args -> io_request "sleep" args); + bind "set-response-status" (fun args -> io_request "set-response-status" args); + bind "set-response-header" (fun args -> io_request "set-response-header" args) - bind "component-params" (fun args -> - match args with - | [Component c] -> List (List.map (fun s -> String s) c.c_params) - | [Island i] -> List (List.map (fun s -> String s) i.i_params) - | _ -> Nil); +(* ---- HTML tag functions (div, span, h1, ...) ---- *) +let setup_html_tags env = + List.iter (fun tag -> + ignore (env_bind env tag + (NativeFn ("html:" ^ tag, fun args -> List (Symbol tag :: args)))) + ) Sx_render.html_tags - bind "component-body" (fun args -> - match args with - | [Component c] -> c.c_body - | [Island i] -> i.i_body - | _ -> Nil); - let has_children_impl = NativeFn ("component-has-children?", fun args -> - match args with - | [Component c] -> Bool c.c_has_children - | [Island i] -> Bool i.i_has_children - | _ -> Bool false) in - ignore (env_bind env "component-has-children" has_children_impl); - ignore (env_bind env "component-has-children?" has_children_impl); +(* ====================================================================== *) +(* Compose environment *) +(* ====================================================================== *) - bind "component-affinity" (fun args -> - match args with - | [Component c] -> String c.c_affinity - | [Island _] -> String "client" - | _ -> String "auto"); - - bind "keyword-name" (fun args -> - match args with - | [Keyword k] -> String k - | _ -> raise (Eval_error "keyword-name: expected keyword")); - - bind "symbol-name" (fun args -> - match args with - | [Symbol s] -> String s - | _ -> raise (Eval_error "symbol-name: expected symbol")); - - (* IO primitives *) +let make_server_env () = + let env = make_env () in + Sx_render.setup_render_env env; + setup_browser_stubs env; + setup_scope_env env; + setup_evaluator_bridge env; + setup_introspection env; + setup_type_operations env; + setup_html_tags env; setup_io_env env; - (* Initialize trampoline ref so HO primitives (map, filter, etc.) - can call SX lambdas. Must be done here (not sx_runtime.ml) - because Sx_ref is only available at the binary level. *) + can call SX lambdas. Must be done here because Sx_ref is only + available at the binary level, not in the library. *) Sx_primitives._sx_trampoline_fn := (fun v -> match v with | Thunk (body, closure_env) -> Sx_ref.eval_expr body (Env closure_env) | other -> other); - env @@ -1040,37 +828,30 @@ let rec dispatch env cmd = | exn -> send_error (Printexc.to_string exn)) | List [Symbol "vm-compile-adapter"] -> - (* Register JIT hook and pre-compile the compiler itself. - The compile function running on the VM makes all subsequent - JIT compilations near-instant (VM speed vs CEK speed). *) + (* Register JIT hook — all functions in the allowlist compile lazily + on first call. Pre-compile the compiler itself so subsequent + JIT compilations run at VM speed, not CEK speed. *) register_jit_hook env; - Printf.eprintf "[jit] JIT hook registered (lazy compilation active)\n%!"; - (* Pre-compile the compiler: compile → make-emitter → make-pool - and other compiler internals so they run on VM from the start *) - let compiler_fns = ["compile"; "compile-module"; "compile-expr"; - "compile-symbol"; "compile-dict"; "compile-list"; "compile-if"; - "compile-when"; "compile-and"; "compile-or"; "compile-begin"; - "compile-let"; "compile-letrec"; "compile-lambda"; "compile-define"; "compile-set"; - "compile-quote"; "compile-cond"; "compile-case"; "compile-case-clauses"; - "compile-thread"; "compile-thread-step"; "compile-defcomp"; - "compile-defmacro"; "compile-quasiquote"; "compile-call"; - "make-emitter"; "make-pool"; "emit-byte"; "emit-u16"; "emit-i16"; - "pool-add"; "resolve-scope"; "scope-resolve"] in let t0 = Unix.gettimeofday () in let count = ref 0 in - List.iter (fun name -> - match Hashtbl.find_opt env.bindings name with - | Some (Lambda l) when l.l_compiled = None -> - l.l_compiled <- Some Sx_vm.jit_failed_sentinel; - (match Sx_vm.jit_compile_lambda l env.bindings with - | Some cl -> - l.l_compiled <- Some cl; - incr count - | None -> ()) - | _ -> () - ) compiler_fns; + (* Pre-compile compiler internals only — these bootstrap the JIT. + Everything else (render, aser, parser) compiles lazily on first call. *) + StringSet.iter (fun name -> + if String.length name >= 7 && String.sub name 0 7 = "compile" + || List.mem name ["make-emitter"; "make-pool"; "make-scope"; "pool-add"; + "scope-define-local"; "scope-resolve"; + "emit-byte"; "emit-u16"; "emit-i16"; "emit-op"; "emit-const"; + "current-offset"; "patch-i16"] then + match Hashtbl.find_opt env.bindings name with + | Some (Lambda l) when l.l_compiled = None -> + l.l_compiled <- Some Sx_vm.jit_failed_sentinel; + (match Sx_vm.jit_compile_lambda l env.bindings with + | Some cl -> l.l_compiled <- Some cl; incr count + | None -> ()) + | _ -> () + ) !jit_allowlist; let dt = Unix.gettimeofday () -. t0 in - Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs\n%!" !count dt; + Printf.eprintf "[jit] Pre-compiled %d compiler functions in %.3fs (lazy JIT active)\n%!" !count dt; send_ok () | List [Symbol "jit-allow"; String name] -> diff --git a/shared/sx/query_registry.py b/shared/sx/query_registry.py index 3edda12..7c9e189 100644 --- a/shared/sx/query_registry.py +++ b/shared/sx/query_registry.py @@ -78,19 +78,18 @@ def clear(service: str | None = None) -> None: def load_query_file(filepath: str, service_name: str) -> list[QueryDef]: """Parse an .sx file and register any defquery definitions.""" from .parser import parse_all - from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline - _eval = lambda expr, env: _trampoline(_raw_eval(expr, env)) from .jinja_bridge import get_component_env with open(filepath, encoding="utf-8") as f: source = f.read() - env = dict(get_component_env()) - exprs = parse_all(source) - queries: list[QueryDef] = [] + # Use the jinja_bridge register_components path which handles + # defquery/defaction via the OCaml kernel + from .jinja_bridge import register_components + register_components(source, _defer_postprocess=True) - for expr in exprs: - _eval(expr, env) + env = get_component_env() + queries: list[QueryDef] = [] for val in env.values(): if isinstance(val, QueryDef): @@ -102,20 +101,15 @@ def load_query_file(filepath: str, service_name: str) -> list[QueryDef]: def load_action_file(filepath: str, service_name: str) -> list[ActionDef]: """Parse an .sx file and register any defaction definitions.""" - from .parser import parse_all - from .ref.sx_ref import eval_expr as _raw_eval, trampoline as _trampoline - _eval = lambda expr, env: _trampoline(_raw_eval(expr, env)) - from .jinja_bridge import get_component_env + from .jinja_bridge import get_component_env, register_components with open(filepath, encoding="utf-8") as f: source = f.read() - env = dict(get_component_env()) - exprs = parse_all(source) - actions: list[ActionDef] = [] + register_components(source, _defer_postprocess=True) - for expr in exprs: - _eval(expr, env) + env = get_component_env() + actions: list[ActionDef] = [] for val in env.values(): if isinstance(val, ActionDef): diff --git a/shared/sx/tests/test_components.py b/shared/sx/tests/test_components.py index 7655e6a..3093206 100644 --- a/shared/sx/tests/test_components.py +++ b/shared/sx/tests/test_components.py @@ -59,7 +59,7 @@ class TestCartMini: class TestAuthMenu: def test_logged_in(self): html = sx( - '(~auth-menu :user-email user-email :account-url account-url)', + '(~shared:fragments/auth-menu :user-email user-email :account-url account-url)', **{"user-email": "alice@example.com", "account-url": "https://account.example.com/"}, ) assert 'id="auth-menu-desktop"' in html @@ -70,7 +70,7 @@ class TestAuthMenu: def test_logged_out(self): html = sx( - '(~auth-menu :account-url account-url)', + '(~shared:fragments/auth-menu :account-url account-url)', **{"account-url": "https://account.example.com/"}, ) assert "fa-solid fa-key" in html @@ -78,7 +78,7 @@ class TestAuthMenu: def test_desktop_has_data_close_details(self): html = sx( - '(~auth-menu :user-email "x@y.com" :account-url "http://a")', + '(~shared:fragments/auth-menu :user-email "x@y.com" :account-url "http://a")', ) assert "data-close-details" in html @@ -86,7 +86,7 @@ class TestAuthMenu: """Both desktop and mobile spans are always rendered.""" for email in ["user@test.com", None]: html = sx( - '(~auth-menu :user-email user-email :account-url account-url)', + '(~shared:fragments/auth-menu :user-email user-email :account-url account-url)', **{"user-email": email, "account-url": "http://a"}, ) assert 'id="auth-menu-desktop"' in html diff --git a/sx/sxc/pages/_ocaml_helpers.py b/sx/sxc/pages/_ocaml_helpers.py new file mode 100644 index 0000000..ad3de31 --- /dev/null +++ b/sx/sxc/pages/_ocaml_helpers.py @@ -0,0 +1,222 @@ +"""OCaml bridge for page helper SX functions. + +Replaces the deleted sx_ref.py imports. Uses OcamlSync to call +SX functions defined in web/page-helpers.sx. +""" +from __future__ import annotations + +import os +import logging +from typing import Any + +_logger = logging.getLogger("sx.page_helpers") +_bridge = None +_loaded = False + + +def _get_bridge(): + """Get the shared OcamlSync bridge, loading page-helpers.sx on first use.""" + global _bridge, _loaded + from shared.sx.ocaml_sync import OcamlSync + if _bridge is None: + _bridge = OcamlSync() + _bridge._ensure() + if not _loaded: + _loaded = True + # Load files needed by page-helpers.sx + base = os.path.join(os.path.dirname(__file__), "../../..") + for f in ["spec/parser.sx", "spec/render.sx", + "web/adapter-html.sx", "web/adapter-sx.sx", + "web/web-forms.sx", "web/page-helpers.sx"]: + path = os.path.join(base, f) + if os.path.isfile(path): + try: + _bridge.load(path) + except Exception as e: + _logger.warning("Failed to load %s: %s", f, e) + return _bridge + + +def _py_to_sx(val: Any) -> str: + """Serialize a Python value to SX source text.""" + if val is None: + return "nil" + if isinstance(val, bool): + return "true" if val else "false" + if isinstance(val, (int, float)): + return str(val) + if isinstance(val, str): + escaped = val.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + return f'"{escaped}"' + if isinstance(val, (list, tuple)): + items = " ".join(_py_to_sx(v) for v in val) + return f"(list {items})" + if isinstance(val, dict): + pairs = " ".join(f":{k} {_py_to_sx(v)}" for k, v in val.items()) + return "{" + pairs + "}" + return f'"{val}"' + + +def _sx_to_py(text: str) -> Any: + """Parse an SX result back to Python. Handles strings, dicts, lists, nil.""" + text = text.strip() + if not text or text == "nil": + return None + if text == "true": + return True + if text == "false": + return False + # String result — the bridge already unescapes + if text.startswith('"') and text.endswith('"'): + return text[1:-1] + # For complex results, parse as SX and convert + from shared.sx.parser import parse_all + from shared.sx.types import Keyword, Symbol, NIL + exprs = parse_all(text) + if not exprs: + return text + + def _convert(val): + if val is None or val is NIL: + return None + if isinstance(val, bool): + return val + if isinstance(val, (int, float)): + return val + if isinstance(val, str): + return val + if isinstance(val, Keyword): + return val.name + if isinstance(val, Symbol): + return val.name + if isinstance(val, dict): + return {k: _convert(v) for k, v in val.items()} + if isinstance(val, list): + return [_convert(v) for v in val] + return str(val) + + return _convert(exprs[0]) + + +def call_sx(fn_name: str, *args: Any) -> Any: + """Call an SX function by name with Python args, return Python result.""" + bridge = _get_bridge() + sx_args = " ".join(_py_to_sx(a) for a in args) + sx_expr = f"({fn_name} {sx_args})" if sx_args else f"({fn_name})" + result = bridge.eval(sx_expr) + return _sx_to_py(result) + + +def evaluate(source: str, env: Any = None) -> Any: + """Evaluate SX source text, return result as SX string.""" + bridge = _get_bridge() + return bridge.eval(source) + + +def load_file(path: str) -> None: + """Load an .sx file into the bridge kernel.""" + bridge = _get_bridge() + bridge.load(path) + + +def eval_in_env(expr_sx: str) -> str: + """Evaluate an SX expression, return raw SX result string.""" + bridge = _get_bridge() + return bridge.eval(expr_sx) + + +# ---- Drop-in replacements for sx_ref.py functions ---- + +def build_component_source(data: dict) -> str: + return call_sx("build-component-source", data) or "" + +def categorize_special_forms(exprs: Any) -> dict: + return call_sx("categorize-special-forms", exprs) or {} + +def build_reference_data(raw: dict, detail_keys: list | None = None) -> dict: + return call_sx("build-reference-data", raw, detail_keys or []) or {} + +def build_attr_detail(slug: str) -> dict: + return call_sx("build-attr-detail", slug) or {} + +def build_header_detail(slug: str) -> dict: + return call_sx("build-header-detail", slug) or {} + +def build_event_detail(slug: str) -> dict: + return call_sx("build-event-detail", slug) or {} + +def build_bundle_analysis(pages: Any, components: Any) -> dict: + return call_sx("build-bundle-analysis", pages, components) or {} + +def build_routing_analysis(pages: Any, components: Any) -> dict: + return call_sx("build-routing-analysis", pages, components) or {} + +def build_affinity_analysis(components: Any, pages: Any) -> dict: + return call_sx("build-affinity-analysis", components, pages) or {} + + +def make_env(): + """No-op — env is managed by the OCaml bridge.""" + return {} + + +def eval_expr(expr, env=None): + """Evaluate a parsed SX expression via OCaml bridge.""" + from shared.sx.parser import serialize + bridge = _get_bridge() + sx_text = serialize(expr) if not isinstance(expr, str) else expr + result = bridge.eval(sx_text) + from shared.sx.parser import parse + return parse(result) if result else None + + +def trampoline(val): + """No-op — OCaml bridge doesn't return thunks.""" + return val + + +def call_lambda(fn, args, env=None): + """Call a lambda via the OCaml bridge.""" + from shared.sx.parser import serialize + bridge = _get_bridge() + parts = [serialize(fn)] + [serialize(a) for a in args] + result = bridge.eval(f"({' '.join(parts)})") + from shared.sx.parser import parse + return parse(result) if result else None + + +def render_to_html(expr, env=None): + """Render an SX expression to HTML via OCaml bridge.""" + from shared.sx.parser import serialize + bridge = _get_bridge() + sx_text = serialize(expr) if not isinstance(expr, str) else expr + return bridge.eval(f'(render-to-html (quote {sx_text}) (env))') + + +# Router/deps/engine helpers — these are loaded from .sx files +# and made available via eval. The try/except pattern in helpers.py +# falls back to loading the .sx file directly, which works. +# These stubs exist so the import doesn't fail. +split_path_segments = None +parse_route_pattern = None +match_route_segments = None +match_route = None +find_matching_route = None +make_route_segment = None +scan_refs = None +scan_components_from_source = None +transitive_deps = None +compute_all_deps = None +components_needed = None +page_component_bundle = None +page_css_classes = None +scan_io_refs = None +transitive_io_refs = None +compute_all_io_refs = None +component_pure_p = None +parse_time = None +parse_trigger_spec = None +default_trigger = None +parse_swap_spec = None +parse_retry_spec = None +filter_params = None diff --git a/sx/sxc/pages/helpers.py b/sx/sxc/pages/helpers.py index 086b3d3..9e0463c 100644 --- a/sx/sxc/pages/helpers.py +++ b/sx/sxc/pages/helpers.py @@ -45,7 +45,7 @@ def _component_source(name: str) -> str: from shared.sx.jinja_bridge import get_component_env from shared.sx.parser import serialize from shared.sx.types import Component, Island - from shared.sx.ref.sx_ref import build_component_source + from ._ocaml_helpers import build_component_source comp = get_component_env().get(name) if isinstance(comp, Island): @@ -109,7 +109,7 @@ def _special_forms_data() -> dict: """Parse special-forms.sx and return categorized form data.""" import os from shared.sx.parser import parse_all - from shared.sx.ref.sx_ref import categorize_special_forms + from ._ocaml_helpers import categorize_special_forms ref_dir = _ref_dir() spec_path = os.path.join(ref_dir, "special-forms.sx") @@ -125,7 +125,7 @@ def _reference_data(slug: str) -> dict: REQUEST_HEADERS, RESPONSE_HEADERS, EVENTS, JS_API, ATTR_DETAILS, HEADER_DETAILS, ) - from shared.sx.ref.sx_ref import build_reference_data + from ._ocaml_helpers import build_reference_data # Build raw data dict and detail keys based on slug if slug == "attributes": @@ -202,7 +202,7 @@ def _js_translate_define(expr: list, name: str) -> str | None: if _JS_SX_ENV is None: from shared.sx.ref.run_js_sx import load_js_sx _JS_SX_ENV = load_js_sx() - from shared.sx.ref.sx_ref import evaluate + from ._ocaml_helpers import evaluate from shared.sx.types import Symbol env = dict(_JS_SX_ENV) env["_defines"] = [[name, expr]] @@ -756,7 +756,7 @@ def _self_hosting_data(ref_dir: str) -> dict: import os from shared.sx.parser import parse_all from shared.sx.types import Symbol - from shared.sx.ref.sx_ref import evaluate, make_env + from ._ocaml_helpers import evaluate, make_env from shared.sx.ref.bootstrap_py import extract_defines, compile_ref_to_py, PyEmitter try: @@ -829,7 +829,7 @@ def _js_self_hosting_data(ref_dir: str) -> dict: """Run js.sx live: load into evaluator, translate all spec defines.""" import os from shared.sx.types import Symbol - from shared.sx.ref.sx_ref import evaluate + from ._ocaml_helpers import evaluate from shared.sx.ref.run_js_sx import load_js_sx from shared.sx.ref.platform_js import extract_defines @@ -881,7 +881,7 @@ def _bundle_analyzer_data() -> dict: from shared.sx.deps import components_needed, scan_components_from_sx from shared.sx.parser import serialize from shared.sx.types import Component, Macro - from shared.sx.ref.sx_ref import build_bundle_analysis + from ._ocaml_helpers import build_bundle_analysis env = get_component_env() total_components = sum(1 for v in env.values() if isinstance(v, Component)) @@ -937,7 +937,7 @@ def _routing_analyzer_data() -> dict: from shared.sx.pages import get_all_pages from shared.sx.parser import serialize as sx_serialize from shared.sx.helpers import _sx_literal - from shared.sx.ref.sx_ref import build_routing_analysis + from ._ocaml_helpers import build_routing_analysis # I/O edge: extract page data from page registry pages_raw = [] @@ -989,7 +989,7 @@ def _attr_detail_data(slug: str) -> dict: """Return attribute detail data for a specific attribute slug.""" from content.pages import ATTR_DETAILS from shared.sx.helpers import sx_call - from shared.sx.ref.sx_ref import build_attr_detail + from ._ocaml_helpers import build_attr_detail detail = ATTR_DETAILS.get(slug) result = build_attr_detail(slug, detail) @@ -1004,7 +1004,7 @@ def _header_detail_data(slug: str) -> dict: """Return header detail data for a specific header slug.""" from content.pages import HEADER_DETAILS from shared.sx.helpers import sx_call - from shared.sx.ref.sx_ref import build_header_detail + from ._ocaml_helpers import build_header_detail result = build_header_detail(slug, HEADER_DETAILS.get(slug)) demo_name = result.get("header-demo") @@ -1017,7 +1017,7 @@ def _event_detail_data(slug: str) -> dict: """Return event detail data for a specific event slug.""" from content.pages import EVENT_DETAILS from shared.sx.helpers import sx_call - from shared.sx.ref.sx_ref import build_event_detail + from ._ocaml_helpers import build_event_detail result = build_event_detail(slug, EVENT_DETAILS.get(slug)) demo_name = result.get("event-demo") @@ -1031,7 +1031,7 @@ def _run_spec_tests() -> dict: import os import time from shared.sx.parser import parse_all - from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline + from ._ocaml_helpers import eval_expr as _eval, trampoline as _trampoline ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") if not os.path.isdir(ref_dir): @@ -1105,7 +1105,7 @@ def _run_modular_tests(spec_name: str) -> dict: import os import time from shared.sx.parser import parse_all - from shared.sx.ref.sx_ref import eval_expr as _eval, trampoline as _trampoline + from ._ocaml_helpers import eval_expr as _eval, trampoline as _trampoline from shared.sx.types import Symbol, Keyword, Lambda, NIL ref_dir = os.path.join(os.path.dirname(__file__), "..", "..", "shared", "sx", "ref") @@ -1175,7 +1175,7 @@ def _run_modular_tests(spec_name: str) -> dict: def render_html(sx_source): try: - from shared.sx.ref.sx_ref import render_to_html as _render_to_html + from ._ocaml_helpers import render_to_html as _render_to_html except ImportError: return "" exprs = parse_all(sx_source) @@ -1187,7 +1187,7 @@ def _run_modular_tests(spec_name: str) -> dict: def _call_sx(fn, args, caller_env): if isinstance(fn, Lambda): - from shared.sx.ref.sx_ref import call_lambda as _call_lambda + from ._ocaml_helpers import call_lambda as _call_lambda return _trampoline(_call_lambda(fn, list(args), caller_env)) return fn(*args) @@ -1259,7 +1259,7 @@ def _run_modular_tests(spec_name: str) -> dict: # Load module functions from bootstrap if sn == "router": try: - from shared.sx.ref.sx_ref import ( + from ._ocaml_helpers import ( split_path_segments, parse_route_pattern, match_route_segments, @@ -1277,7 +1277,7 @@ def _run_modular_tests(spec_name: str) -> dict: eval_file("router.sx") elif sn == "deps": try: - from shared.sx.ref.sx_ref import ( + from ._ocaml_helpers import ( scan_refs, scan_components_from_source, transitive_deps, compute_all_deps, components_needed, page_component_bundle, @@ -1302,7 +1302,7 @@ def _run_modular_tests(spec_name: str) -> dict: env["test-env"] = lambda: env elif sn == "engine": try: - from shared.sx.ref.sx_ref import ( + from ._ocaml_helpers import ( parse_time, parse_trigger_spec, default_trigger, parse_swap_spec, parse_retry_spec, filter_params, ) @@ -1345,7 +1345,7 @@ def _run_modular_tests(spec_name: str) -> dict: env[stub] = _noop # Load engine (orchestration depends on it) try: - from shared.sx.ref.sx_ref import ( + from ._ocaml_helpers import ( parse_time, parse_trigger_spec, default_trigger, parse_swap_spec, parse_retry_spec, filter_params, ) @@ -1459,7 +1459,7 @@ def _affinity_demo_data() -> dict: from shared.sx.jinja_bridge import get_component_env from shared.sx.types import Component from shared.sx.pages import get_all_pages - from shared.sx.ref.sx_ref import build_affinity_analysis + from ._ocaml_helpers import build_affinity_analysis # I/O edge: extract component data and page render plans env = get_component_env() @@ -1530,9 +1530,9 @@ def _prove_data() -> dict: """ import time from shared.sx.parser import parse_all - from shared.sx.ref.sx_ref import evaluate + from ._ocaml_helpers import evaluate from shared.sx.primitives import all_primitives - from shared.sx.ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda + from ._ocaml_helpers import trampoline as _trampoline, call_lambda as _call_lambda env = all_primitives() @@ -1643,7 +1643,7 @@ def _page_helpers_demo_data() -> dict: import os import time from shared.sx.parser import parse_all - from shared.sx.ref.sx_ref import ( + from ._ocaml_helpers import ( categorize_special_forms, build_reference_data, build_attr_detail, build_component_source, build_routing_analysis, diff --git a/sx/sxc/pages/sx_router.py b/sx/sxc/pages/sx_router.py index 77fe60e..01b73ee 100644 --- a/sx/sxc/pages/sx_router.py +++ b/sx/sxc/pages/sx_router.py @@ -173,10 +173,7 @@ async def eval_sx_url(raw_path: str) -> Any: return None else: # Python fallback - if os.environ.get("SX_USE_REF") == "1": - from shared.sx.ref.async_eval_ref import async_eval - else: - from shared.sx.async_eval import async_eval + from shared.sx.async_eval import async_eval try: page_ast = await async_eval(expr, env, ctx)