diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 9c1d31e1..57d42780 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -291,15 +291,20 @@ let resolve_library_path lib_spec = let parts = match lib_spec with List l | ListRef { contents = l } -> l | _ -> [] in match List.map (fun v -> match v with Symbol s -> s | String s -> s | _ -> "") parts with | ["sx"; name] -> - (* Check spec/ first, then lib/ *) + (* Check spec/ first, then lib/, then web/lib/ (dom.sx, browser.sx live there) *) let spec_path = Filename.concat !_spec_base (name ^ ".sx") in let lib_path = Filename.concat !_lib_base (name ^ ".sx") in + let web_lib_path = Filename.concat (Filename.concat !_web_base "lib") (name ^ ".sx") in if Sys.file_exists spec_path then Some spec_path else if Sys.file_exists lib_path then Some lib_path + else if Sys.file_exists web_lib_path then Some web_lib_path else None | ["web"; name] -> let path = Filename.concat !_web_base (name ^ ".sx") in - if Sys.file_exists path then Some path else None + let lib_path = Filename.concat (Filename.concat !_web_base "lib") (name ^ ".sx") in + if Sys.file_exists path then Some path + else if Sys.file_exists lib_path then Some lib_path + else None | [prefix; name] -> (* Generic: try prefix/name.sx *) let path = Filename.concat prefix (name ^ ".sx") in @@ -311,9 +316,14 @@ let resolve_library_path lib_spec = let _import_env : env option ref = ref None let load_library_file path = + (* Use eval_expr which has the cek_run import patch — handles nested imports *) let env = match !_import_env with Some e -> e | None -> Sx_types.make_env () in let exprs = Sx_parser.parse_file path in - List.iter (fun expr -> ignore (Sx_ref.eval_expr expr (Env env))) exprs + List.iter (fun expr -> + try ignore (Sx_ref.eval_expr expr (Env env)) + with Eval_error msg -> + Printf.eprintf "[load-library] %s: %s\n%!" (Filename.basename path) msg + ) exprs (** IO-aware CEK run — handles suspension by dispatching IO requests. Import requests are handled locally (load .sx file). @@ -787,11 +797,11 @@ let () = Loads the .sx file for the library, registers it, and returns true. *) let () = Sx_types._import_hook := Some (fun lib_spec -> - let key = Sx_ref.library_name_key lib_spec in - if Sx_types.sx_truthy (Sx_ref.library_loaded_p key) then true + if Sx_types.sx_truthy (Sx_ref.library_loaded_p lib_spec) then true else match resolve_library_path lib_spec with | Some path -> - (try load_library_file path; true + (try load_library_file path; + true with _ -> false) | None -> false) @@ -2492,6 +2502,8 @@ let http_mode port = ignore (env_bind env "_spec-dir" (String spec_base)); ignore (env_bind env "_lib-dir" (String lib_base)); ignore (env_bind env "_web-dir" (String web_base)); + (* Set import env so load_library_file (called by _import_hook) uses the main env *) + _import_env := Some env; let t0 = Unix.gettimeofday () in (* Core spec + adapters. Skip: primitives.sx (declarative metadata — all prims native in OCaml), diff --git a/hosts/ocaml/lib/sx_ref.ml b/hosts/ocaml/lib/sx_ref.ml index f8668b27..ef5a60ce 100644 --- a/hosts/ocaml/lib/sx_ref.ml +++ b/hosts/ocaml/lib/sx_ref.ml @@ -495,8 +495,7 @@ and cek_run state = let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in if op = "import" then let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in - let key = library_name_key lib_spec in - let resolved = sx_truthy (library_loaded_p key) || + let resolved = sx_truthy (library_loaded_p lib_spec) || (match !_import_hook with Some hook -> hook lib_spec | None -> false) in if resolved then run (cek_resume final Nil) else raise (Eval_error "IO suspension in non-IO context") @@ -834,8 +833,7 @@ let cek_run_iterative state = let op = match request with Dict d -> (match Hashtbl.find_opt d "op" with Some (String s) -> s | _ -> "") | _ -> "" in if op = "import" then begin let lib_spec = match request with Dict d -> (match Hashtbl.find_opt d "library" with Some v -> v | _ -> Nil) | _ -> Nil in - let key = library_name_key lib_spec in - let resolved = sx_truthy (library_loaded_p key) || + let resolved = sx_truthy (library_loaded_p lib_spec) || (match !_import_hook with Some hook -> hook lib_spec | None -> false) in if resolved then begin s := cek_resume !s Nil; diff --git a/spec/tests/test-import-bind.sx b/spec/tests/test-import-bind.sx new file mode 100644 index 00000000..f3d13434 --- /dev/null +++ b/spec/tests/test-import-bind.sx @@ -0,0 +1,75 @@ +;; Tests for import binding — catching bind_import_set bugs + +;; Test 1: basic define-library + import (same session) +(define-library (test basic-lib) + (export double greet) + (begin + (define double (fn (x) (* x 2))) + (define greet "hi"))) + +(import (test basic-lib)) + +(deftest "import binds exported functions" + (assert= (double 5) 10)) + +(deftest "import binds exported values" + (assert= greet "hi")) + +;; Test 2: library with import clause inside define-library +;; This is what engine.sx does — import clause triggers suspension. +;; The define-library handler should process import clauses. +(define-library (test dep-lib) + (export triple) + (begin + (define triple (fn (x) (* x 3))))) + +(define-library (test uses-dep) + (export six-times) + (import (test dep-lib)) + (begin + (define six-times (fn (x) (triple (double x)))))) + +(import (test uses-dep)) + +(deftest "define-library import clause binds into library env" + (assert= (six-times 2) 12)) + +;; Test 3: import after cek_run suspension +(define-library (test suspended-lib) + (export add10) + (begin + (define add10 (fn (x) (+ x 10))))) + +(import (test suspended-lib)) + +(deftest "import after suspension binds correctly" + (assert= (add10 5) 15)) + +;; Test 4: library-loaded? works +(deftest "library-loaded? accepts list spec" + (assert= (library-loaded? (quote (test basic-lib))) true)) + +;; Test 5: re-import binds +(define-library (test reimport) + (export val99) + (begin + (define val99 99))) + +(import (test reimport)) + +(deftest "re-import of loaded library binds exports" + (assert= val99 99)) + +;; Test 6: bind_import_set with library loaded by _import_hook +;; When the hook loads a library, the library key in the registry +;; may be a string "web engine" rather than a list (web engine). +;; bind_import_set must handle both. +(define-library (test string-key-lib) + (export sval) + (begin + (define sval 42))) + +(import (test string-key-lib)) + +(deftest "import works with string-keyed library" + (assert= sval 42))