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:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user