SX renderer: adapter-html.sx as sole renderer, conditions, pattern matching
Evaluator: conditions/restarts, pattern matching, render-trace support. adapter-html.sx: full SX-defined HTML renderer replacing native OCaml. spec/render.sx: updated render mode helpers. sx_browser.ml: use SX render-to-html instead of native. sx_ref.ml: evaluator updates for conditions + match. Bootstrap + transpiler updates for new forms. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -407,7 +407,7 @@ let () =
|
||||
let render_html src =
|
||||
let exprs = Sx_parser.parse_all src in
|
||||
let expr = match exprs with [e] -> e | _ -> Nil in
|
||||
Sx_render.render_to_html expr env
|
||||
Sx_render.sx_render_to_html env expr env
|
||||
in
|
||||
|
||||
(* Helper: call SX render-to-html via the adapter *)
|
||||
|
||||
@@ -259,7 +259,14 @@ let setup_env () =
|
||||
(* Load eval-rules *)
|
||||
(try load_sx_file e (Filename.concat spec_dir "eval-rules.sx")
|
||||
with exn -> Printf.eprintf "[mcp] Warning: eval-rules.sx load failed: %s\n%!" (Printexc.to_string exn));
|
||||
Printf.eprintf "[mcp] SX tree-tools + harness + eval-rules loaded\n%!";
|
||||
(* Load render pipeline — native OCaml renderer + HTML tag bindings *)
|
||||
Sx_render.setup_render_env e;
|
||||
List.iter (fun tag ->
|
||||
ignore (Sx_types.env_bind e tag
|
||||
(NativeFn ("html:" ^ tag, fun args -> List (Symbol tag :: args))))
|
||||
) Sx_render.html_tags;
|
||||
ignore (Sx_types.env_bind e "island?" (NativeFn ("island?", fun args -> match args with [Island _] -> Bool true | _ -> Bool false)));
|
||||
Printf.eprintf "[mcp] SX tree-tools + harness + eval-rules + render loaded\n%!";
|
||||
env := e
|
||||
|
||||
(* ------------------------------------------------------------------ *)
|
||||
@@ -277,7 +284,9 @@ let parse_file path =
|
||||
List exprs
|
||||
|
||||
let parse_path_str s =
|
||||
(* Parse SX path string: "(0 3 2)" or "0 3 2" → SX list of numbers *)
|
||||
(* Parse SX path string: "(0 3 2)" or "(0,3,2)" or "0 3 2" → SX list of numbers.
|
||||
Commas are unquote in SX, so strip them before parsing. *)
|
||||
let s = String.map (fun c -> if c = ',' then ' ' else c) s in
|
||||
let exprs = Sx_parser.parse_all s in
|
||||
match exprs with
|
||||
| [List items] ->
|
||||
@@ -1375,10 +1384,65 @@ let rec handle_tool name args =
|
||||
) Nil exprs in
|
||||
text_result (Sx_runtime.value_to_str result)
|
||||
|
||||
| "sx_guard" ->
|
||||
let expr_str = args |> member "expr" |> to_string in
|
||||
let file = try Some (args |> member "file" |> to_string) with _ -> None in
|
||||
let e = !env in
|
||||
(match file with
|
||||
| Some f -> (try load_sx_file e f with _ -> ())
|
||||
| None -> ());
|
||||
let exprs = Sx_parser.parse_all expr_str in
|
||||
let conditions = ref [] in
|
||||
(* Evaluate with error recovery — catch Eval_error, log it, return placeholder *)
|
||||
let result = ref Nil in
|
||||
(try
|
||||
result := List.fold_left (fun _acc expr ->
|
||||
Sx_ref.eval_expr expr (Env e)
|
||||
) Nil exprs
|
||||
with Eval_error msg ->
|
||||
let enhanced = Sx_ref.enhance_error_with_trace msg in
|
||||
conditions := enhanced :: !conditions;
|
||||
result := String ("<error: " ^ msg ^ ">"));
|
||||
let cond_lines = match !conditions with
|
||||
| [] -> ""
|
||||
| cs -> "\n\nConditions signaled:\n" ^
|
||||
String.concat "\n" (List.rev_map (fun c -> " - " ^ c) cs) in
|
||||
text_result (Sx_runtime.value_to_str !result ^ cond_lines)
|
||||
|
||||
| "sx_render_trace" ->
|
||||
let expr_str = args |> member "expr" |> to_string in
|
||||
let file = try Some (args |> member "file" |> to_string) with _ -> None in
|
||||
let e = !env in
|
||||
(match file with
|
||||
| Some f -> (try load_sx_file e f with _ -> ())
|
||||
| None -> ());
|
||||
let exprs = Sx_parser.parse_all expr_str in
|
||||
let expr = match exprs with [e] -> e | _ -> List (Symbol "do" :: exprs) in
|
||||
let trace = Buffer.create 2048 in
|
||||
let truncate s n = if String.length s > n then String.sub s 0 n ^ "..." else s in
|
||||
let expr_str = truncate (Sx_runtime.value_to_str expr) 60 in
|
||||
let kind = match expr with
|
||||
| Nil -> "nil" | Bool _ -> "bool" | Number _ -> "number"
|
||||
| String _ -> "string" | Symbol _ -> "symbol" | Keyword _ -> "keyword"
|
||||
| RawHTML _ -> "raw-html"
|
||||
| List (Symbol s :: _) | ListRef { contents = Symbol s :: _ } ->
|
||||
if List.mem s Sx_render.html_tags then "element:" ^ s
|
||||
else if List.mem s ["if";"when";"cond";"case";"let";"let*";"do";"begin";"map";"filter";"define";"defcomp"] then "form:" ^ s
|
||||
else "call:" ^ s
|
||||
| List _ -> "list" | _ -> "other" in
|
||||
Buffer.add_string trace (Printf.sprintf "→ %s %s\n" kind expr_str);
|
||||
let result = (try
|
||||
Sx_render.sx_render_to_html e expr e
|
||||
with Sx_types.Eval_error msg -> "ERROR: " ^ Sx_ref.enhance_error_with_trace msg) in
|
||||
let result_str = truncate result 60 in
|
||||
Buffer.add_string trace (Printf.sprintf "← %s\n" result_str);
|
||||
text_result (Printf.sprintf "Result: %s\n\nRender trace:\n%s" result (Buffer.contents trace))
|
||||
|
||||
| "sx_trace" ->
|
||||
let expr_str = args |> member "expr" |> to_string in
|
||||
let max_steps = (try args |> member "max_steps" |> to_int with _ -> 200) in
|
||||
let file = try Some (args |> member "file" |> to_string) with _ -> None in
|
||||
let components_only = (try args |> member "components_only" |> to_bool with _ -> false) in
|
||||
let e = !env in
|
||||
(match file with
|
||||
| Some f -> (try load_sx_file e f with _ -> ())
|
||||
@@ -1389,6 +1453,29 @@ let rec handle_tool name args =
|
||||
let steps = Buffer.create 2048 in
|
||||
let step_count = ref 0 in
|
||||
let truncate s n = if String.length s > n then String.sub s 0 n ^ "..." else s in
|
||||
(* Track comp-trace depth for component-only mode *)
|
||||
let comp_depth = ref 0 in
|
||||
let prev_comp_depth = ref 0 in
|
||||
let get_frame_type kont = match kont with
|
||||
| List (CekFrame f :: _) -> f.cf_type
|
||||
| List (Dict d :: _) ->
|
||||
(match Hashtbl.find_opt d "type" with Some (String s) -> s | _ -> "?")
|
||||
| _ -> "done" in
|
||||
let count_comp_trace kont =
|
||||
let n = ref 0 in
|
||||
let k = ref kont in
|
||||
(try while true do
|
||||
(match !k with
|
||||
| List (CekFrame f :: rest) ->
|
||||
if f.cf_type = "comp-trace" then incr n;
|
||||
k := List rest
|
||||
| List (Dict d :: rest) ->
|
||||
(match Hashtbl.find_opt d "type" with
|
||||
| Some (String "comp-trace") -> incr n | _ -> ());
|
||||
k := List rest
|
||||
| _ -> raise Exit)
|
||||
done with Exit -> ());
|
||||
!n in
|
||||
(try
|
||||
while !step_count < max_steps do
|
||||
let s = !state in
|
||||
@@ -1396,44 +1483,56 @@ let rec handle_tool name args =
|
||||
| CekState cs ->
|
||||
incr step_count;
|
||||
let n = !step_count in
|
||||
if cs.cs_phase = "eval" then begin
|
||||
let ctrl = cs.cs_control in
|
||||
(match ctrl with
|
||||
| Symbol sym_name ->
|
||||
let resolved = (try
|
||||
let v = Sx_ref.eval_expr ctrl cs.cs_env in
|
||||
truncate (Sx_runtime.value_to_str v) 60
|
||||
with _ -> "???") in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d LOOKUP %s → %s\n" n sym_name resolved)
|
||||
| List (hd :: _) ->
|
||||
let head_str = truncate (Sx_runtime.value_to_str hd) 30 in
|
||||
let ctrl_str = truncate (Sx_runtime.value_to_str ctrl) 80 in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d CALL %s\n" n ctrl_str);
|
||||
ignore head_str
|
||||
| _ ->
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d LITERAL %s\n" n
|
||||
(truncate (Sx_runtime.value_to_str ctrl) 60)))
|
||||
if components_only then begin
|
||||
let depth = count_comp_trace cs.cs_kont in
|
||||
(if depth > !prev_comp_depth then begin
|
||||
let indent = String.make (depth * 2) ' ' in
|
||||
let ft = get_frame_type cs.cs_kont in
|
||||
let name = (match cs.cs_kont with
|
||||
| List (CekFrame f :: _) when f.cf_type = "comp-trace" ->
|
||||
(match f.cf_name with String s -> s | _ -> "?")
|
||||
| _ -> "?") in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%s→ ENTER ~%s\n" indent name);
|
||||
ignore ft
|
||||
end else if depth < !prev_comp_depth then begin
|
||||
let indent = String.make ((depth + 1) * 2) ' ' in
|
||||
let val_str = if cs.cs_phase = "continue"
|
||||
then truncate (Sx_runtime.value_to_str cs.cs_value) 60
|
||||
else "..." in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%s← EXIT → %s\n" indent val_str)
|
||||
end);
|
||||
prev_comp_depth := depth
|
||||
end else begin
|
||||
(* continue phase *)
|
||||
let val_str = truncate (Sx_runtime.value_to_str cs.cs_value) 60 in
|
||||
let kont = cs.cs_kont in
|
||||
let frame_type = match kont with
|
||||
| List (Dict d :: _) ->
|
||||
(match Hashtbl.find_opt d "type" with
|
||||
| Some (String s) -> s | _ -> "?")
|
||||
| List (CekState ks :: _) ->
|
||||
(match ks.cs_control with
|
||||
| Dict d ->
|
||||
(match Hashtbl.find_opt d "type" with
|
||||
| Some (String s) -> s | _ -> "?")
|
||||
| _ -> "?")
|
||||
| _ -> "done" in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d RETURN %s → %s\n" n val_str frame_type)
|
||||
if cs.cs_phase = "eval" then begin
|
||||
let ctrl = cs.cs_control in
|
||||
(match ctrl with
|
||||
| Symbol sym_name ->
|
||||
let resolved = (try
|
||||
let v = Sx_ref.eval_expr ctrl cs.cs_env in
|
||||
truncate (Sx_runtime.value_to_str v) 60
|
||||
with _ -> "???") in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d LOOKUP %s → %s\n" n sym_name resolved)
|
||||
| List (hd :: _) ->
|
||||
let head_str = truncate (Sx_runtime.value_to_str hd) 30 in
|
||||
let ctrl_str = truncate (Sx_runtime.value_to_str ctrl) 80 in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d CALL %s\n" n ctrl_str);
|
||||
ignore head_str
|
||||
| _ ->
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d LITERAL %s\n" n
|
||||
(truncate (Sx_runtime.value_to_str ctrl) 60)))
|
||||
end else begin
|
||||
let val_str = truncate (Sx_runtime.value_to_str cs.cs_value) 60 in
|
||||
let ft = get_frame_type cs.cs_kont in
|
||||
Buffer.add_string steps
|
||||
(Printf.sprintf "%3d RETURN %s → %s\n" n val_str ft)
|
||||
end
|
||||
end;
|
||||
ignore comp_depth;
|
||||
(match Sx_ref.cek_terminal_p s with
|
||||
| Bool true -> raise Exit
|
||||
| _ -> ());
|
||||
@@ -1443,7 +1542,8 @@ let rec handle_tool name args =
|
||||
with
|
||||
| Exit -> ()
|
||||
| Eval_error msg ->
|
||||
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" msg)
|
||||
let enhanced = Sx_ref.enhance_error_with_trace msg in
|
||||
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" enhanced)
|
||||
| exn ->
|
||||
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" (Printexc.to_string exn)));
|
||||
let final_val = (match !state with
|
||||
@@ -1714,10 +1814,17 @@ let tool_definitions = `List [
|
||||
[file_prop; path_prop; ("wrapper", `Assoc [("type", `String "string"); ("description", `String "Wrapper with _ placeholder")])] ["file"; "path"; "wrapper"];
|
||||
tool "sx_eval" "Evaluate an SX expression. Environment has parser + tree-tools + primitives."
|
||||
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate")])] ["expr"];
|
||||
tool "sx_trace" "Step-through SX evaluation showing each CEK machine step (symbol lookups, function calls, returns). Useful for debugging."
|
||||
tool "sx_guard" "Evaluate with error recovery. Catches errors, shows component trace, and continues. Returns result + any conditions signaled."
|
||||
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate with error recovery")]);
|
||||
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")])] ["expr"];
|
||||
tool "sx_render_trace" "Render an SX expression to HTML with full dispatch tracing. Shows which render path each sub-expression takes."
|
||||
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to render with tracing")]);
|
||||
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")])] ["expr"];
|
||||
tool "sx_trace" "Step-through SX evaluation showing each CEK machine step (symbol lookups, function calls, returns). Set components_only=true for component entry/exit only."
|
||||
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to trace")]);
|
||||
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")]);
|
||||
("max_steps", `Assoc [("type", `String "integer"); ("description", `String "Max CEK steps to show (default: 200)")])] ["expr"];
|
||||
("max_steps", `Assoc [("type", `String "integer"); ("description", `String "Max CEK steps to show (default: 200)")]);
|
||||
("components_only", `Assoc [("type", `String "boolean"); ("description", `String "Show only component entry/exit events (default: false)")])] ["expr"];
|
||||
tool "sx_explain" "Explain SX evaluation rules. Pass a form name (if, let, map, ...) or category (literal, special-form, higher-order, ...)."
|
||||
[("name", `Assoc [("type", `String "string"); ("description", `String "Form name or category to explain")])] ["name"];
|
||||
tool "sx_deps" "Dependency analysis for a component or file. Shows all referenced symbols and where they're defined."
|
||||
|
||||
@@ -292,10 +292,15 @@ let make_test_env () =
|
||||
bind "eval-expr" (fun args ->
|
||||
match args with
|
||||
| [expr; e] ->
|
||||
(match e with
|
||||
| Dict _ -> Printf.eprintf "[EVAL-EXPR] env is Dict! expr=%s\n%!" (Sx_runtime.value_to_str expr)
|
||||
| Nil -> Printf.eprintf "[EVAL-EXPR] env is Nil! expr=%s\n%!" (Sx_runtime.value_to_str expr)
|
||||
| _ -> ());
|
||||
let ue = Sx_runtime.unwrap_env e in
|
||||
eval_expr expr (Env ue)
|
||||
| [expr] -> eval_expr expr (Env env)
|
||||
| _ -> raise (Eval_error "eval-expr: expected (expr env)"));
|
||||
bind "set-render-active!" (fun _args -> Nil);
|
||||
(* Scope primitives — use a local scope stacks table.
|
||||
Must match the same pattern as sx_server.ml's _scope_stacks. *)
|
||||
let _scope_stacks : (string, Sx_types.value list) Hashtbl.t = Hashtbl.create 8 in
|
||||
@@ -354,20 +359,62 @@ let make_test_env () =
|
||||
bind "cond-scheme?" (fun args ->
|
||||
match args with
|
||||
| [(List clauses | ListRef { contents = clauses })] ->
|
||||
(match clauses with
|
||||
| (List _ | ListRef _) :: _ -> Bool true
|
||||
| _ -> Bool false)
|
||||
Bool (List.for_all (fun c ->
|
||||
match c with
|
||||
| List l | ListRef { contents = l } -> List.length l = 2
|
||||
| _ -> false
|
||||
) clauses)
|
||||
| _ -> Bool false);
|
||||
bind "expand-macro" (fun args ->
|
||||
match args with
|
||||
| [Macro m; (List a | ListRef { contents = a }); _] ->
|
||||
let local = Sx_types.env_extend m.m_closure in
|
||||
List.iteri (fun i p ->
|
||||
ignore (Sx_types.env_bind local p (if i < List.length a then List.nth a i else Nil))
|
||||
) m.m_params;
|
||||
let rec bind_params ps as' =
|
||||
match ps, as' with
|
||||
| [], rest ->
|
||||
(match m.m_rest_param with
|
||||
| Some rp -> ignore (Sx_types.env_bind local rp (List rest))
|
||||
| None -> ())
|
||||
| p :: ps_rest, a :: as_rest ->
|
||||
ignore (Sx_types.env_bind local p a);
|
||||
bind_params ps_rest as_rest
|
||||
| remaining, [] ->
|
||||
List.iter (fun p -> ignore (Sx_types.env_bind local p Nil)) remaining
|
||||
in
|
||||
bind_params m.m_params a;
|
||||
eval_expr m.m_body (Env local)
|
||||
| _ -> raise (Eval_error "expand-macro: expected (macro args env)"));
|
||||
|
||||
(* Declarative type/effect forms — no-ops at runtime *)
|
||||
bind "deftype" (fun _args -> Nil);
|
||||
bind "defeffect" (fun _args -> Nil);
|
||||
|
||||
(* --- Primitives for canonical.sx / content tests --- *)
|
||||
bind "contains-char?" (fun args ->
|
||||
match args with
|
||||
| [String s; String c] when String.length c = 1 ->
|
||||
Bool (String.contains s c.[0])
|
||||
| _ -> Bool false);
|
||||
bind "escape-string" (fun args ->
|
||||
match args with
|
||||
| [String s] ->
|
||||
let buf = Buffer.create (String.length s + 4) in
|
||||
String.iter (fun c -> match c with
|
||||
| '"' -> Buffer.add_string buf "\\\""
|
||||
| '\\' -> Buffer.add_string buf "\\\\"
|
||||
| '\n' -> Buffer.add_string buf "\\n"
|
||||
| '\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 "sha3-256" (fun args ->
|
||||
match args with
|
||||
| [String s] ->
|
||||
(* Stub: use a simple hash for testing — not real SHA3 *)
|
||||
let h = Hashtbl.hash s in
|
||||
String (Printf.sprintf "%064x" (abs h))
|
||||
| _ -> raise (Eval_error "sha3-256: expected string"));
|
||||
|
||||
(* --- Missing primitives referenced by tests --- *)
|
||||
|
||||
bind "upcase" (fun args ->
|
||||
@@ -467,6 +514,8 @@ let make_test_env () =
|
||||
bind "component-param-types" (fun _args -> Nil);
|
||||
|
||||
bind "component-set-param-types!" (fun _args -> Nil);
|
||||
bind "component-file" (fun args -> match args with [v] -> component_file v | _ -> Nil);
|
||||
bind "component-set-file!" (fun args -> match args with [v; f] -> component_set_file v f | _ -> Nil);
|
||||
|
||||
bind "component-params" (fun args ->
|
||||
match args with
|
||||
|
||||
@@ -226,7 +226,7 @@ let sx_render_to_html expr env =
|
||||
let result = Sx_ref.cek_call fn (List [expr; Env env]) in
|
||||
match result with String s -> s | _ -> Sx_runtime.value_to_str result
|
||||
else
|
||||
Sx_render.render_to_html expr env
|
||||
Sx_render.sx_render_to_html env expr env
|
||||
|
||||
|
||||
(* ====================================================================== *)
|
||||
|
||||
@@ -60,6 +60,9 @@ let _prim_param_types_ref = ref Nil
|
||||
run with hook = None (pure CEK, no compilation dependency). *)
|
||||
let jit_call_hook : (value -> value list -> value option) option ref = ref None
|
||||
|
||||
(* Component trace — captures kont from last CEK error for diagnostics *)
|
||||
let _last_error_kont : value ref = ref Nil
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -75,13 +78,58 @@ let () = trampoline_fn := (fun v ->
|
||||
(* Wire up the primitives trampoline so call_any in HO forms resolves Thunks *)
|
||||
let () = Sx_primitives._sx_trampoline_fn := !trampoline_fn
|
||||
|
||||
(* Override recursive cek_run with iterative loop *)
|
||||
(* Override recursive cek_run with iterative loop.
|
||||
On error, capture the kont from the last state for comp-trace. *)
|
||||
let cek_run_iterative state =
|
||||
let s = ref state in
|
||||
while not (match cek_terminal_p !s with Bool true -> true | _ -> false) do
|
||||
s := cek_step !s
|
||||
(try
|
||||
while not (match cek_terminal_p !s with Bool true -> true | _ -> false) do
|
||||
s := cek_step !s
|
||||
done;
|
||||
cek_value !s
|
||||
with Eval_error msg ->
|
||||
_last_error_kont := cek_kont !s;
|
||||
raise (Eval_error msg))
|
||||
|
||||
(* Collect component trace from a kont value *)
|
||||
let collect_comp_trace kont =
|
||||
let trace = ref [] in
|
||||
let k = ref kont in
|
||||
while (match !k with List (_::_) -> true | _ -> false) do
|
||||
(match !k with
|
||||
| List (frame :: rest) ->
|
||||
(match frame with
|
||||
| CekFrame f when f.cf_type = "comp-trace" ->
|
||||
let name = match f.cf_name with String s -> s | _ -> "?" in
|
||||
let file = match f.cf_env with String s -> s | Nil -> "" | _ -> "" in
|
||||
trace := (name, file) :: !trace
|
||||
| Dict d when (match Hashtbl.find_opt d "type" with Some (String "comp-trace") -> true | _ -> false) ->
|
||||
let name = match Hashtbl.find_opt d "name" with Some (String s) -> s | _ -> "?" in
|
||||
let file = match Hashtbl.find_opt d "file" with Some (String s) -> s | _ -> "" in
|
||||
trace := (name, file) :: !trace
|
||||
| _ -> ());
|
||||
k := List rest
|
||||
| _ -> k := List [])
|
||||
done;
|
||||
cek_value !s
|
||||
List.rev !trace
|
||||
|
||||
(* Format a comp-trace into a human-readable string *)
|
||||
let format_comp_trace trace =
|
||||
match trace with
|
||||
| [] -> ""
|
||||
| entries ->
|
||||
let lines = List.mapi (fun i (name, file) ->
|
||||
let prefix = if i = 0 then " in " else " called from " in
|
||||
if file = "" then prefix ^ "~" ^ name
|
||||
else prefix ^ "~" ^ name ^ " (" ^ file ^ ")"
|
||||
) entries in
|
||||
"\n" ^ String.concat "\n" lines
|
||||
|
||||
(* Enhance an error message with component trace *)
|
||||
let enhance_error_with_trace msg =
|
||||
let trace = collect_comp_trace !_last_error_kont in
|
||||
_last_error_kont := Nil;
|
||||
msg ^ (format_comp_trace trace)
|
||||
|
||||
|
||||
|
||||
@@ -248,6 +296,24 @@ def compile_spec_to_ml(spec_dir: str | None = None) -> str:
|
||||
import sys
|
||||
print("WARNING: Could not find lambda body pattern for JIT injection", file=sys.stderr)
|
||||
|
||||
# Instrument recursive cek_run to capture kont on error (for comp-trace).
|
||||
# The iterative cek_run_iterative already does this, but cek_call uses
|
||||
# the recursive cek_run.
|
||||
cek_run_old = (
|
||||
'and cek_run state =\n'
|
||||
' (if sx_truthy ((cek_terminal_p (state))) then (cek_value (state)) else (cek_run ((cek_step (state)))))'
|
||||
)
|
||||
cek_run_new = (
|
||||
'and cek_run state =\n'
|
||||
' (if sx_truthy ((cek_terminal_p (state))) then (cek_value (state)) else\n'
|
||||
' try cek_run ((cek_step (state)))\n'
|
||||
' with Eval_error msg ->\n'
|
||||
' (if !_last_error_kont = Nil then _last_error_kont := cek_kont state);\n'
|
||||
' raise (Eval_error msg))'
|
||||
)
|
||||
if cek_run_old in output:
|
||||
output = output.replace(cek_run_old, cek_run_new, 1)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ let api_render_to_html expr_js =
|
||||
let prev = !_sx_render_mode in
|
||||
_sx_render_mode := true;
|
||||
(try
|
||||
let html = Sx_render.render_to_html expr global_env in
|
||||
let html = Sx_render.sx_render_to_html global_env expr global_env in
|
||||
_sx_render_mode := prev;
|
||||
Js.Unsafe.inject (Js.string html)
|
||||
with Eval_error msg ->
|
||||
@@ -641,7 +641,7 @@ let () =
|
||||
bind "<>" (fun args ->
|
||||
RawHTML (String.concat "" (List.map (fun a ->
|
||||
match a with String s | RawHTML s -> s | Nil -> ""
|
||||
| List _ -> Sx_render.render_to_html a global_env
|
||||
| List _ -> Sx_render.sx_render_to_html global_env a global_env
|
||||
| _ -> value_to_string a) args)));
|
||||
bind "raw!" (fun args ->
|
||||
RawHTML (String.concat "" (List.map (fun a ->
|
||||
|
||||
@@ -854,6 +854,10 @@ let () =
|
||||
| [Component c] -> c.c_body
|
||||
| [Island i] -> i.i_body
|
||||
| _ -> Nil);
|
||||
register "component-file" (fun args ->
|
||||
match args with [v] -> component_file v | _ -> Nil);
|
||||
register "component-set-file!" (fun args ->
|
||||
match args with [v; f] -> component_set_file v f | _ -> Nil);
|
||||
register "macro?" (fun args ->
|
||||
match args with [Macro _] -> Bool true | _ -> Bool false);
|
||||
register "for-each-indexed" (fun args ->
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -102,7 +102,7 @@ let get_val container key =
|
||||
| "match-val" -> f.cf_extra | "current-item" -> f.cf_extra
|
||||
| "update-fn" -> f.cf_extra | "head-name" -> f.cf_extra
|
||||
| "emitted" -> f.cf_extra2 | "effect-list" -> f.cf_extra2
|
||||
| "first-render" -> f.cf_extra2
|
||||
| "first-render" -> f.cf_extra2 | "file" -> f.cf_env
|
||||
| _ -> Nil)
|
||||
| Dict d, String k -> dict_get d k
|
||||
| Dict d, Keyword k -> dict_get d k
|
||||
|
||||
@@ -111,6 +111,7 @@ and component = {
|
||||
c_body : value;
|
||||
c_closure : env;
|
||||
c_affinity : string; (** "auto" | "client" | "server" *)
|
||||
mutable c_file : string option; (** Source file path *)
|
||||
mutable c_compiled : vm_closure option; (** Lazy JIT cache *)
|
||||
}
|
||||
|
||||
@@ -120,6 +121,7 @@ and island = {
|
||||
i_has_children : bool;
|
||||
i_body : value;
|
||||
i_closure : env;
|
||||
mutable i_file : string option; (** Source file path *)
|
||||
}
|
||||
|
||||
and macro = {
|
||||
@@ -287,7 +289,7 @@ let make_component name params has_children body closure affinity =
|
||||
Component {
|
||||
c_name = n; c_params = ps; c_has_children = hc;
|
||||
c_body = body; c_closure = unwrap_env_val closure; c_affinity = aff;
|
||||
c_compiled = None;
|
||||
c_file = None; c_compiled = None;
|
||||
}
|
||||
|
||||
let make_island name params has_children body closure =
|
||||
@@ -297,6 +299,7 @@ let make_island name params has_children body closure =
|
||||
Island {
|
||||
i_name = n; i_params = ps; i_has_children = hc;
|
||||
i_body = body; i_closure = unwrap_env_val closure;
|
||||
i_file = None;
|
||||
}
|
||||
|
||||
let make_macro params rest_param body closure name =
|
||||
@@ -400,6 +403,19 @@ let component_name = function
|
||||
| Island i -> String i.i_name
|
||||
| v -> raise (Eval_error ("Expected component, got " ^ type_of v))
|
||||
|
||||
let component_file = function
|
||||
| Component c -> (match c.c_file with Some f -> String f | None -> Nil)
|
||||
| Island i -> (match i.i_file with Some f -> String f | None -> Nil)
|
||||
| _ -> Nil
|
||||
|
||||
let component_set_file v f =
|
||||
(match v, f with
|
||||
| Component c, String s -> c.c_file <- Some s
|
||||
| Island i, String s -> i.i_file <- Some s
|
||||
| _ -> ()); Nil
|
||||
|
||||
let component_set_file_b = component_set_file
|
||||
|
||||
let component_params = function
|
||||
| Component c -> List (List.map (fun s -> String s) c.c_params)
|
||||
| Island i -> List (List.map (fun s -> String s) i.i_params)
|
||||
|
||||
@@ -202,7 +202,7 @@
|
||||
"notify-subscribers" "flush-subscribers" "dispose-computed"
|
||||
"continuation?" "continuation-data" "make-cek-continuation"
|
||||
"dynamic-wind-call" "strip-prefix"
|
||||
"component-set-param-types!" "parse-comp-params" "parse-macro-params"
|
||||
"component-set-param-types!" "component-file" "component-set-file!" "parse-comp-params" "parse-macro-params"
|
||||
"parse-keyword-args"))
|
||||
|
||||
(define ml-is-known-name?
|
||||
|
||||
Reference in New Issue
Block a user