···11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44+55+## Project Overview
66+77+This is an OCaml toplevel (REPL) designed to run in a web worker. The project consists of multiple OPAM packages that work together to provide an OCaml interactive environment in the browser.
88+99+## Build Commands
1010+1111+```bash
1212+# Build the entire project
1313+dune build
1414+1515+# Run tests
1616+dune runtest
1717+1818+# Build and watch for changes
1919+dune build --watch
2020+2121+# Run a specific test
2222+dune test test/cram
2323+```
2424+2525+## Running the Example
2626+2727+The worker needs to be served by an HTTP server rather than loaded from the filesystem:
2828+2929+```bash
3030+dune build
3131+cd _build/default/example
3232+python3 -m http.server 8000
3333+# Then open http://localhost:8000/
3434+```
3535+3636+## Architecture
3737+3838+The codebase is organized into several interconnected packages:
3939+4040+- **js_top_worker**: Core library implementing the OCaml toplevel functionality
4141+- **js_top_worker-web**: Web-specific worker implementation with browser integration
4242+- **js_top_worker-client**: Client library for communicating with the worker (Lwt-based)
4343+- **js_top_worker-client_fut**: Alternative client library using Fut for concurrency
4444+- **js_top_worker-rpc**: RPC definitions and communication layer
4545+- **js_top_worker-unix**: Unix implementation for testing outside the browser
4646+- **js_top_worker-bin**: Command-line tools including `jtw` for package management
4747+4848+Key directories:
4949+- `lib/`: Core toplevel implementation with OCaml compiler integration
5050+- `idl/`: RPC interface definitions using `ppx_deriving_rpc`
5151+- `example/`: Example applications demonstrating worker usage
5252+- `bin/`: Command-line tools, notably `jtw` for OPAM package handling
5353+5454+The system uses RPC (via `rpclib`) for communication between the client and worker, with support for both browser WebWorkers and Unix sockets for testing.
···5050 Sys.remove filename_out;
5151 Sys.remove filename_err)
52525353-let (let*) = Lwt.bind
5454-5353+let ( let* ) = Lwt.bind
55545655let binary_handler process s =
5756 (* Read a 16 byte length encoded as a string *)
···7574 p_mkdir dir
76757776let serve_requests rpcfn path =
7878- let (let*) = Lwt.bind in
7777+ let ( let* ) = Lwt.bind in
7978 (try Unix.unlink path with Unix.Unix_error (Unix.ENOENT, _, _) -> ());
8079 mkdir_rec (Filename.dirname path) 0o0755;
8180 let sock = Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
···8483 let rec loop () =
8584 let* this_connection, _ = Lwt_unix.accept sock in
8685 let* () =
8787- Lwt.finalize (fun () ->
8888- (* Here I am calling M.run to make sure that I am running the process,
8686+ Lwt.finalize
8787+ (fun () ->
8888+ (* Here I am calling M.run to make sure that I am running the process,
8989 this is not much of a problem with IdM or ExnM, but in general you
9090 should ensure that the computation is started by a runner. *)
9191- binary_handler rpcfn this_connection)
9292- (fun () -> Lwt_unix.close this_connection)
9191+ binary_handler rpcfn this_connection)
9292+ (fun () -> Lwt_unix.close this_connection)
9393 in
9494 loop ()
9595 in
···131131 with exn ->
132132 handle_findlib_error exn;
133133 []
134134-134134+135135 let path = "/tmp"
136136end
137137
+95-93
lib/impl.ml
···235235 with End_of_file -> ());
236236 flush_all ()
237237238238- let execute :
239239- string ->
240240- Toplevel_api_gen.exec_result =
238238+ let execute : string -> Toplevel_api_gen.exec_result =
241239 let code_buff = Buffer.create 100 in
242240 let res_buff = Buffer.create 100 in
243241 let pp_code = Format.formatter_of_buffer code_buff in
···304302 let path =
305303 match !path with Some p -> p | None -> failwith "Path not set"
306304 in
307307- let (let*) = Lwt.bind in
308308- let* () = Lwt_list.iter_p
309309- (fun name ->
310310- let filename = filename_of_module name in
311311- let* r = fetch (filename_of_module name) in
312312- let () =
313313- match r with
314314- | Ok content -> (
315315- let name = Filename.(concat path filename) in
316316- try S.create_file ~name ~content with _ -> ())
317317- | Error _ -> () in
318318- Lwt.return ())
319319- dcs.dcs_toplevel_modules
305305+ let ( let* ) = Lwt.bind in
306306+ let* () =
307307+ Lwt_list.iter_p
308308+ (fun name ->
309309+ let filename = filename_of_module name in
310310+ let* r = fetch (filename_of_module name) in
311311+ let () =
312312+ match r with
313313+ | Ok content -> (
314314+ let name = Filename.(concat path filename) in
315315+ try S.create_file ~name ~content with _ -> ())
316316+ | Error _ -> ()
317317+ in
318318+ Lwt.return ())
319319+ dcs.dcs_toplevel_modules
320320 in
321321322322 let new_load ~s ~old_loader ~allow_hidden ~unit_name =
···352352 let furl = "file://" in
353353 let l = String.length furl in
354354 let () =
355355- if String.length dcs.dcs_url > l && String.sub dcs.dcs_url 0 l = furl then
356356- let path = String.sub dcs.dcs_url l (String.length dcs.dcs_url - l) in
357357- Topdirs.dir_directory path
358358- else
359359- let open Persistent_env.Persistent_signature in
360360- let old_loader = !load in
361361- load := new_load ~s:"comp" ~old_loader;
355355+ if String.length dcs.dcs_url > l && String.sub dcs.dcs_url 0 l = furl then
356356+ let path = String.sub dcs.dcs_url l (String.length dcs.dcs_url - l) in
357357+ Topdirs.dir_directory path
358358+ else
359359+ let open Persistent_env.Persistent_signature in
360360+ let old_loader = !load in
361361+ load := new_load ~s:"comp" ~old_loader;
362362363363- let open Ocaml_typing.Persistent_env.Persistent_signature in
364364- let old_loader = !load in
365365- load := new_load ~s:"merl" ~old_loader in
363363+ let open Ocaml_typing.Persistent_env.Persistent_signature in
364364+ let old_loader = !load in
365365+ load := new_load ~s:"merl" ~old_loader
366366+ in
366367 Lwt.return ()
367368368369 let init (init_libs : Toplevel_api_gen.init_config) =
369369- Lwt.catch (fun () ->
370370- Logs.info (fun m -> m "init()");
371371- path := Some S.path;
370370+ Lwt.catch
371371+ (fun () ->
372372+ Logs.info (fun m -> m "init()");
373373+ path := Some S.path;
372374373373- findlib_v := Some (S.findlib_init "findlib_index");
374374- let stdlib_dcs =
375375- match init_libs.stdlib_dcs with
376376- | Some dcs -> dcs
377377- | None -> "lib/ocaml/dynamic_cmis.json"
378378- in
379379- let* () =
380380- match S.get_stdlib_dcs stdlib_dcs with
381381- | [ dcs ] -> add_dynamic_cmis dcs
382382- | _ -> Lwt.return () in
383383- Clflags.no_check_prims := true;
375375+ findlib_v := Some (S.findlib_init "findlib_index");
376376+ let stdlib_dcs =
377377+ match init_libs.stdlib_dcs with
378378+ | Some dcs -> dcs
379379+ | None -> "lib/ocaml/dynamic_cmis.json"
380380+ in
381381+ let* () =
382382+ match S.get_stdlib_dcs stdlib_dcs with
383383+ | [ dcs ] -> add_dynamic_cmis dcs
384384+ | _ -> Lwt.return ()
385385+ in
386386+ Clflags.no_check_prims := true;
384387385385- requires := init_libs.findlib_requires;
386386- functions := Some [];
387387- execution_allowed := init_libs.execute;
388388+ requires := init_libs.findlib_requires;
389389+ functions := Some [];
390390+ execution_allowed := init_libs.execute;
388391389389- (* Set up the toplevel environment *)
390390- Logs.info (fun m -> m "init() finished");
392392+ (* Set up the toplevel environment *)
393393+ Logs.info (fun m -> m "init() finished");
391394392392- Lwt.return (Ok ()))
393393- (fun e ->
394394- Lwt.return (Error
395395- (Toplevel_api_gen.InternalError (Printexc.to_string e))))
395395+ Lwt.return (Ok ()))
396396+ (fun e ->
397397+ Lwt.return
398398+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
396399397400 let setup () =
398398- Lwt.catch (fun () ->
399399- Logs.info (fun m -> m "setup() ...");
401401+ Lwt.catch
402402+ (fun () ->
403403+ Logs.info (fun m -> m "setup() ...");
400404401401- let o =
402402- try
403403- match !functions with
404404- | Some l -> setup l ()
405405- | None -> failwith "Error: toplevel has not been initialised"
406406- with
407407- | Persistent_env.Error e ->
408408- Persistent_env.report_error Format.err_formatter e;
409409- let err = Format.asprintf "%a" Persistent_env.report_error e in
410410- failwith ("Error: " ^ err)
411411- | Env.Error e ->
412412- Env.report_error Format.err_formatter e;
413413- let err = Format.asprintf "%a" Env.report_error e in
414414- failwith ("Error: " ^ err)
415415- in
405405+ let o =
406406+ try
407407+ match !functions with
408408+ | Some l -> setup l ()
409409+ | None -> failwith "Error: toplevel has not been initialised"
410410+ with
411411+ | Persistent_env.Error e ->
412412+ Persistent_env.report_error Format.err_formatter e;
413413+ let err = Format.asprintf "%a" Persistent_env.report_error e in
414414+ failwith ("Error: " ^ err)
415415+ | Env.Error e ->
416416+ Env.report_error Format.err_formatter e;
417417+ let err = Format.asprintf "%a" Env.report_error e in
418418+ failwith ("Error: " ^ err)
419419+ in
416420417417- let dcs =
418418- match !findlib_v with
419419- | Some v -> S.require (not !execution_allowed) v !requires
420420- | None -> []
421421- in
422422- let* () = Lwt_list.iter_p add_dynamic_cmis dcs in
421421+ let dcs =
422422+ match !findlib_v with
423423+ | Some v -> S.require (not !execution_allowed) v !requires
424424+ | None -> []
425425+ in
426426+ let* () = Lwt_list.iter_p add_dynamic_cmis dcs in
423427424424- Logs.info (fun m -> m "setup() finished");
428428+ Logs.info (fun m -> m "setup() finished");
425429426426- Lwt.return
427427- (Ok Toplevel_api_gen.
428428- {
429429- stdout = string_opt o.stdout;
430430- stderr = string_opt o.stderr;
431431- sharp_ppf = None;
432432- caml_ppf = None;
433433- highlight = None;
434434- mime_vals = [];
435435- }))
436436- (fun e ->
437437- Lwt.return (Error
438438- (Toplevel_api_gen.InternalError (Printexc.to_string e))))
430430+ Lwt.return
431431+ (Ok
432432+ Toplevel_api_gen.
433433+ {
434434+ stdout = string_opt o.stdout;
435435+ stderr = string_opt o.stderr;
436436+ sharp_ppf = None;
437437+ caml_ppf = None;
438438+ highlight = None;
439439+ mime_vals = [];
440440+ }))
441441+ (fun e ->
442442+ Lwt.return
443443+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
439444440445 let complete _phrase = failwith "Not implemented"
441446···602607 let mime_vals =
603608 List.fold_left
604609 (fun acc (phr, _junk, _output) ->
605605- let new_output =
606606- execute phr
607607- in
610610+ let new_output = execute phr in
608611 Printf.bprintf buf "# %s\n" phr;
609612 let r =
610613 Option.to_list new_output.stdout
···839842 let b = Sys.file_exists (prefix ^ ".cmi") in
840843 failed_cells := StringSet.remove id !failed_cells;
841844 Logs.info (fun m -> m "file_exists: %s = %b" (prefix ^ ".cmi") b));
842842- Ocaml_typing.Cmi_cache.clear ();
845845+ Ocaml_typing.Cmi_cache.clear ()
843846 with
844847 | Env.Error e ->
845848 Logs.err (fun m -> m "Env.Error: %a" Env.report_error e);
···913916 source;
914917 })
915918 in
916916- (if List.length errors = 0
917917- then add_cmi id deps src
918918- else failed_cells := StringSet.add id !failed_cells);
919919+ if List.length errors = 0 then add_cmi id deps src
920920+ else failed_cells := StringSet.add id !failed_cells;
919921920922 (* Logs.info (fun m -> m "Got to end"); *)
921923 IdlM.ErrM.return errors
+14-15
lib/jslib.ml
···33 (fun s -> Js_of_ocaml.(Console.console##log (Js.string s)))
44 fmt
5566-76let map_url url =
87 let open Js_of_ocaml in
98 let global_rel_url =
1010- let x : Js.js_string Js.t option = Js.Unsafe.js_expr "globalThis.__global_rel_url" |> Js.Optdef.to_option in
99+ let x : Js.js_string Js.t option =
1010+ Js.Unsafe.js_expr "globalThis.__global_rel_url" |> Js.Optdef.to_option
1111+ in
1112 Option.map Js.to_string x
1213 in
1313- match global_rel_url with
1414- | Some rel -> Filename.concat rel url
1515- | None -> url
1414+ match global_rel_url with Some rel -> Filename.concat rel url | None -> url
16151716let sync_get url =
1817 let open Js_of_ocaml in
···3231 (fun b -> Some (Typed_array.String.of_arrayBuffer b))
3332 | _ -> None
34333535-3634let async_get url =
3737- let (let*) = Lwt.bind in
3535+ let ( let* ) = Lwt.bind in
3836 let open Js_of_ocaml in
3937 Console.console##log (Js.string ("Fetching: " ^ url));
4040- let* frame = Js_of_ocaml_lwt.XmlHttpRequest.perform_raw
4141- ~response_type:ArrayBuffer url in
3838+ let* frame =
3939+ Js_of_ocaml_lwt.XmlHttpRequest.perform_raw ~response_type:ArrayBuffer url
4040+ in
4241 match frame.code with
4342 | 200 ->
4444- Lwt.return (Js.Opt.case
4545- frame.content
4646- (fun () ->
4747- Error (`Msg "Failed to receive file"))
4848- (fun b -> Ok (Typed_array.String.of_arrayBuffer b)))
4343+ Lwt.return
4444+ (Js.Opt.case frame.content
4545+ (fun () -> Error (`Msg "Failed to receive file"))
4646+ (fun b -> Ok (Typed_array.String.of_arrayBuffer b)))
4947 | _ ->
5050- Lwt.return (Error (`Msg (Printf.sprintf "Failed to fetch %s: %d" url frame.code)))4848+ Lwt.return
4949+ (Error (`Msg (Printf.sprintf "Failed to fetch %s: %d" url frame.code)))