Transparent lazy module loading — code loads like data
When the VM or CEK hits an undefined symbol, it checks a symbol→library index (built from manifest exports at boot), loads the library that exports it, and returns the value. Execution continues as if the module was always loaded. No import statements, no load-library! calls, no Suspense boundaries — just call the function. This is the same mechanism as IO suspension for data fetching. The programmer doesn't distinguish between calling a local function and calling one that needs its module fetched first. The runtime treats code as just another resource. Implementation: - _symbol_resolve_hook in sx_types.ml — called by env_get_id (CEK path) and vm_global_get (VM path) when a symbol isn't found - Symbol→library index built from manifest exports in sx-platform.js - __resolve-symbol native calls __sxLoadLibrary, module loads, symbol appears in globals, execution resumes - compile-modules.js extracts export lists into module-manifest.json - Playground page demonstrates: (freeze-scope) triggers freeze.sxbc download transparently on first use 2650/2650 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -500,7 +500,7 @@ and cek_step state =
|
||||
|
||||
(* step-eval *)
|
||||
and step_eval state =
|
||||
(let expr = (cek_control (state)) in let env = (cek_env (state)) in let kont = (cek_kont (state)) in (let _match_val = (type_of (expr)) in (if _match_val = (String "number") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "string") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "boolean") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "nil") then (make_cek_value (Nil) (env) (kont)) else (if _match_val = (String "symbol") then (let name = (symbol_name (expr)) in (let val' = (if sx_truthy ((env_has (env) (name))) then (env_get (env) (name)) else (if sx_truthy ((is_primitive (name))) then (get_primitive (name)) else (if sx_truthy ((prim_call "=" [name; (String "true")])) then (Bool true) else (if sx_truthy ((prim_call "=" [name; (String "false")])) then (Bool false) else (if sx_truthy ((prim_call "=" [name; (String "nil")])) then Nil else (raise (Eval_error (value_to_str (String (sx_str [(String "Undefined symbol: "); name])))))))))) in (let () = ignore ((if sx_truthy ((let _and = (is_nil (val')) in if not (sx_truthy _and) then _and else (prim_call "starts-with?" [name; (String "~")]))) then (debug_log ((String "Component not found:")) (name)) else Nil)) in (make_cek_value (val') (env) (kont))))) else (if _match_val = (String "keyword") then (make_cek_value ((keyword_name (expr))) (env) (kont)) else (if _match_val = (String "dict") then (let ks = (prim_call "keys" [expr]) in (if sx_truthy ((empty_p (ks))) then (make_cek_value ((Dict (Hashtbl.create 0))) (env) (kont)) else (let first_key = (first (ks)) in let remaining_entries = ref ((List [])) in (let () = ignore ((List.iter (fun k -> ignore ((remaining_entries := sx_append_b !remaining_entries (List [k; (get (expr) (k))]); Nil))) (sx_to_list (rest (ks))); Nil)) in (make_cek_state ((get (expr) (first_key))) (env) ((kont_push ((make_dict_frame (!remaining_entries) ((List [(List [first_key])])) (env))) (kont)))))))) else (if _match_val = (String "list") then (if sx_truthy ((empty_p (expr))) then (make_cek_value ((List [])) (env) (kont)) else (step_eval_list (expr) (env) (kont))) else (make_cek_value (expr) (env) (kont))))))))))))
|
||||
(let expr = (cek_control (state)) in let env = (cek_env (state)) in let kont = (cek_kont (state)) in (let _match_val = (type_of (expr)) in (if _match_val = (String "number") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "string") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "boolean") then (make_cek_value (expr) (env) (kont)) else (if _match_val = (String "nil") then (make_cek_value (Nil) (env) (kont)) else (if _match_val = (String "symbol") then (let name = (symbol_name (expr)) in (let val' = (if sx_truthy ((env_has (env) (name))) then (env_get (env) (name)) else (if sx_truthy ((is_primitive (name))) then (get_primitive (name)) else (if sx_truthy ((prim_call "=" [name; (String "true")])) then (Bool true) else (if sx_truthy ((prim_call "=" [name; (String "false")])) then (Bool false) else (if sx_truthy ((prim_call "=" [name; (String "nil")])) then Nil else (match !_symbol_resolve_hook with Some hook -> (match hook (value_to_str name) with Some v -> v | None -> raise (Eval_error ("Undefined symbol: " ^ value_to_str name))) | None -> raise (Eval_error ("Undefined symbol: " ^ value_to_str name)))))))) in (let () = ignore ((if sx_truthy ((let _and = (is_nil (val')) in if not (sx_truthy _and) then _and else (prim_call "starts-with?" [name; (String "~")]))) then (debug_log ((String "Component not found:")) (name)) else Nil)) in (make_cek_value (val') (env) (kont))))) else (if _match_val = (String "keyword") then (make_cek_value ((keyword_name (expr))) (env) (kont)) else (if _match_val = (String "dict") then (let ks = (prim_call "keys" [expr]) in (if sx_truthy ((empty_p (ks))) then (make_cek_value ((Dict (Hashtbl.create 0))) (env) (kont)) else (let first_key = (first (ks)) in let remaining_entries = ref ((List [])) in (let () = ignore ((List.iter (fun k -> ignore ((remaining_entries := sx_append_b !remaining_entries (List [k; (get (expr) (k))]); Nil))) (sx_to_list (rest (ks))); Nil)) in (make_cek_state ((get (expr) (first_key))) (env) ((kont_push ((make_dict_frame (!remaining_entries) ((List [(List [first_key])])) (env))) (kont)))))))) else (if _match_val = (String "list") then (if sx_truthy ((empty_p (expr))) then (make_cek_value ((List [])) (env) (kont)) else (step_eval_list (expr) (env) (kont))) else (make_cek_value (expr) (env) (kont))))))))))))
|
||||
|
||||
(* step-sf-raise *)
|
||||
and step_sf_raise args env kont =
|
||||
|
||||
@@ -259,6 +259,13 @@ let _vm_global_set_hook : (string -> value -> unit) option ref = ref None
|
||||
If set, the hook loads the library and returns true; cek_run then resumes. *)
|
||||
let _import_hook : (value -> bool) option ref = ref None
|
||||
|
||||
(* Optional hook: called by vm_global_get when a symbol isn't found.
|
||||
Receives the symbol name. If the hook can resolve it (e.g. by loading a
|
||||
library that exports it), it returns Some value. Otherwise None.
|
||||
This enables transparent lazy module loading — just use a symbol and
|
||||
the VM loads whatever module provides it. *)
|
||||
let _symbol_resolve_hook : (string -> value option) option ref = ref None
|
||||
|
||||
let env_bind env name v =
|
||||
Hashtbl.replace env.bindings (intern name) v;
|
||||
(match !_env_bind_hook with Some f -> f env name v | None -> ());
|
||||
@@ -278,7 +285,16 @@ let rec env_get_id env id name =
|
||||
match env.parent with
|
||||
| Some p -> env_get_id p id name
|
||||
| None ->
|
||||
raise (Eval_error ("Undefined symbol: " ^ name))
|
||||
(* Symbol not in any scope — try the resolve hook (transparent lazy loading).
|
||||
The hook loads the module that exports this symbol, making it available. *)
|
||||
match !_symbol_resolve_hook with
|
||||
| Some hook ->
|
||||
(match hook name with
|
||||
| Some v ->
|
||||
(* Cache in the root env so subsequent lookups are instant *)
|
||||
Hashtbl.replace env.bindings id v; v
|
||||
| None -> raise (Eval_error ("Undefined symbol: " ^ name)))
|
||||
| None -> raise (Eval_error ("Undefined symbol: " ^ name))
|
||||
|
||||
let env_get env name = env_get_id env (intern name) name
|
||||
|
||||
|
||||
@@ -204,20 +204,26 @@ let vm_global_get vm_val frame_val name =
|
||||
| None ->
|
||||
(* Walk closure env chain *)
|
||||
let f = unwrap_frame frame_val in
|
||||
let not_found () =
|
||||
(* Try evaluator's primitive table *)
|
||||
try prim_call n [] with _ ->
|
||||
(* Try symbol resolve hook — transparent lazy module loading *)
|
||||
match !_symbol_resolve_hook with
|
||||
| Some hook ->
|
||||
(match hook n with
|
||||
| Some v -> v
|
||||
| None -> raise (Eval_error ("VM undefined: " ^ n)))
|
||||
| None -> raise (Eval_error ("VM undefined: " ^ n))
|
||||
in
|
||||
(match f.vf_closure.vm_closure_env with
|
||||
| Some env ->
|
||||
let id = intern n in
|
||||
let rec find_env e =
|
||||
match Hashtbl.find_opt e.bindings id with
|
||||
| Some v -> v
|
||||
| None -> (match e.parent with Some p -> find_env p | None ->
|
||||
(* Try evaluator's primitive table as last resort *)
|
||||
(try prim_call n [] with _ ->
|
||||
raise (Eval_error ("VM undefined: " ^ n))))
|
||||
| None -> (match e.parent with Some p -> find_env p | None -> not_found ())
|
||||
in find_env env
|
||||
| None ->
|
||||
(try prim_call n [] with _ ->
|
||||
raise (Eval_error ("VM undefined: " ^ n))))
|
||||
| None -> not_found ())
|
||||
|
||||
let vm_global_set vm_val frame_val name v =
|
||||
let m = unwrap_vm vm_val in
|
||||
|
||||
Reference in New Issue
Block a user