diff --git a/hosts/ocaml/bin/run_tests.ml b/hosts/ocaml/bin/run_tests.ml index 93a865c8..c4730127 100644 --- a/hosts/ocaml/bin/run_tests.ml +++ b/hosts/ocaml/bin/run_tests.ml @@ -610,24 +610,13 @@ let make_test_env () = island | _ -> Nil))); - (* defio — IO registry for platform suspension points *) - let io_registry = Hashtbl.create 64 in - ignore (Sx_types.env_bind env "__io-registry" (Dict io_registry)); - ignore (Sx_ref.register_special_form (String "defio") (NativeFn ("defio", fun sf_args -> - let raw_args = match sf_args with - | [List a; Env _] | [ListRef { contents = a }; Env _] -> a - | _ -> [] in - match raw_args with - | String name :: rest -> - let entry = Hashtbl.create 8 in - let rec parse = function - | Keyword k :: v :: rest -> Hashtbl.replace entry k v; parse rest - | _ -> () in - parse rest; - Hashtbl.replace entry "name" (String name); - Hashtbl.replace io_registry name (Dict entry); - Dict entry - | _ -> Nil))); + (* IO registry — spec-level defio populates *io-registry* in evaluator. + Bind accessor functions + __io-registry alias for backward compat. *) + ignore (Sx_types.env_bind env "__io-registry" Sx_ref._io_registry_); + bind "io-registered?" (fun args -> match args with [String n] -> Sx_ref.io_registered_p (String n) | _ -> Bool false); + bind "io-lookup" (fun args -> match args with [String n] -> Sx_ref.io_lookup (String n) | _ -> Nil); + bind "io-names" (fun _args -> Sx_ref.io_names ()); + bind "io-register!" (fun args -> match args with [String n; spec] -> Sx_ref.io_register_b (String n) spec | _ -> Nil); (* --- Primitives for canonical.sx / content tests --- *) bind "contains-char?" (fun args -> diff --git a/hosts/ocaml/bin/sx_server.ml b/hosts/ocaml/bin/sx_server.ml index 6d260e0b..43620c16 100644 --- a/hosts/ocaml/bin/sx_server.ml +++ b/hosts/ocaml/bin/sx_server.ml @@ -2007,26 +2007,9 @@ let http_setup_declarative_stubs env = noop "defaction"; noop "defrelation"; noop "defstyle"; - (* IO registry — starts empty, platforms extend via defio. - defio is a special form that populates __io-registry with metadata - about suspension points (IO ops that require platform resolution). *) - let io_registry = Hashtbl.create 64 in - ignore (env_bind env "__io-registry" (Dict io_registry)); - ignore (Sx_ref.register_special_form (String "defio") (NativeFn ("defio", fun sf_args -> - let raw_args = match sf_args with - | [List a; Env _] | [ListRef { contents = a }; Env _] -> a - | _ -> [] in - match raw_args with - | String name :: rest -> - let entry = Hashtbl.create 8 in - let rec parse = function - | Keyword k :: v :: rest -> Hashtbl.replace entry k v; parse rest - | _ -> () in - parse rest; - Hashtbl.replace entry "name" (String name); - Hashtbl.replace io_registry name (Dict entry); - Dict entry - | _ -> Nil))) + (* IO registry — spec-level defio populates *io-registry* in evaluator. + Alias as __io-registry for backward compat. *) + ignore (env_bind env "__io-registry" Sx_ref._io_registry_) let http_setup_platform_constructors env = (* Platform constructor functions expected by evaluator.sx. diff --git a/hosts/ocaml/browser/sx_browser.ml b/hosts/ocaml/browser/sx_browser.ml index 3874aa25..92357ae6 100644 --- a/hosts/ocaml/browser/sx_browser.ml +++ b/hosts/ocaml/browser/sx_browser.ml @@ -670,26 +670,9 @@ let () = bind "define-page-helper" (fun _ -> Nil); - (* IO registry — starts empty in browser. Platforms extend via defio. - Browser has zero suspension points initially; future browser IO - (lazy module loads, fetch-request) will add entries here. *) - let io_registry = Hashtbl.create 16 in - ignore (env_bind global_env "__io-registry" (Dict io_registry)); - ignore (Sx_ref.register_special_form (String "defio") (NativeFn ("defio", fun sf_args -> - let raw_args = match sf_args with - | [List a; Env _] | [ListRef { contents = a }; Env _] -> a - | _ -> [] in - match raw_args with - | String name :: rest -> - let entry = Hashtbl.create 8 in - let rec parse = function - | Keyword k :: v :: rest -> Hashtbl.replace entry k v; parse rest - | _ -> () in - parse rest; - Hashtbl.replace entry "name" (String name); - Hashtbl.replace io_registry name (Dict entry); - Dict entry - | _ -> Nil))); + (* IO registry — spec-level defio populates *io-registry* in evaluator. + Alias as __io-registry for backward compat. *) + ignore (env_bind global_env "__io-registry" Sx_ref._io_registry_); (* --- Render --- *) Sx_render.setup_render_env global_env; diff --git a/hosts/ocaml/lib/sx_ref.ml b/hosts/ocaml/lib/sx_ref.ml index e7f6bf05..befc1de6 100644 --- a/hosts/ocaml/lib/sx_ref.ml +++ b/hosts/ocaml/lib/sx_ref.ml @@ -329,6 +329,30 @@ and library_exports spec = and register_library spec exports = (sx_dict_set_b _library_registry_ (library_name_key (spec)) (let _d = Hashtbl.create 1 in Hashtbl.replace _d "exports" exports; Dict _d)) +(* *io-registry* *) +and _io_registry_ = + (Dict (Hashtbl.create 0)) + +(* io-register! *) +and io_register_b name spec = + (sx_dict_set_b _io_registry_ name spec) + +(* io-registered? *) +and io_registered_p name = + (prim_call "has-key?" [_io_registry_; name]) + +(* io-lookup *) +and io_lookup name = + (get (_io_registry_) (name)) + +(* io-names *) +and io_names () = + (prim_call "keys" [_io_registry_]) + +(* step-sf-io *) +and step_sf_io args env kont = + (let name = (first (args)) in let io_args = (rest (args)) in (let () = ignore ((if sx_truthy ((Bool (not (sx_truthy ((io_registered_p (name))))))) then (raise (Eval_error (value_to_str (String (sx_str [(String "io: unknown operation '"); name; (String "' — not in *io-registry*")]))))) else Nil)) in (make_cek_state ((cons ((Symbol "perform")) ((List [(let _d = Hashtbl.create 2 in Hashtbl.replace _d "args" io_args; Hashtbl.replace _d "op" name; Dict _d)])))) (env) (kont)))) + (* *strict* *) and _strict_ = !_strict_ref @@ -401,6 +425,14 @@ and parse_comp_params params_expr = and sf_defisland args env = (let name_sym = (first (args)) in let params_raw = (nth (args) ((Number 1.0))) in let body_exprs = (prim_call "slice" [args; (Number 2.0)]) in let body = (if sx_truthy ((prim_call "=" [(len (body_exprs)); (Number 1.0)])) then (first (body_exprs)) else (cons ((make_symbol ((String "begin")))) (body_exprs))) in let comp_name = (strip_prefix ((symbol_name (name_sym))) ((String "~"))) in let parsed = (parse_comp_params (params_raw)) in let params = (first (parsed)) in let has_children = (nth (parsed) ((Number 1.0))) in (let island = (make_island (comp_name) (params) (has_children) (body) (env)) in (let () = ignore ((if sx_truthy ((env_has (env) ((String "*current-file*")))) then (component_set_file_b (island) ((env_get (env) ((String "*current-file*"))))) else Nil)) in (let () = ignore ((env_bind env (sx_to_string (symbol_name (name_sym))) island)) in island)))) +(* defio-parse-kwargs! *) +and defio_parse_kwargs_b spec remaining = + (if sx_truthy ((let _and = (Bool (not (sx_truthy ((empty_p (remaining)))))) in if not (sx_truthy _and) then _and else (let _and = (prim_call ">=" [(len (remaining)); (Number 2.0)]) in if not (sx_truthy _and) then _and else (keyword_p ((first (remaining))))))) then (let () = ignore ((sx_dict_set_b spec (keyword_name ((first (remaining)))) (nth (remaining) ((Number 1.0))))) in (defio_parse_kwargs_b (spec) ((rest ((rest (remaining))))))) else Nil) + +(* sf-defio *) +and sf_defio args env = + (let name = (first (args)) in let spec = (Dict (Hashtbl.create 0)) in (let () = ignore ((sx_dict_set_b spec (String "name") name)) in (let () = ignore ((defio_parse_kwargs_b (spec) ((rest (args))))) in (let () = ignore ((io_register_b (name) (spec))) in spec)))) + (* sf-defmacro *) and sf_defmacro args env = (let name_sym = (first (args)) in let params_raw = (nth (args) ((Number 1.0))) in let body = (nth (args) ((Number 2.0))) in let parsed = (parse_macro_params (params_raw)) in let params = (first (parsed)) in let rest_param = (nth (parsed) ((Number 1.0))) in (let mac = (make_macro (params) (rest_param) (body) (env) ((symbol_name (name_sym)))) in (let () = ignore ((env_bind env (sx_to_string (symbol_name (name_sym))) mac)) in mac))) @@ -467,7 +499,7 @@ and step_sf_guard args env kont = (* step-eval-list *) and step_eval_list expr env kont = - (let head = (first (expr)) in let args = (rest (expr)) in (if sx_truthy ((Bool (not (sx_truthy ((let _or = (prim_call "=" [(type_of (head)); (String "symbol")]) in if sx_truthy _or then _or else (let _or = (prim_call "=" [(type_of (head)); (String "lambda")]) in if sx_truthy _or then _or else (prim_call "=" [(type_of (head)); (String "list")])))))))) then (if sx_truthy ((empty_p (expr))) then (make_cek_value ((List [])) (env) (kont)) else (make_cek_state ((first (expr))) (env) ((kont_push ((make_map_frame (Nil) ((rest (expr))) ((List [])) (env))) (kont))))) else (if sx_truthy ((prim_call "=" [(type_of (head)); (String "symbol")])) then (let name = (symbol_name (head)) in (let _match_val = name in (if sx_truthy ((prim_call "=" [_match_val; (String "if")])) then (step_sf_if (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "when")])) then (step_sf_when (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "cond")])) then (step_sf_cond (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "case")])) then (step_sf_case (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "and")])) then (step_sf_and (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "or")])) then (step_sf_or (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "let")])) then (step_sf_let (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "let*")])) then (step_sf_let (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "lambda")])) then (step_sf_lambda (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "fn")])) then (step_sf_lambda (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "define")])) then (step_sf_define (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defcomp")])) then (make_cek_value ((sf_defcomp (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defisland")])) then (make_cek_value ((sf_defisland (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defmacro")])) then (make_cek_value ((sf_defmacro (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "begin")])) then (step_sf_begin (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "do")])) then (if sx_truthy ((let _and = (Bool (not (sx_truthy ((empty_p (args)))))) in if not (sx_truthy _and) then _and else (let _and = (list_p ((first (args)))) in if not (sx_truthy _and) then _and else (let _and = (Bool (not (sx_truthy ((empty_p ((first (args)))))))) in if not (sx_truthy _and) then _and else (list_p ((first ((first (args)))))))))) then (let bindings = (first (args)) in let test_clause = (nth (args) ((Number 1.0))) in let body = (rest ((rest (args)))) in let vars = (List (List.map (fun b -> (first (b))) (sx_to_list bindings))) in let inits = (List (List.map (fun b -> (nth (b) ((Number 1.0)))) (sx_to_list bindings))) in let steps = (List (List.map (fun b -> (if sx_truthy ((prim_call ">" [(len (b)); (Number 2.0)])) then (nth (b) ((Number 2.0))) else (first (b)))) (sx_to_list bindings))) in let test = (first (test_clause)) in let result' = (rest (test_clause)) in (step_eval_list ((cons ((Symbol "let")) ((cons ((Symbol "__do-loop")) ((cons ((List (List.map (fun b -> (List [(first (b)); (nth (b) ((Number 1.0)))])) (sx_to_list bindings)))) ((List [(cons ((Symbol "if")) ((cons (test) ((cons ((if sx_truthy ((empty_p (result'))) then Nil else (cons ((Symbol "begin")) (result')))) ((List [(cons ((Symbol "begin")) ((prim_call "append" [body; (List [(cons ((Symbol "__do-loop")) (steps))])])))])))))))])))))))) (env) (kont))) else (step_sf_begin (args) (env) (kont))) else (if sx_truthy ((prim_call "=" [_match_val; (String "guard")])) then (step_sf_guard (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "quote")])) then (make_cek_value ((if sx_truthy ((empty_p (args))) then Nil else (first (args)))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "quasiquote")])) then (make_cek_value ((qq_expand ((first (args))) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "->")])) then (step_sf_thread_first (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "set!")])) then (step_sf_set_b (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "letrec")])) then (step_sf_letrec (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "reset")])) then (step_sf_reset (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "shift")])) then (step_sf_shift (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "deref")])) then (step_sf_deref (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "scope")])) then (step_sf_scope (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "provide")])) then (step_sf_provide (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "context")])) then (step_sf_context (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "emit!")])) then (step_sf_emit (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "emitted")])) then (step_sf_emitted (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "handler-bind")])) then (step_sf_handler_bind (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "restart-case")])) then (step_sf_restart_case (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "signal-condition")])) then (step_sf_signal (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "invoke-restart")])) then (step_sf_invoke_restart (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "match")])) then (step_sf_match (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "dynamic-wind")])) then (make_cek_value ((sf_dynamic_wind (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "map")])) then (step_ho_map (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "map-indexed")])) then (step_ho_map_indexed (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "filter")])) then (step_ho_filter (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "reduce")])) then (step_ho_reduce (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "some")])) then (step_ho_some (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "every?")])) then (step_ho_every (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "for-each")])) then (step_ho_for_each (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "raise")])) then (step_sf_raise (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "raise-continuable")])) then (make_cek_state ((first (args))) (env) ((kont_push ((make_raise_eval_frame (env) ((Bool true)))) (kont)))) else (if sx_truthy ((prim_call "=" [_match_val; (String "call/cc")])) then (step_sf_callcc (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "call-with-current-continuation")])) then (step_sf_callcc (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "perform")])) then (step_sf_perform (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "define-library")])) then (step_sf_define_library (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "import")])) then (step_sf_import (args) (env) (kont)) else (if sx_truthy ((prim_call "has-key?" [custom_special_forms; name])) then (make_cek_value ((cek_call ((get (custom_special_forms) (name))) (List [args; env]))) (env) (kont)) else (if sx_truthy ((let _and = (env_has (env) (name)) in if not (sx_truthy _and) then _and else (is_macro ((env_get (env) (name)))))) then (let mac = (env_get (env) (name)) in (make_cek_state ((expand_macro (mac) (args) (env))) (env) (kont))) else (if sx_truthy ((let _and = render_check in if not (sx_truthy _and) then _and else (cek_call (render_check) (List [expr; env])))) then (make_cek_value ((cek_call (render_fn) (List [expr; env]))) (env) (kont)) else (step_eval_call (head) (args) (env) (kont))))))))))))))))))))))))))))))))))))))))))))))))))))))))) else (step_eval_call (head) (args) (env) (kont))))) + (let head = (first (expr)) in let args = (rest (expr)) in (if sx_truthy ((Bool (not (sx_truthy ((let _or = (prim_call "=" [(type_of (head)); (String "symbol")]) in if sx_truthy _or then _or else (let _or = (prim_call "=" [(type_of (head)); (String "lambda")]) in if sx_truthy _or then _or else (prim_call "=" [(type_of (head)); (String "list")])))))))) then (if sx_truthy ((empty_p (expr))) then (make_cek_value ((List [])) (env) (kont)) else (make_cek_state ((first (expr))) (env) ((kont_push ((make_map_frame (Nil) ((rest (expr))) ((List [])) (env))) (kont))))) else (if sx_truthy ((prim_call "=" [(type_of (head)); (String "symbol")])) then (let name = (symbol_name (head)) in (let _match_val = name in (if sx_truthy ((prim_call "=" [_match_val; (String "if")])) then (step_sf_if (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "when")])) then (step_sf_when (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "cond")])) then (step_sf_cond (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "case")])) then (step_sf_case (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "and")])) then (step_sf_and (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "or")])) then (step_sf_or (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "let")])) then (step_sf_let (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "let*")])) then (step_sf_let (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "lambda")])) then (step_sf_lambda (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "fn")])) then (step_sf_lambda (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "define")])) then (step_sf_define (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defcomp")])) then (make_cek_value ((sf_defcomp (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defisland")])) then (make_cek_value ((sf_defisland (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defmacro")])) then (make_cek_value ((sf_defmacro (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "defio")])) then (make_cek_value ((sf_defio (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "io")])) then (step_sf_io (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "begin")])) then (step_sf_begin (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "do")])) then (if sx_truthy ((let _and = (Bool (not (sx_truthy ((empty_p (args)))))) in if not (sx_truthy _and) then _and else (let _and = (list_p ((first (args)))) in if not (sx_truthy _and) then _and else (let _and = (Bool (not (sx_truthy ((empty_p ((first (args)))))))) in if not (sx_truthy _and) then _and else (list_p ((first ((first (args)))))))))) then (let bindings = (first (args)) in let test_clause = (nth (args) ((Number 1.0))) in let body = (rest ((rest (args)))) in let vars = (List (List.map (fun b -> (first (b))) (sx_to_list bindings))) in let inits = (List (List.map (fun b -> (nth (b) ((Number 1.0)))) (sx_to_list bindings))) in let steps = (List (List.map (fun b -> (if sx_truthy ((prim_call ">" [(len (b)); (Number 2.0)])) then (nth (b) ((Number 2.0))) else (first (b)))) (sx_to_list bindings))) in let test = (first (test_clause)) in let result' = (rest (test_clause)) in (step_eval_list ((cons ((Symbol "let")) ((cons ((Symbol "__do-loop")) ((cons ((List (List.map (fun b -> (List [(first (b)); (nth (b) ((Number 1.0)))])) (sx_to_list bindings)))) ((List [(cons ((Symbol "if")) ((cons (test) ((cons ((if sx_truthy ((empty_p (result'))) then Nil else (cons ((Symbol "begin")) (result')))) ((List [(cons ((Symbol "begin")) ((prim_call "append" [body; (List [(cons ((Symbol "__do-loop")) (steps))])])))])))))))])))))))) (env) (kont))) else (step_sf_begin (args) (env) (kont))) else (if sx_truthy ((prim_call "=" [_match_val; (String "guard")])) then (step_sf_guard (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "quote")])) then (make_cek_value ((if sx_truthy ((empty_p (args))) then Nil else (first (args)))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "quasiquote")])) then (make_cek_value ((qq_expand ((first (args))) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "->")])) then (step_sf_thread_first (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "set!")])) then (step_sf_set_b (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "letrec")])) then (step_sf_letrec (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "reset")])) then (step_sf_reset (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "shift")])) then (step_sf_shift (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "deref")])) then (step_sf_deref (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "scope")])) then (step_sf_scope (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "provide")])) then (step_sf_provide (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "context")])) then (step_sf_context (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "emit!")])) then (step_sf_emit (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "emitted")])) then (step_sf_emitted (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "handler-bind")])) then (step_sf_handler_bind (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "restart-case")])) then (step_sf_restart_case (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "signal-condition")])) then (step_sf_signal (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "invoke-restart")])) then (step_sf_invoke_restart (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "match")])) then (step_sf_match (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "dynamic-wind")])) then (make_cek_value ((sf_dynamic_wind (args) (env))) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "map")])) then (step_ho_map (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "map-indexed")])) then (step_ho_map_indexed (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "filter")])) then (step_ho_filter (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "reduce")])) then (step_ho_reduce (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "some")])) then (step_ho_some (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "every?")])) then (step_ho_every (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "for-each")])) then (step_ho_for_each (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "raise")])) then (step_sf_raise (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "raise-continuable")])) then (make_cek_state ((first (args))) (env) ((kont_push ((make_raise_eval_frame (env) ((Bool true)))) (kont)))) else (if sx_truthy ((prim_call "=" [_match_val; (String "call/cc")])) then (step_sf_callcc (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "call-with-current-continuation")])) then (step_sf_callcc (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "perform")])) then (step_sf_perform (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "define-library")])) then (step_sf_define_library (args) (env) (kont)) else (if sx_truthy ((prim_call "=" [_match_val; (String "import")])) then (step_sf_import (args) (env) (kont)) else (if sx_truthy ((prim_call "has-key?" [custom_special_forms; name])) then (make_cek_value ((cek_call ((get (custom_special_forms) (name))) (List [args; env]))) (env) (kont)) else (if sx_truthy ((let _and = (env_has (env) (name)) in if not (sx_truthy _and) then _and else (is_macro ((env_get (env) (name)))))) then (let mac = (env_get (env) (name)) in (make_cek_state ((expand_macro (mac) (args) (env))) (env) (kont))) else (if sx_truthy ((let _and = render_check in if not (sx_truthy _and) then _and else (cek_call (render_check) (List [expr; env])))) then (make_cek_value ((cek_call (render_fn) (List [expr; env]))) (env) (kont)) else (step_eval_call (head) (args) (env) (kont))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) else (step_eval_call (head) (args) (env) (kont))))) (* step-sf-define-library *) and step_sf_define_library args env kont = diff --git a/hosts/ocaml/lib/sx_runtime.ml b/hosts/ocaml/lib/sx_runtime.ml index c6e01921..df6871b3 100644 --- a/hosts/ocaml/lib/sx_runtime.ml +++ b/hosts/ocaml/lib/sx_runtime.ml @@ -159,6 +159,7 @@ let take a b = _prim "take" [a; b] let drop a b = _prim "drop" [a; b] (* Predicates *) +let keyword_p a = _prim "keyword?" [a] let empty_p a = _prim "empty?" [a] let number_p a = _prim "number?" [a] let string_p a = _prim "string?" [a] diff --git a/spec/evaluator.sx b/spec/evaluator.sx index 4aee9d79..008668e8 100644 --- a/spec/evaluator.sx +++ b/spec/evaluator.sx @@ -391,6 +391,30 @@ (spec exports) (dict-set! *library-registry* (library-name-key spec) {:exports exports}))) +(define *io-registry* (dict)) + +;; Cond/case helpers +(define io-register! (fn (name spec) (dict-set! *io-registry* name spec))) + +(define io-registered? (fn (name) (has-key? *io-registry* name))) + +;; Special form constructors — build state for CEK evaluation +(define io-lookup (fn (name) (get *io-registry* name))) + +(define io-names (fn () (keys *io-registry*))) + +(define + step-sf-io + (fn + (args env kont) + (let + ((name (first args)) (io-args (rest args))) + (when + (not (io-registered? name)) + (error + (str "io: unknown operation '" name "' — not in *io-registry*"))) + (make-cek-state (cons (quote perform) (list {:args io-args :op name})) env kont)))) + (define trampoline (fn @@ -403,16 +427,15 @@ (trampoline (eval-expr (thunk-expr result) (thunk-env result))) result))))) -;; Cond/case helpers (define *strict* false) (define set-strict! (fn (val) (set! *strict* val))) -;; Special form constructors — build state for CEK evaluation (define *prim-param-types* nil) (define set-prim-param-types! (fn (types) (set! *prim-param-types* types))) +;; Quasiquote expansion (define value-matches-type? (fn @@ -577,7 +600,7 @@ (env-bind! local "children" children)) (make-thunk (component-body comp) local)))) -;; Quasiquote expansion +;; Macro expansion — expand then re-evaluate the result (define parse-keyword-args (fn @@ -609,6 +632,14 @@ raw-args) (list kwargs children)))) +;; ═══════════════════════════════════════════════════════════════ +;; Part 6: CEK Machine Core +;; +;; cek-run: trampoline loop — steps until terminal. +;; cek-step: single step — dispatches on phase (eval vs continue). +;; step-eval: evaluates control expression, pushes frames. +;; step-continue: pops a frame, processes result. +;; ═══════════════════════════════════════════════════════════════ (define cond-scheme? (fn @@ -683,6 +714,12 @@ ((init-vals (map (fn (e) (trampoline (eval-expr e env))) inits))) (cek-call loop-fn init-vals)))))) +;; ═══════════════════════════════════════════════════════════════ +;; Part 7: Special Form Step Functions +;; +;; Each step-sf-* handles one special form in the eval phase. +;; They push frames and return new CEK states — never recurse. +;; ═══════════════════════════════════════════════════════════════ (define sf-lambda (fn @@ -712,6 +749,7 @@ params-expr))) (make-lambda param-names body env)))) +;; R7RS guard: desugars to call/cc + handler-bind with sentinel re-raise (define sf-defcomp (fn @@ -749,7 +787,9 @@ (env-bind! env (symbol-name name-sym) comp) comp)))) -;; Macro expansion — expand then re-evaluate the result +;; List evaluation — dispatches on head: special forms, macros, +;; higher-order forms, or function calls. This is the main +;; expression dispatcher for the CEK machine. (define defcomp-kwarg (fn @@ -772,14 +812,7 @@ (range 2 end 1)) result))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 6: CEK Machine Core -;; -;; cek-run: trampoline loop — steps until terminal. -;; cek-step: single step — dispatches on phase (eval vs continue). -;; step-eval: evaluates control expression, pushes frames. -;; step-continue: pops a frame, processes result. -;; ═══════════════════════════════════════════════════════════════ +;; call/cc: capture entire kont as undelimited escape continuation (define parse-comp-params (fn @@ -851,6 +884,31 @@ (env-bind! env (symbol-name name-sym) island) island)))) +(define + defio-parse-kwargs! + (fn + (spec remaining) + (when + (and + (not (empty? remaining)) + (>= (len remaining) 2) + (keyword? (first remaining))) + (dict-set! spec (keyword-name (first remaining)) (nth remaining 1)) + (defio-parse-kwargs! spec (rest (rest remaining)))))) + +;; Pattern matching (match form) +(define + sf-defio + (fn + (args env) + (let + ((name (first args)) (spec (dict))) + (dict-set! spec "name" name) + (defio-parse-kwargs! spec (rest args)) + (io-register! name spec) + spec))) + +;; Condition system special forms (define sf-defmacro (fn @@ -867,12 +925,6 @@ (env-bind! env (symbol-name name-sym) mac) mac)))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 7: Special Form Step Functions -;; -;; Each step-sf-* handles one special form in the eval phase. -;; They push frames and return new CEK states — never recurse. -;; ═══════════════════════════════════════════════════════════════ (define parse-macro-params (fn @@ -901,7 +953,6 @@ params-expr) (list params rest-param)))) -;; R7RS guard: desugars to call/cc + handler-bind with sentinel re-raise (define qq-expand (fn @@ -941,9 +992,6 @@ (list) template))))))) -;; List evaluation — dispatches on head: special forms, macros, -;; higher-order forms, or function calls. This is the main -;; expression dispatcher for the CEK machine. (define sf-letrec (fn @@ -999,7 +1047,6 @@ (slice body 0 (dec (len body)))) (make-thunk (last body) local)))) -;; call/cc: capture entire kont as undelimited escape continuation (define step-sf-letrec (fn @@ -1045,7 +1092,6 @@ (scope-pop! name) result)))) -;; Pattern matching (match form) (define sf-provide (fn @@ -1062,7 +1108,6 @@ (scope-pop! name) result))) -;; Condition system special forms (define expand-macro (fn @@ -1265,6 +1310,7 @@ env kont)))) +;; Scope/provide/context — structured downward data passing (define step-eval-list (fn @@ -1304,6 +1350,8 @@ ("defcomp" (make-cek-value (sf-defcomp args env) env kont)) ("defisland" (make-cek-value (sf-defisland args env) env kont)) ("defmacro" (make-cek-value (sf-defmacro args env) env kont)) + ("defio" (make-cek-value (sf-defio args env) env kont)) + ("io" (step-sf-io args env kont)) ("begin" (step-sf-begin args env kont)) ("do" (if @@ -1527,6 +1575,7 @@ env (kont-push (make-perform-frame env) kont))))) +;; Delimited continuations (define step-sf-callcc (fn @@ -1553,6 +1602,7 @@ (list local body) (match-find-clause val (rest clauses) env)))))) +;; Signal dereferencing with reactive dependency tracking (define match-pattern (fn @@ -1585,7 +1635,13 @@ pairs))) :else (= pattern value)))) -;; Scope/provide/context — structured downward data passing +;; ═══════════════════════════════════════════════════════════════ +;; Part 8: Call Dispatch +;; +;; cek-call: invoke a function from native code (runs a nested +;; trampoline). step-eval-call: CEK-native call dispatch for +;; lambda, component, native fn, and continuations. +;; ═══════════════════════════════════════════════════════════════ (define step-sf-match (fn @@ -1600,6 +1656,7 @@ (error (str "match: no clause matched " (inspect val))) (make-cek-state (nth result 1) (first result) kont)))))) +;; Reactive signal tracking — captures dependency continuation for re-render (define step-sf-handler-bind (fn @@ -1647,6 +1704,13 @@ env (kont-push (make-restart-frame restarts (list) env) kont))))) +;; ═══════════════════════════════════════════════════════════════ +;; Part 9: Higher-Order Form Machinery +;; +;; Data-first HO forms: (map coll fn) and (map fn coll) both work. +;; ho-swap-args auto-detects argument order. HoSetupFrame stages +;; argument evaluation, then dispatches to the appropriate step-ho-*. +;; ═══════════════════════════════════════════════════════════════ (define step-sf-signal (fn @@ -1692,7 +1756,6 @@ (env-bind! restart-env (first params) restart-arg)) (make-cek-state body restart-env rest-kont))))))) -;; Delimited continuations (define step-sf-if (fn @@ -1716,7 +1779,6 @@ env (kont-push (make-when-frame (rest args) env) kont)))) -;; Signal dereferencing with reactive dependency tracking (define step-sf-begin (fn @@ -1732,13 +1794,6 @@ env (kont-push (make-begin-frame (rest args) env) kont)))))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 8: Call Dispatch -;; -;; cek-call: invoke a function from native code (runs a nested -;; trampoline). step-eval-call: CEK-native call dispatch for -;; lambda, component, native fn, and continuations. -;; ═══════════════════════════════════════════════════════════════ (define step-sf-let (fn @@ -1783,7 +1838,6 @@ (make-let-frame vname rest-bindings body local) kont))))))))) -;; Reactive signal tracking — captures dependency continuation for re-render (define step-sf-define (fn @@ -1831,13 +1885,6 @@ env (kont-push (make-set-frame (symbol-name (first args)) env) kont)))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 9: Higher-Order Form Machinery -;; -;; Data-first HO forms: (map coll fn) and (map fn coll) both work. -;; ho-swap-args auto-detects argument order. HoSetupFrame stages -;; argument evaluation, then dispatches to the appropriate step-ho-*. -;; ═══════════════════════════════════════════════════════════════ (define step-sf-and (fn @@ -1895,6 +1942,14 @@ env (kont-push (make-cond-frame args env false) kont))))))))) +;; ═══════════════════════════════════════════════════════════════ +;; Part 10: Continue Phase — Frame Dispatch +;; +;; When phase="continue", pop the top frame and process the value. +;; Each frame type has its own handling: if frames check truthiness, +;; let frames bind the value, arg frames accumulate it, etc. +;; continue-with-call handles the final function/component dispatch. +;; ═══════════════════════════════════════════════════════════════ (define step-sf-case (fn @@ -1904,6 +1959,9 @@ env (kont-push (make-case-frame nil (rest args) env) kont)))) +;; Final call dispatch from arg frame — all args evaluated, invoke function. +;; Handles: lambda (bind params + TCO), component (keyword args + TCO), +;; native fn (direct call), continuation (resume), callcc continuation (escape). (define step-sf-thread-first (fn @@ -1917,6 +1975,13 @@ step-sf-lambda (fn (args env kont) (make-cek-value (sf-lambda args env) env kont))) +;; ═══════════════════════════════════════════════════════════════ +;; Part 11: Entry Points +;; +;; eval-expr-cek / trampoline-cek: CEK evaluation entry points. +;; eval-expr / trampoline: top-level bindings that override the +;; forward declarations from Part 5. +;; ═══════════════════════════════════════════════════════════════ (define step-sf-scope (fn @@ -2022,14 +2087,6 @@ env kont)))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 10: Continue Phase — Frame Dispatch -;; -;; When phase="continue", pop the top frame and process the value. -;; Each frame type has its own handling: if frames check truthiness, -;; let frames bind the value, arg frames accumulate it, etc. -;; continue-with-call handles the final function/component dispatch. -;; ═══════════════════════════════════════════════════════════════ (define step-sf-reset (fn @@ -2039,9 +2096,6 @@ env (kont-push (make-reset-frame env) kont)))) -;; Final call dispatch from arg frame — all args evaluated, invoke function. -;; Handles: lambda (bind params + TCO), component (keyword args + TCO), -;; native fn (direct call), continuation (resume), callcc continuation (escape). (define step-sf-shift (fn @@ -2068,13 +2122,6 @@ env (kont-push (make-deref-frame env) kont)))) -;; ═══════════════════════════════════════════════════════════════ -;; Part 11: Entry Points -;; -;; eval-expr-cek / trampoline-cek: CEK evaluation entry points. -;; eval-expr / trampoline: top-level bindings that override the -;; forward declarations from Part 5. -;; ═══════════════════════════════════════════════════════════════ (define cek-call (fn diff --git a/spec/tests/test-io-registry.sx b/spec/tests/test-io-registry.sx new file mode 100644 index 00000000..3c00438a --- /dev/null +++ b/spec/tests/test-io-registry.sx @@ -0,0 +1,101 @@ +;; IO registry tests — defio, *io-registry*, accessor functions, io contract +(defsuite + "io-registry-basic" + (deftest + "defio registers an IO operation" + (defio + "test-io-basic" + :category :data + :params () + :returns "string" + :doc "Basic test op.") + (assert (io-registered? "test-io-basic"))) + (deftest + "io-lookup returns spec dict" + (defio + "test-io-lookup" + :category :effect + :params (x) + :returns "nil" + :doc "Test effect.") + (let + ((spec (io-lookup "test-io-lookup"))) + (assert= (get spec "name") "test-io-lookup") + (assert= (keyword-name (get spec "category")) "effect") + (assert= (get spec "returns") "nil") + (assert= (get spec "doc") "Test effect."))) + (deftest + "io-registered? returns false for unknown" + (assert (not (io-registered? "nonexistent-io-op")))) + (deftest + "io-names includes registered ops" + (defio + "test-io-names" + :category :data + :params () + :returns "any" + :doc "Names test.") + (assert (contains? (io-names) "test-io-names"))) + (deftest + "defio returns the spec dict" + (let + ((result (defio "test-io-ret" :category :code :params (a b) :returns "string" :doc "Return test."))) + (assert= (get result "name") "test-io-ret") + (assert= (keyword-name (get result "category")) "code")))) + +(defsuite + "io-registry-kwargs" + (deftest + "defio parses batchable flag" + (defio + "test-io-batch" + :category :code + :params (code lang) + :returns "string" + :batchable true + :doc "Batchable op.") + (assert= (get (io-lookup "test-io-batch") "batchable") true)) + (deftest + "defio parses cacheable flag" + (defio + "test-io-cache" + :category :data + :params () + :returns "list" + :cacheable true + :doc "Cacheable op.") + (assert= (get (io-lookup "test-io-cache") "cacheable") true)) + (deftest + "defio parses params list" + (defio + "test-io-params" + :category :data + :params (a b c) + :returns "list" + :doc "Multi param.") + (assert= (len (get (io-lookup "test-io-params") "params")) 3))) + +(defsuite + "io-contract" + (deftest + "io rejects unregistered operations" + (let + ((caught false)) + (try-catch + (fn () (io "totally-unknown-op-xyz")) + (fn (err) (set! caught true))) + (assert caught))) + (deftest + "io suspends for registered operations" + (defio + "test-io-contract" + :category :data + :params () + :returns "string" + :doc "Contract test.") + (let + ((caught-msg "")) + (try-catch + (fn () (io "test-io-contract")) + (fn (err) (set! caught-msg err))) + (assert (not (string-contains? caught-msg "unknown operation"))))))