Page helpers in pure SX: 3 OCaml primitives + SX data + SX helpers

Add pretty-print, read-file, env-list-typed primitives to OCaml kernel.
Convert Python reference data (attrs, headers, events, primitives) to SX
data files. Implement page helpers (component-source, handler-source,
read-spec-file, reference-data, etc.) as pure SX functions.

The helper dispatcher in HTTP mode looks up named functions in the env
and calls them directly, replacing the Python IO bridge path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 07:42:59 +00:00
parent 9ce8659f74
commit 465ce1abcb
4 changed files with 766 additions and 26 deletions

View File

@@ -1907,33 +1907,116 @@ let http_load_files env files =
) files;
rebind_host_extensions env
(* Pretty printer — AST value → formatted SX source string *)
let pp_atom = Sx_types.inspect
let rec est_width = function
| Nil -> 3 | Bool true -> 4 | Bool false -> 5
| Number n -> String.length (if Float.is_integer n then string_of_int (int_of_float n) else Printf.sprintf "%g" n)
| String s -> String.length s + 2
| Symbol s -> String.length s
| Keyword k -> String.length k + 1
| SxExpr s -> String.length s + 2
| List items | ListRef { contents = items } ->
2 + List.fold_left (fun acc x -> acc + est_width x + 1) 0 items
| _ -> 10
let pretty_print_value ?(max_width=80) v =
let buf = Buffer.create 4096 in
let rec pp indent v =
match v with
| List items | ListRef { contents = items } when items <> [] ->
if est_width v <= max_width - indent then
Buffer.add_string buf (pp_atom v)
else begin
Buffer.add_char buf '(';
let head = List.hd items in
Buffer.add_string buf (pp_atom head);
let child_indent = indent + 2 in
let rest = List.tl items in
let rec emit = function
| [] -> ()
| Keyword k :: v :: rest ->
Buffer.add_char buf '\n';
Buffer.add_string buf (String.make child_indent ' ');
Buffer.add_char buf ':';
Buffer.add_string buf k;
Buffer.add_char buf ' ';
pp child_indent v;
emit rest
| item :: rest ->
Buffer.add_char buf '\n';
Buffer.add_string buf (String.make child_indent ' ');
pp child_indent item;
emit rest
in
emit rest;
Buffer.add_char buf ')'
end
| _ -> Buffer.add_string buf (pp_atom v)
in
pp 0 v;
Buffer.contents buf
let http_setup_page_helpers env =
(* Page helpers that Python normally provides. Minimal stubs for HTTP mode.
These return empty/nil so pages render without hanging. *)
let bind name fn = ignore (env_bind env name (NativeFn (name, fn))) in
(* highlight — passthrough without syntax coloring *)
bind "highlight" (fun args ->
(* Primitive 1: pretty-print — AST → formatted SX source *)
bind "pretty-print" (fun args ->
match args with
| String code :: _ ->
let escaped = escape_sx_string code in
SxExpr (Printf.sprintf "(pre :class \"text-sm overflow-x-auto\" (code \"%s\"))" escaped)
| _ -> Nil);
(* Stub all Python page helpers with nil/empty returns *)
| [v] -> String (pretty_print_value v)
| _ -> raise (Eval_error "pretty-print: expected 1 argument"));
(* Primitive 2: read-file — path → string contents or nil *)
bind "read-file" (fun args ->
match args with
| [String path] ->
(try
let ic = open_in path in
let n = in_channel_length ic in
let s = Bytes.create n in
really_input ic s 0 n;
close_in ic;
String (Bytes.to_string s)
with _ -> Nil)
| _ -> raise (Eval_error "read-file: expected string path"));
(* Primitive 3: env-list-typed — list all bindings of a given type *)
bind "env-list-typed" (fun args ->
match args with
| [String type_name] ->
let matches = ref [] in
Hashtbl.iter (fun id v ->
let matches_type = match type_name, v with
| "component", Component _ -> true
| "island", Island _ -> true
| "lambda", Lambda _ -> true
| "macro", Macro _ -> true
| "native", NativeFn _ -> true
| _ -> false
in
if matches_type then
matches := String (Sx_types.unintern id) :: !matches
) env.bindings;
List (List.sort compare !matches)
| _ -> raise (Eval_error "env-list-typed: expected type name string"));
(* helper dispatcher — looks up named function in env, calls it directly.
In coroutine mode this goes through the Python IO bridge.
In HTTP mode we dispatch locally to functions defined by SX helpers. *)
bind "helper" (fun args ->
match args with
| String name :: rest ->
(try
let fn = env_get env name in
Sx_ref.cek_call fn (List rest)
with Eval_error _ ->
Printf.eprintf "[helper] not found: %s\n%!" name;
Nil)
| _ -> raise (Eval_error "helper: expected (helper \"name\" ...args)"));
(* Stub remaining demo/action helpers that need real IO *)
let stub name = bind name (fun _args -> Nil) in
let stub_s name = bind name (fun _args -> String "") in
stub_s "component-source";
stub_s "handler-source";
stub "primitives-data";
stub "special-forms-data";
stub "reference-data";
stub "attr-detail-data";
stub "header-detail-data";
stub "event-detail-data";
stub_s "read-spec-file";
stub "bootstrapper-data";
stub "bundle-analyzer-data";
stub "routing-analyzer-data";
stub "data-test-data";
stub "run-spec-tests";
stub "run-modular-tests";
stub "streaming-demo-data";
@@ -1942,9 +2025,7 @@ let http_setup_page_helpers env =
stub "action:add-demo-item";
stub "offline-demo-data";
stub "prove-data";
stub "page-helpers-demo-data";
stub "spec-explorer-data";
stub "spec-explorer-data-by-slug"
stub "page-helpers-demo-data"
let http_mode port =
let env = make_server_env () in
@@ -1972,6 +2053,11 @@ let http_mode port =
let dev_path = project_dir ^ "/sx/sx" in
if Sys.file_exists (docker_path ^ "/page-functions.sx") then docker_path
else dev_path in
(* Expose project paths to SX helpers *)
ignore (env_bind env "_project-dir" (String project_dir));
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));
let t0 = Unix.gettimeofday () in
(* Core spec + adapters.
Skip: primitives.sx (declarative metadata — all prims native in OCaml),