···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.
5555+5656+## Technical Q&A Log
5757+5858+When the user asks technical questions about the codebase, tools, or dependencies (especially js_of_ocaml, dune, findlib, etc.), Claude should:
5959+6060+1. **Answer the question** with technical accuracy
6161+2. **Record the Q&A** in `docs/technical-qa.md` with:
6262+ - The question asked
6363+ - The answer provided
6464+ - Verification steps taken (code inspection, testing, documentation lookup)
6565+ - Date of the entry
6666+6767+This creates institutional knowledge that persists across sessions.
+13
js_top_worker/README.md
···11+# An OCaml toplevel designed to run in a web worker
22+33+To run the example, the worker needs to be served by an http server rather
44+than loaded from the filesystem. Therefore the example may be run in the
55+following way:
66+77+```
88+$ dune build
99+$ cd _build/default/example
1010+$ python3 -m http.server 8000
1111+```
1212+1313+and then opening the URL `http://localhost:8000/`
+35
js_top_worker/bin/cmd_outputs.ml
···11+type log_dest =
22+ [ `Compile
33+ | `Compile_src
44+ | `Link
55+ | `Count_occurrences
66+ | `Generate
77+ | `Index
88+ | `Sherlodoc
99+ | `Classify ]
1010+1111+type log_line = { log_dest : log_dest; prefix : string; run : Run.t }
1212+1313+let outputs : log_line list ref = ref []
1414+1515+let maybe_log log_dest run =
1616+ match log_dest with
1717+ | Some (log_dest, prefix) ->
1818+ outputs := !outputs @ [ { log_dest; run; prefix } ]
1919+ | None -> ()
2020+2121+let submit log_dest desc cmd output_file =
2222+ match Worker_pool.submit desc cmd output_file with
2323+ | Ok x ->
2424+ maybe_log log_dest x;
2525+ String.split_on_char '\n' x.output
2626+ | Error exn -> raise exn
2727+2828+let submit_ignore_failures log_dest desc cmd output_file =
2929+ match Worker_pool.submit desc cmd output_file with
3030+ | Ok x ->
3131+ maybe_log log_dest x;
3232+ ()
3333+ | Error exn ->
3434+ Logs.err (fun m -> m "Error: %s" (Printexc.to_string exn));
3535+ ()
···11+(** Try to relativize a path against findlib_dir. If the result contains
22+ ".." (indicating the path is in a different tree), fall back to extracting
33+ the path components after "lib" directory. *)
44+let relativize_or_fallback ~findlib_dir path =
55+ (* First try standard relativize *)
66+ let rel = match Fpath.relativize ~root:findlib_dir path with
77+ | Some rel -> rel
88+ | None -> path (* shouldn't happen for absolute paths, but fallback *)
99+ in
1010+ (* If the result contains "..", use fallback instead *)
1111+ let segs = Fpath.segs rel in
1212+ if List.mem ".." segs then begin
1313+ (* Fallback: use path components after "lib" directory *)
1414+ let path_segs = Fpath.segs path in
1515+ let rec find_after_lib = function
1616+ | [] -> Fpath.v (Fpath.basename path)
1717+ | "lib" :: rest -> Fpath.v (String.concat Fpath.dir_sep rest)
1818+ | _ :: rest -> find_after_lib rest
1919+ in
2020+ find_after_lib path_segs
2121+ end else
2222+ rel
2323+2424+let cmi_files dir =
2525+ Bos.OS.Dir.fold_contents ~traverse:`None ~elements:`Files
2626+ (fun path acc ->
2727+ if Fpath.has_ext ".cmi" path then Fpath.filename path :: acc else acc)
2828+ [] dir
2929+3030+let gen_cmis ?path_prefix cmis =
3131+ let gen_one (dir, cmis) =
3232+ let all_cmis =
3333+ List.map (fun s -> String.sub s 0 (String.length s - 4)) cmis
3434+ in
3535+ let hidden, non_hidden =
3636+ List.partition (fun x -> Astring.String.is_infix ~affix:"__" x) all_cmis
3737+ in
3838+ let prefixes =
3939+ List.filter_map
4040+ (fun x ->
4141+ match Astring.String.cuts ~sep:"__" x with
4242+ | x :: _ -> Some (x ^ "__")
4343+ | _ -> None)
4444+ hidden
4545+ in
4646+ let prefixes = Util.StringSet.(of_list prefixes |> to_list) in
4747+ let findlib_dir = Ocamlfind.findlib_dir () |> Fpath.v in
4848+ let d = relativize_or_fallback ~findlib_dir dir in
4949+ (* Include path_prefix in dcs_url so it's correct relative to HTTP root *)
5050+ let dcs_url_path = match path_prefix with
5151+ | Some prefix -> Fpath.(v prefix / "lib" // d)
5252+ | None -> Fpath.(v "lib" // d)
5353+ in
5454+ let dcs =
5555+ {
5656+ Js_top_worker_rpc.Toplevel_api_gen.dcs_url = Fpath.to_string dcs_url_path;
5757+ dcs_toplevel_modules = List.map String.capitalize_ascii non_hidden;
5858+ dcs_file_prefixes = prefixes;
5959+ }
6060+ in
6161+ ( dir,
6262+ Jsonrpc.to_string
6363+ (Rpcmarshal.marshal
6464+ Js_top_worker_rpc.Toplevel_api_gen.typ_of_dynamic_cmis dcs) )
6565+ in
6666+ List.map gen_one cmis
6767+6868+(** Read dependency paths from a file (one path per line) *)
6969+let read_deps_file path =
7070+ match Bos.OS.File.read_lines (Fpath.v path) with
7171+ | Ok lines -> List.filter (fun s -> String.length s > 0) lines
7272+ | Error (`Msg m) ->
7373+ Format.eprintf "Warning: Failed to read deps file %s: %s\n%!" path m;
7474+ []
7575+7676+let opam verbose output_dir_str switch libraries no_worker path deps_file =
7777+ Opam.switch := switch;
7878+ (* When --path is specified, only compile the specified libraries (no deps) *)
7979+ let libraries_with_deps, libraries_only =
8080+ match Ocamlfind.deps libraries with
8181+ | Ok l ->
8282+ let all = Util.StringSet.of_list ("stdlib" :: l) in
8383+ (* In --path mode, don't auto-add stdlib - only include requested libs *)
8484+ let only = Util.StringSet.of_list libraries in
8585+ (all, only)
8686+ | Error (`Msg m) ->
8787+ Format.eprintf "Failed to find libs: %s\n%!" m;
8888+ failwith ("Bad libs: " ^ m)
8989+ in
9090+ (* In path mode, only compile the specified packages *)
9191+ let libraries = if path <> None then libraries_only else libraries_with_deps in
9292+ (* Read dependency paths from file if specified *)
9393+ let dep_paths = match deps_file with
9494+ | Some f -> read_deps_file f
9595+ | None -> []
9696+ in
9797+ Eio_main.run @@ fun env ->
9898+ Eio.Switch.run @@ fun sw ->
9999+ if verbose then Logs.set_level (Some Logs.Debug) else Logs.set_level None;
100100+ Logs.set_reporter (Logs_fmt.reporter ());
101101+ let () = Worker_pool.start_workers env sw 16 in
102102+ Logs.debug (fun m ->
103103+ m "Libraries: %a"
104104+ (Fmt.list ~sep:Fmt.comma Fmt.string)
105105+ (Util.StringSet.elements libraries));
106106+ (* output_dir is always from -o; --path is a subdirectory within it *)
107107+ let base_output_dir = Fpath.v output_dir_str in
108108+ let output_dir =
109109+ match path with
110110+ | Some p -> Fpath.(base_output_dir // v p)
111111+ | None -> base_output_dir
112112+ in
113113+ let meta_files =
114114+ List.map
115115+ (fun lib -> Ocamlfind.meta_file lib)
116116+ (Util.StringSet.elements libraries)
117117+ |> Util.StringSet.of_list
118118+ in
119119+ let cmi_dirs =
120120+ match Ocamlfind.deps (Util.StringSet.to_list libraries) with
121121+ | Ok libs ->
122122+ let dirs =
123123+ List.filter_map
124124+ (fun lib ->
125125+ match Ocamlfind.get_dir lib with Ok x -> Some x | _ -> None)
126126+ libs
127127+ in
128128+ dirs
129129+ | Error (`Msg m) ->
130130+ Format.eprintf "Failed to find libs: %s\n%!" m;
131131+ []
132132+ in
133133+ Format.eprintf "cmi_dirs: %a\n%!" (Fmt.list ~sep:Fmt.comma Fpath.pp) cmi_dirs;
134134+ (* In --path mode, only include cmi dirs from specified libraries and their
135135+ subpackages, not external dependencies *)
136136+ let cmi_dirs_to_copy =
137137+ if path <> None then
138138+ let lib_dirs =
139139+ List.filter_map
140140+ (fun lib ->
141141+ match Ocamlfind.get_dir lib with Ok x -> Some x | _ -> None)
142142+ (Util.StringSet.to_list libraries)
143143+ in
144144+ (* Filter cmi_dirs to include directories that are equal to or subdirectories
145145+ of lib_dirs. This includes subpackages like base.base_internalhash_types.
146146+ We check that the relative path doesn't start with ".." *)
147147+ List.filter
148148+ (fun dir ->
149149+ List.exists
150150+ (fun lib_dir ->
151151+ Fpath.equal dir lib_dir ||
152152+ match Fpath.relativize ~root:lib_dir dir with
153153+ | Some rel ->
154154+ let segs = Fpath.segs rel in
155155+ (match segs with
156156+ | ".." :: _ -> false (* Goes outside lib_dir *)
157157+ | _ -> true)
158158+ | None -> false)
159159+ lib_dirs)
160160+ cmi_dirs
161161+ else
162162+ cmi_dirs
163163+ in
164164+ let cmis =
165165+ List.fold_left
166166+ (fun acc dir ->
167167+ match cmi_files dir with
168168+ | Ok files -> (dir, files) :: acc
169169+ | Error _ -> acc)
170170+ [] cmi_dirs_to_copy
171171+ in
172172+ let ( let* ) = Result.bind in
173173+174174+ let _ =
175175+ let* _ = Bos.OS.Dir.create output_dir in
176176+ let findlib_dir = Ocamlfind.findlib_dir () |> Fpath.v in
177177+178178+ List.iter
179179+ (fun (dir, files) ->
180180+ let d = relativize_or_fallback ~findlib_dir dir in
181181+ List.iter
182182+ (fun f ->
183183+ let dest_dir = Fpath.(output_dir / "lib" // d) in
184184+ let dest = Fpath.(dest_dir / f) in
185185+ let _ = Bos.OS.Dir.create ~path:true dest_dir in
186186+ match Bos.OS.File.exists dest with
187187+ | Ok true -> ()
188188+ | Ok false -> Util.cp Fpath.(dir / f) dest
189189+ | Error _ -> failwith "file exists failed")
190190+ files)
191191+ cmis;
192192+193193+ let meta_rels =
194194+ Util.StringSet.fold
195195+ (fun meta_file acc ->
196196+ let meta_file = Fpath.v meta_file in
197197+ let d =
198198+ Fpath.relativize ~root:findlib_dir meta_file
199199+ |> Option.get |> Fpath.parent
200200+ in
201201+ (meta_file, d) :: acc)
202202+ meta_files []
203203+ in
204204+205205+ List.iter
206206+ (fun (meta_file, d) ->
207207+ let dest = Fpath.(output_dir / "lib" // d) in
208208+ let _ = Bos.OS.Dir.create dest in
209209+ Util.cp meta_file dest)
210210+ meta_rels;
211211+212212+ (* Generate findlib_index as JSON with metas field *)
213213+ let metas_json =
214214+ List.map
215215+ (fun (meta_file, d) ->
216216+ let file = Fpath.filename meta_file in
217217+ let rel_path = Fpath.(v "lib" // d / file) in
218218+ `String (Fpath.to_string rel_path))
219219+ meta_rels
220220+ in
221221+ (* TODO: dep_paths should also contribute META paths once we have full universe info *)
222222+ let _ = dep_paths in
223223+ let findlib_json = `Assoc [("metas", `List metas_json)] in
224224+ Out_channel.with_open_bin
225225+ Fpath.(output_dir / "findlib_index" |> to_string)
226226+ (fun oc -> Printf.fprintf oc "%s\n" (Yojson.Safe.to_string findlib_json));
227227+228228+ (* Compile archives for each library AND its subpackages *)
229229+ Util.StringSet.iter
230230+ (fun lib ->
231231+ (* Get subpackages (e.g., base.base_internalhash_types for base) *)
232232+ let sub_libs = Ocamlfind.sub_libraries lib in
233233+ let all_libs = Util.StringSet.add lib sub_libs in
234234+ Util.StringSet.iter
235235+ (fun sub_lib ->
236236+ match Ocamlfind.get_dir sub_lib with
237237+ | Error _ -> ()
238238+ | Ok dir ->
239239+ let archives = Ocamlfind.archives sub_lib in
240240+ let archives = List.map (fun x -> Fpath.(dir / x)) archives in
241241+ let d = relativize_or_fallback ~findlib_dir dir in
242242+ let dest = Fpath.(output_dir / "lib" // d) in
243243+ let (_ : (bool, _) result) = Bos.OS.Dir.create dest in
244244+ let compile_archive archive =
245245+ let output = Fpath.(dest / (Fpath.filename archive ^ ".js")) in
246246+ let js_runtime = Ocamlfind.jsoo_runtime sub_lib in
247247+ let js_files =
248248+ List.map (fun f -> Fpath.(dir / f |> to_string)) js_runtime
249249+ in
250250+ let base_cmd =
251251+ match switch with
252252+ | None -> Bos.Cmd.(v "js_of_ocaml")
253253+ | Some s ->
254254+ Bos.Cmd.(v "opam" % "exec" % "--switch" % s % "--" % "js_of_ocaml")
255255+ in
256256+ let cmd =
257257+ Bos.Cmd.(
258258+ base_cmd % "compile" % "--toplevel" % "--include-runtime"
259259+ % "--effects=disabled")
260260+ in
261261+ let cmd = List.fold_left (fun c f -> Bos.Cmd.(c % f)) cmd js_files in
262262+ let cmd =
263263+ Bos.Cmd.(cmd % Fpath.to_string archive % "-o" % Fpath.to_string output)
264264+ in
265265+ ignore (Util.lines_of_process cmd)
266266+ in
267267+ List.iter compile_archive archives)
268268+ all_libs)
269269+ libraries;
270270+271271+ (* Format.eprintf "@[<hov 2>dir: %a [%a]@]\n%!" Fpath.pp dir (Fmt.list ~sep:Fmt.sp Fmt.string) files) cmis; *)
272272+ Ok ()
273273+ in
274274+ let init_cmis = gen_cmis ?path_prefix:path cmis in
275275+ List.iter
276276+ (fun (dir, dcs) ->
277277+ let findlib_dir = Ocamlfind.findlib_dir () |> Fpath.v in
278278+ let d = Fpath.relativize ~root:findlib_dir dir in
279279+ match d with
280280+ | None ->
281281+ Format.eprintf "Failed to relativize %a wrt %a\n%!" Fpath.pp dir
282282+ Fpath.pp findlib_dir
283283+ | Some dir ->
284284+ Format.eprintf "Generating %a\n%!" Fpath.pp dir;
285285+ let dir = Fpath.(output_dir / "lib" // dir) in
286286+ let _ = Bos.OS.Dir.create dir in
287287+ let oc = open_out Fpath.(dir / "dynamic_cmis.json" |> to_string) in
288288+ Printf.fprintf oc "%s" dcs;
289289+ close_out oc)
290290+ init_cmis;
291291+ Format.eprintf "Number of cmis: %d\n%!" (List.length init_cmis);
292292+293293+ let () =
294294+ if no_worker then () else Mk_backend.mk switch output_dir
295295+ in
296296+297297+ `Ok ()
298298+299299+(** Generate a single package's universe directory.
300300+ Returns (pkg_path, meta_path) where meta_path is the full path to META
301301+ relative to the output_dir root. *)
302302+let generate_package_universe ~switch ~output_dir ~findlib_dir ~pkg ~pkg_deps =
303303+ (* Use package name as directory path *)
304304+ let pkg_path = pkg in
305305+ let pkg_output_dir = Fpath.(output_dir / pkg_path) in
306306+ let _ = Bos.OS.Dir.create ~path:true pkg_output_dir in
307307+308308+ (* Get the package's directory and copy cmi files *)
309309+ let pkg_dir = match Ocamlfind.get_dir pkg with
310310+ | Ok d -> d
311311+ | Error _ -> failwith ("Cannot find package: " ^ pkg)
312312+ in
313313+314314+ (* Also include subpackages (directories under pkg_dir) *)
315315+ let all_pkg_dirs =
316316+ let sub_libs = Ocamlfind.sub_libraries pkg in
317317+ Util.StringSet.fold (fun sub acc ->
318318+ match Ocamlfind.get_dir sub with
319319+ | Ok d -> d :: acc
320320+ | Error _ -> acc)
321321+ sub_libs [pkg_dir]
322322+ |> List.sort_uniq Fpath.compare
323323+ in
324324+325325+ (* Copy cmi files *)
326326+ List.iter (fun dir ->
327327+ match cmi_files dir with
328328+ | Ok files ->
329329+ let d = relativize_or_fallback ~findlib_dir dir in
330330+ List.iter (fun f ->
331331+ let dest_dir = Fpath.(pkg_output_dir / "lib" // d) in
332332+ let dest = Fpath.(dest_dir / f) in
333333+ let _ = Bos.OS.Dir.create ~path:true dest_dir in
334334+ match Bos.OS.File.exists dest with
335335+ | Ok true -> ()
336336+ | Ok false -> Util.cp Fpath.(dir / f) dest
337337+ | Error _ -> ())
338338+ files
339339+ | Error _ -> ())
340340+ all_pkg_dirs;
341341+342342+ (* Copy META file *)
343343+ let meta_file = Fpath.v (Ocamlfind.meta_file pkg) in
344344+ let meta_rel = relativize_or_fallback ~findlib_dir meta_file |> Fpath.parent in
345345+ let meta_dest = Fpath.(pkg_output_dir / "lib" // meta_rel) in
346346+ let _ = Bos.OS.Dir.create ~path:true meta_dest in
347347+ Util.cp meta_file meta_dest;
348348+349349+ (* Compile archives for main package and all subpackages *)
350350+ let sub_libs = Ocamlfind.sub_libraries pkg in
351351+ let all_libs = Util.StringSet.add pkg sub_libs in
352352+ Util.StringSet.iter (fun lib ->
353353+ match Ocamlfind.get_dir lib with
354354+ | Error _ -> ()
355355+ | Ok lib_dir ->
356356+ let archives = Ocamlfind.archives lib in
357357+ let archives = List.map (fun x -> Fpath.(lib_dir / x)) archives in
358358+ let d = relativize_or_fallback ~findlib_dir lib_dir in
359359+ let dest = Fpath.(pkg_output_dir / "lib" // d) in
360360+ let _ = Bos.OS.Dir.create ~path:true dest in
361361+ List.iter (fun archive ->
362362+ let output = Fpath.(dest / (Fpath.filename archive ^ ".js")) in
363363+ let js_runtime = Ocamlfind.jsoo_runtime lib in
364364+ let js_files = List.map (fun f -> Fpath.(lib_dir / f |> to_string)) js_runtime in
365365+ let base_cmd = match switch with
366366+ | None -> Bos.Cmd.(v "js_of_ocaml")
367367+ | Some s -> Bos.Cmd.(v "opam" % "exec" % "--switch" % s % "--" % "js_of_ocaml")
368368+ in
369369+ let cmd = Bos.Cmd.(base_cmd % "compile" % "--toplevel" % "--include-runtime" % "--effects=disabled") in
370370+ let cmd = List.fold_left (fun c f -> Bos.Cmd.(c % f)) cmd js_files in
371371+ let cmd = Bos.Cmd.(cmd % Fpath.to_string archive % "-o" % Fpath.to_string output) in
372372+ ignore (Util.lines_of_process cmd))
373373+ archives)
374374+ all_libs;
375375+376376+ (* Generate dynamic_cmis.json for each directory *)
377377+ List.iter (fun dir ->
378378+ match cmi_files dir with
379379+ | Ok files ->
380380+ let all_cmis = List.map (fun s -> String.sub s 0 (String.length s - 4)) files in
381381+ let hidden, non_hidden = List.partition (fun x -> Astring.String.is_infix ~affix:"__" x) all_cmis in
382382+ let prefixes = List.filter_map (fun x ->
383383+ match Astring.String.cuts ~sep:"__" x with
384384+ | x :: _ -> Some (x ^ "__")
385385+ | _ -> None) hidden in
386386+ let prefixes = Util.StringSet.(of_list prefixes |> to_list) in
387387+ let d = relativize_or_fallback ~findlib_dir dir in
388388+ (* Include pkg_path in dcs_url so it's correct relative to the HTTP root *)
389389+ let dcs = {
390390+ Js_top_worker_rpc.Toplevel_api_gen.dcs_url = Fpath.(v pkg_path / "lib" // d |> to_string);
391391+ dcs_toplevel_modules = List.map String.capitalize_ascii non_hidden;
392392+ dcs_file_prefixes = prefixes;
393393+ } in
394394+ let dcs_json = Jsonrpc.to_string (Rpcmarshal.marshal Js_top_worker_rpc.Toplevel_api_gen.typ_of_dynamic_cmis dcs) in
395395+ let dcs_dir = Fpath.(pkg_output_dir / "lib" // d) in
396396+ let _ = Bos.OS.Dir.create ~path:true dcs_dir in
397397+ let oc = open_out Fpath.(dcs_dir / "dynamic_cmis.json" |> to_string) in
398398+ Printf.fprintf oc "%s" dcs_json;
399399+ close_out oc
400400+ | Error _ -> ())
401401+ all_pkg_dirs;
402402+403403+ (* Return pkg_path and the META path relative to pkg_path *)
404404+ let local_meta_path = Fpath.(v "lib" // meta_rel / "META" |> to_string) in
405405+ (pkg_path, local_meta_path, pkg_deps)
406406+407407+let opam_all verbose output_dir_str switch libraries no_worker all_pkgs =
408408+ Opam.switch := switch;
409409+410410+ (* Get all packages and their dependencies *)
411411+ let all_packages =
412412+ if all_pkgs then
413413+ (* Build all installed packages *)
414414+ Ocamlfind.all ()
415415+ else if libraries = [] then
416416+ (* No packages specified, just stdlib *)
417417+ ["stdlib"]
418418+ else
419419+ match Ocamlfind.deps libraries with
420420+ | Ok l -> "stdlib" :: l
421421+ | Error (`Msg m) -> failwith ("Failed to find libs: " ^ m)
422422+ in
423423+424424+ (* Remove duplicates and sort *)
425425+ let all_packages = Util.StringSet.(of_list all_packages |> to_list) in
426426+427427+ Format.eprintf "Generating universes for %d packages\n%!" (List.length all_packages);
428428+429429+ Eio_main.run @@ fun env ->
430430+ Eio.Switch.run @@ fun sw ->
431431+ if verbose then Logs.set_level (Some Logs.Debug) else Logs.set_level None;
432432+ Logs.set_reporter (Logs_fmt.reporter ());
433433+ let () = Worker_pool.start_workers env sw 16 in
434434+435435+ let output_dir = Fpath.v output_dir_str in
436436+ let _ = Bos.OS.Dir.create ~path:true output_dir in
437437+ let findlib_dir = Ocamlfind.findlib_dir () |> Fpath.v in
438438+439439+ (* Build dependency map: package -> list of direct dependency paths *)
440440+ let dep_map = Hashtbl.create 64 in
441441+ List.iter (fun pkg ->
442442+ let deps = match Ocamlfind.deps [pkg] with
443443+ | Ok l -> List.filter (fun d -> d <> pkg) l (* Remove self from deps *)
444444+ | Error _ -> []
445445+ in
446446+ Hashtbl.add dep_map pkg deps)
447447+ all_packages;
448448+449449+ (* Generate each package and collect results *)
450450+ let pkg_results = List.map (fun pkg ->
451451+ Format.eprintf "Generating %s...\n%!" pkg;
452452+ let pkg_deps = Hashtbl.find dep_map pkg in
453453+ generate_package_universe ~switch ~output_dir ~findlib_dir ~pkg ~pkg_deps)
454454+ all_packages
455455+ in
456456+457457+ (* Build a map from package name to full META path *)
458458+ let meta_path_map = Hashtbl.create 64 in
459459+ List.iter (fun (pkg_path, local_meta_path, _deps) ->
460460+ let full_meta_path = pkg_path ^ "/" ^ local_meta_path in
461461+ Hashtbl.add meta_path_map pkg_path full_meta_path)
462462+ pkg_results;
463463+464464+ (* Generate findlib_index for each package with correct META paths *)
465465+ List.iter (fun (pkg_path, local_meta_path, deps) ->
466466+ let this_meta = pkg_path ^ "/" ^ local_meta_path in
467467+ let dep_metas = List.filter_map (fun dep ->
468468+ match Hashtbl.find_opt meta_path_map dep with
469469+ | Some path -> Some path
470470+ | None ->
471471+ Format.eprintf "Warning: no META path found for dep %s\n%!" dep;
472472+ None)
473473+ deps
474474+ in
475475+ let all_metas = this_meta :: dep_metas in
476476+ let findlib_json = `Assoc [("metas", `List (List.map (fun s -> `String s) all_metas))] in
477477+ Out_channel.with_open_bin Fpath.(output_dir / pkg_path / "findlib_index" |> to_string)
478478+ (fun oc -> Printf.fprintf oc "%s\n" (Yojson.Safe.to_string findlib_json)))
479479+ pkg_results;
480480+481481+ (* Generate root findlib_index with all META paths *)
482482+ let all_metas = List.map (fun (pkg_path, local_meta_path, _) ->
483483+ pkg_path ^ "/" ^ local_meta_path)
484484+ pkg_results
485485+ in
486486+ let root_index = `Assoc [("metas", `List (List.map (fun s -> `String s) all_metas))] in
487487+ Out_channel.with_open_bin Fpath.(output_dir / "findlib_index" |> to_string)
488488+ (fun oc -> Printf.fprintf oc "%s\n" (Yojson.Safe.to_string root_index));
489489+490490+ Format.eprintf "Generated root findlib_index with %d META files\n%!" (List.length pkg_results);
491491+492492+ (* Generate worker.js if requested *)
493493+ let () = if no_worker then () else Mk_backend.mk switch output_dir in
494494+495495+ `Ok ()
496496+497497+open Cmdliner
498498+499499+let opam_cmd =
500500+ let libraries = Arg.(value & pos_all string [] & info [] ~docv:"LIB") in
501501+ let output_dir =
502502+ let doc =
503503+ "Output directory in which to put all outputs. This should be the root \
504504+ directory of the HTTP server. Ignored when --path is specified."
505505+ in
506506+ Arg.(value & opt string "html" & info [ "o"; "output" ] ~doc)
507507+ in
508508+ let verbose =
509509+ let doc = "Enable verbose logging" in
510510+ Arg.(value & flag & info [ "v"; "verbose" ] ~doc) in
511511+ let no_worker =
512512+ let doc = "Do not create worker.js" in
513513+ Arg.(value & flag & info [ "no-worker" ] ~doc)
514514+ in
515515+ let switch =
516516+ let doc = "Opam switch to use" in
517517+ Arg.(value & opt (some string) None & info [ "switch" ] ~doc)
518518+ in
519519+ let path =
520520+ let doc =
521521+ "Full output path for this package (e.g., universes/abc123/base/v0.17.1/). \
522522+ When specified, only the named packages are compiled (not dependencies)."
523523+ in
524524+ Arg.(value & opt (some string) None & info [ "path" ] ~doc)
525525+ in
526526+ let deps_file =
527527+ let doc =
528528+ "File containing dependency paths, one per line. Each path should be \
529529+ relative to the HTTP root (e.g., universes/xyz789/sexplib0/v0.17.0/)."
530530+ in
531531+ Arg.(value & opt (some string) None & info [ "deps-file" ] ~doc)
532532+ in
533533+ let info = Cmd.info "opam" ~doc:"Generate opam files" in
534534+ Cmd.v info
535535+ Term.(ret (const opam $ verbose $ output_dir $ switch $ libraries $ no_worker $ path $ deps_file))
536536+537537+let opam_all_cmd =
538538+ let libraries = Arg.(value & pos_all string [] & info [] ~docv:"LIB") in
539539+ let output_dir =
540540+ let doc =
541541+ "Output directory for all universes. Each package gets its own subdirectory."
542542+ in
543543+ Arg.(value & opt string "html" & info [ "o"; "output" ] ~doc)
544544+ in
545545+ let verbose =
546546+ let doc = "Enable verbose logging" in
547547+ Arg.(value & flag & info [ "v"; "verbose" ] ~doc)
548548+ in
549549+ let no_worker =
550550+ let doc = "Do not create worker.js" in
551551+ Arg.(value & flag & info [ "no-worker" ] ~doc)
552552+ in
553553+ let switch =
554554+ let doc = "Opam switch to use" in
555555+ Arg.(value & opt (some string) None & info [ "switch" ] ~doc)
556556+ in
557557+ let all_pkgs =
558558+ let doc = "Build all installed packages (from ocamlfind list)" in
559559+ Arg.(value & flag & info [ "all" ] ~doc)
560560+ in
561561+ let info = Cmd.info "opam-all" ~doc:"Generate universes for all packages and their dependencies" in
562562+ Cmd.v info
563563+ Term.(ret (const opam_all $ verbose $ output_dir $ switch $ libraries $ no_worker $ all_pkgs))
564564+565565+let main_cmd =
566566+ let doc = "An odoc notebook tool" in
567567+ let info = Cmd.info "odoc-notebook" ~version:"%%VERSION%%" ~doc in
568568+ let default = Term.(ret (const (`Help (`Pager, None)))) in
569569+ Cmd.group info ~default [ opam_cmd; opam_all_cmd ]
570570+571571+let () = exit (Cmd.eval main_cmd)
+54
js_top_worker/bin/mk_backend.ml
···11+(* To make a toplevel backend.js *)
22+33+let mk switch dir =
44+ let txt = {|let _ = Js_top_worker_web.Worker.run ()|} in
55+ let file = Fpath.(dir / "worker.ml") in
66+ Util.write_file file [ txt ];
77+ let ocamlfind_cmd, js_of_ocaml_cmd =
88+ match switch with
99+ | None -> (Bos.Cmd.(v "ocamlfind"), Bos.Cmd.(v "js_of_ocaml"))
1010+ | Some s ->
1111+ ( Bos.Cmd.(v "opam" % "exec" % "--switch" % s % "--" % "ocamlfind"),
1212+ Bos.Cmd.(v "opam" % "exec" % "--switch" % s % "--" % "js_of_ocaml") )
1313+ in
1414+ let cmd =
1515+ Bos.Cmd.(
1616+ ocamlfind_cmd % "ocamlc" % "-package" % "js_of_ocaml-ppx.as-lib"
1717+ % "-package" % "js_top_worker-web")
1818+ in
1919+ let cmd = Bos.Cmd.(cmd % "-linkpkg" % "-linkall" % Fpath.to_string file) in
2020+ let cmd =
2121+ Bos.Cmd.(cmd % "-g" % "-o" % Fpath.(dir / "worker.bc" |> to_string))
2222+ in
2323+ let _ = Util.lines_of_process cmd in
2424+ (* No longer query library stubs - they are now linked directly into each library's JS file *)
2525+ let cmd =
2626+ Bos.Cmd.(
2727+ js_of_ocaml_cmd % "--toplevel" % "--no-cmis" % "--linkall" % "--pretty")
2828+ in
2929+ let cmd =
3030+ List.fold_right
3131+ (fun a cmd -> Bos.Cmd.(cmd % a))
3232+ [
3333+ "+dynlink.js";
3434+ "+toplevel.js";
3535+ "+bigstringaf/runtime.js";
3636+ "+js_top_worker/stubs.js";
3737+ ]
3838+ cmd
3939+ in
4040+ let cmd =
4141+ Bos.Cmd.(
4242+ cmd
4343+ % Fpath.(dir / "worker.bc" |> to_string)
4444+ % "-o"
4545+ % Fpath.(dir / "worker.js" |> to_string))
4646+ in
4747+ Logs.info (fun m -> m "cmd: %s" (Bos.Cmd.to_string cmd));
4848+ let _ = Util.lines_of_process cmd in
4949+ let to_delete = [ "worker.bc"; "worker.ml"; "worker.cmi"; "worker.cmo" ] in
5050+ let results =
5151+ List.map (fun f -> Bos.OS.File.delete Fpath.(dir / f)) to_delete
5252+ in
5353+ ignore results;
5454+ ()
+80
js_top_worker/bin/ocamlfind.ml
···11+let init =
22+ let initialized = ref false in
33+ fun () ->
44+ if !initialized then ()
55+ else
66+ let prefix = Opam.prefix () in
77+ let env_camllib = Fpath.(v prefix / "lib" / "ocaml" |> to_string) in
88+ let config = Fpath.(v prefix / "lib" / "findlib.conf" |> to_string) in
99+ Findlib.init ~config ~env_camllib ()
1010+1111+let all () =
1212+ init ();
1313+ Fl_package_base.list_packages ()
1414+1515+let get_dir lib =
1616+ try
1717+ init ();
1818+ Fl_package_base.query lib |> fun x ->
1919+ Logs.debug (fun m -> m "Package %s is in directory %s@." lib x.package_dir);
2020+ Ok Fpath.(v x.package_dir |> to_dir_path)
2121+ with e ->
2222+ Printf.eprintf "Error: %s\n" (Printexc.to_string e);
2323+ Error (`Msg "Error getting directory")
2424+2525+let findlib_dir () = Findlib.default_location ()
2626+2727+let archives pkg =
2828+ init ();
2929+ let package = Fl_package_base.query pkg in
3030+ let get_1 preds =
3131+ try
3232+ [
3333+ Fl_metascanner.lookup "archive" preds
3434+ package.Fl_package_base.package_defs;
3535+ ]
3636+ with _ -> []
3737+ in
3838+ match pkg with
3939+ | "stdlib" -> [ "stdlib.cma" ]
4040+ | _ ->
4141+ get_1 [ "byte" ] @ get_1 [ "byte"; "ppx_driver" ]
4242+ |> List.filter (fun x -> String.length x > 0)
4343+ |> List.sort_uniq String.compare
4444+4545+let sub_libraries top =
4646+ init ();
4747+ let packages = Fl_package_base.list_packages () in
4848+ List.fold_left
4949+ (fun acc lib ->
5050+ let package = String.split_on_char '.' lib |> List.hd in
5151+ if package = top then Util.StringSet.add lib acc else acc)
5252+ Util.StringSet.empty packages
5353+5454+let deps pkgs =
5555+ init ();
5656+ try
5757+ let packages =
5858+ Fl_package_base.requires_deeply ~preds:[ "ppx_driver"; "byte" ] pkgs
5959+ in
6060+ Ok packages
6161+ with e -> Error (`Msg (Printexc.to_string e))
6262+6363+let meta_file pkg =
6464+ init ();
6565+ let package = Fl_package_base.query pkg in
6666+ let meta = package.Fl_package_base.package_meta in
6767+ meta
6868+6969+let jsoo_runtime pkg =
7070+ init ();
7171+ let package = Fl_package_base.query pkg in
7272+ try
7373+ let runtime =
7474+ Fl_metascanner.lookup "jsoo_runtime" []
7575+ package.Fl_package_base.package_defs
7676+ in
7777+ (* Runtime may be space-separated list of files *)
7878+ String.split_on_char ' ' runtime
7979+ |> List.filter (fun x -> String.length x > 0)
8080+ with _ -> []
+170
js_top_worker/bin/opam.ml
···11+open Bos
22+33+let opam = Cmd.v "opam"
44+let switch = ref None
55+let prefix = ref None
66+77+type package = { name : string; version : string }
88+99+let pp fmt p = Format.fprintf fmt "%s.%s" p.name p.version
1010+1111+let rec get_switch () =
1212+ match !switch with
1313+ | None ->
1414+ let cur_switch =
1515+ Util.lines_of_process Cmd.(opam % "switch" % "show") |> List.hd
1616+ in
1717+ switch := Some cur_switch;
1818+ get_switch ()
1919+ | Some s -> s
2020+2121+let prefix () =
2222+ match !prefix with
2323+ | Some p -> p
2424+ | None ->
2525+ let p =
2626+ Util.lines_of_process
2727+ Cmd.(opam % "var" % "--switch" % get_switch () % "prefix")
2828+ |> List.hd
2929+ in
3030+ prefix := Some p;
3131+ p
3232+3333+let deps_of_opam_result line =
3434+ match Astring.String.fields ~empty:false line with
3535+ | [ name; version ] -> [ { name; version } ]
3636+ | _ -> []
3737+3838+let all_opam_packages () =
3939+ Util.lines_of_process
4040+ Cmd.(
4141+ opam % "list" % "--switch" % get_switch () % "--columns=name,version"
4242+ % "--color=never" % "--short")
4343+ |> List.map deps_of_opam_result
4444+ |> List.flatten
4545+4646+let pkg_contents { name; _ } =
4747+ let prefix = Fpath.v (prefix ()) in
4848+ let changes_file =
4949+ Format.asprintf "%a/.opam-switch/install/%s.changes" Fpath.pp prefix name
5050+ in
5151+ let file = OpamFilename.raw changes_file in
5252+ let filename =
5353+ OpamFile.make @@ OpamFilename.raw @@ Filename.basename changes_file
5454+ in
5555+ let changed =
5656+ OpamFilename.with_contents
5757+ (fun str ->
5858+ OpamFile.Changes.read_from_string ~filename
5959+ @@
6060+ (* Field [opam-version] is invalid in [*.changes] files, displaying a warning. *)
6161+ if String.starts_with ~prefix:"opam-version" str then
6262+ match OpamStd.String.cut_at str '\n' with
6363+ | Some (_, str) -> str
6464+ | None -> assert false
6565+ else str)
6666+ file
6767+ in
6868+ let added =
6969+ OpamStd.String.Map.fold
7070+ (fun file x acc ->
7171+ match x with
7272+ | OpamDirTrack.Added _ -> (
7373+ try
7474+ if not @@ Sys.is_directory Fpath.(to_string (prefix // v file))
7575+ then file :: acc
7676+ else acc
7777+ with _ ->
7878+ acc
7979+ (* dose (and maybe others) sometimes creates a symlink to something that doesn't exist *)
8080+ )
8181+ | _ -> acc)
8282+ changed []
8383+ in
8484+ List.map Fpath.v added
8585+8686+(* let opam_file { name; version } = *)
8787+(* let prefix = Fpath.v (prefix ()) in *)
8888+(* let opam_file = *)
8989+(* Format.asprintf "%a/.opam-switch/packages/%s.%s/opam" Fpath.pp prefix name *)
9090+(* version *)
9191+(* in *)
9292+(* let ic = open_in opam_file in *)
9393+(* try *)
9494+(* let lines = Util.lines_of_channel ic in *)
9595+(* close_in ic; *)
9696+(* Some lines *)
9797+(* with _ -> *)
9898+(* close_in ic; *)
9999+(* None *)
100100+101101+type installed_files = {
102102+ libs : Fpath.set;
103103+ odoc_pages : Fpath.set;
104104+ other_docs : Fpath.set;
105105+}
106106+107107+type package_of_fpath = package Fpath.map
108108+109109+(* Here we use an associative list *)
110110+type fpaths_of_package = (package * installed_files) list
111111+112112+let pkg_to_dir_map () =
113113+ let pkgs = all_opam_packages () in
114114+ let prefix = prefix () in
115115+ let pkg_content =
116116+ List.map
117117+ (fun p ->
118118+ let contents = pkg_contents p in
119119+ let libs =
120120+ List.fold_left
121121+ (fun set fpath ->
122122+ match Fpath.segs fpath with
123123+ | "lib" :: "stublibs" :: _ -> set
124124+ | "lib" :: _ :: _ :: _ when Fpath.has_ext ".cmi" fpath ->
125125+ Fpath.Set.add
126126+ Fpath.(v prefix // fpath |> split_base |> fst)
127127+ set
128128+ | _ -> set)
129129+ Fpath.Set.empty contents
130130+ in
131131+ let odoc_pages, other_docs =
132132+ List.fold_left
133133+ (fun (odoc_pages, others) fpath ->
134134+ match Fpath.segs fpath with
135135+ | "doc" :: _pkg :: "odoc-pages" :: _ ->
136136+ Logs.debug (fun m -> m "Found odoc page: %a" Fpath.pp fpath);
137137+138138+ (Fpath.Set.add Fpath.(v prefix // fpath) odoc_pages, others)
139139+ | "doc" :: _ ->
140140+ Logs.debug (fun m -> m "Found other doc: %a" Fpath.pp fpath);
141141+ (odoc_pages, Fpath.Set.add Fpath.(v prefix // fpath) others)
142142+ | _ -> (odoc_pages, others))
143143+ Fpath.Set.(empty, empty)
144144+ contents
145145+ in
146146+ Logs.debug (fun m ->
147147+ m "Found %d odoc pages, %d other docs"
148148+ (Fpath.Set.cardinal odoc_pages)
149149+ (Fpath.Set.cardinal other_docs));
150150+ (p, { libs; odoc_pages; other_docs }))
151151+ pkgs
152152+ in
153153+ let map =
154154+ List.fold_left
155155+ (fun map (p, { libs; _ }) ->
156156+ Fpath.Set.fold
157157+ (fun dir map ->
158158+ Fpath.Map.update dir
159159+ (function
160160+ | None -> Some p
161161+ | Some x ->
162162+ Logs.debug (fun m ->
163163+ m "Multiple packages (%a,%a) found for dir %a" pp x pp p
164164+ Fpath.pp dir);
165165+ Some p)
166166+ map)
167167+ libs map)
168168+ Fpath.Map.empty pkg_content
169169+ in
170170+ (pkg_content, map)
+116
js_top_worker/bin/run.ml
···11+let instrument = false
22+33+open Bos
44+55+let instrument_dir =
66+ lazy
77+ (let dir = Fpath.v "landmarks" in
88+ OS.Dir.delete dir |> Result.get_ok;
99+ OS.Dir.create dir |> Result.get_ok |> ignore;
1010+ dir)
1111+1212+type t = {
1313+ cmd : string list;
1414+ time : float; (** Running time in seconds. *)
1515+ output_file : Fpath.t option;
1616+ output : string;
1717+ errors : string;
1818+ status : [ `Exited of int | `Signaled of int ];
1919+}
2020+2121+(* Environment variables passed to commands. *)
2222+2323+(* Record the commands executed, their running time and optionally the path to
2424+ the produced file. *)
2525+let commands = ref []
2626+let n = Atomic.make 0
2727+2828+(** Return the list of executed commands where the first argument was [cmd]. *)
2929+let run env cmd output_file =
3030+ let cmd = Bos.Cmd.to_list cmd in
3131+ let myn = Atomic.fetch_and_add n 1 in
3232+ Logs.debug (fun m -> m "%d - Executing: %s" myn (String.concat " " cmd));
3333+ let proc_mgr = Eio.Stdenv.process_mgr env in
3434+ let t_start = Unix.gettimeofday () in
3535+ let env =
3636+ let env = OS.Env.current () |> Result.get_ok in
3737+ env
3838+ in
3939+ let env =
4040+ Astring.String.Map.fold
4141+ (fun k v env -> Astring.String.concat [ k; "="; v ] :: env)
4242+ env []
4343+ |> Array.of_list
4444+ in
4545+ (* Logs.debug (fun m -> m "Running cmd %a" Fmt.(list ~sep:sp string) cmd); *)
4646+ let output, errors, status =
4747+ Eio.Switch.run ~name:"Process.parse_out" @@ fun sw ->
4848+ let r, w = Eio.Process.pipe proc_mgr ~sw in
4949+ let re, we = Eio.Process.pipe proc_mgr ~sw in
5050+ try
5151+ let child =
5252+ Eio.Process.spawn ~sw proc_mgr ~stdout:w ~stderr:we ~env cmd
5353+ in
5454+ Eio.Flow.close w;
5555+ Eio.Flow.close we;
5656+ let output, err =
5757+ Eio.Fiber.pair
5858+ (fun () ->
5959+ Eio.Buf_read.parse_exn Eio.Buf_read.take_all r ~max_size:max_int)
6060+ (fun () ->
6161+ Eio.Buf_read.parse_exn Eio.Buf_read.take_all re ~max_size:max_int)
6262+ in
6363+ Eio.Flow.close r;
6464+ Eio.Flow.close re;
6565+ let status = Eio.Process.await child in
6666+ (output, err, status)
6767+ with Eio.Exn.Io _ as ex ->
6868+ let bt = Printexc.get_raw_backtrace () in
6969+ Eio.Exn.reraise_with_context ex bt "%d - running command: %a" myn
7070+ Eio.Process.pp_args cmd
7171+ in
7272+ (* Logs.debug (fun m ->
7373+ m "Finished running cmd %a" Fmt.(list ~sep:sp string) cmd); *)
7474+ let t_end = Unix.gettimeofday () in
7575+ let time = t_end -. t_start in
7676+ let result = { cmd; time; output_file; output; errors; status } in
7777+ commands := result :: !commands;
7878+ (match result.status with
7979+ | `Exited 0 -> ()
8080+ | _ ->
8181+ let verb, n =
8282+ match result.status with
8383+ | `Exited n -> ("exited", n)
8484+ | `Signaled n -> ("signaled", n)
8585+ in
8686+ Logs.err (fun m ->
8787+ m
8888+ "@[<2>Process %s with %d:@ '@[%a'@]@]@\n\n\
8989+ Stdout:\n\
9090+ %s\n\n\
9191+ Stderr:\n\
9292+ %s"
9393+ verb n
9494+ Fmt.(list ~sep:sp string)
9595+ result.cmd result.output result.errors));
9696+ result
9797+9898+(** Print an executed command and its time. *)
9999+100100+let filter_commands cmd =
101101+ match
102102+ List.filter
103103+ (fun c -> match c.cmd with _ :: cmd' :: _ -> cmd = cmd' | _ -> false)
104104+ !commands
105105+ with
106106+ | [] -> []
107107+ | _ :: _ as cmds -> cmds
108108+109109+let print_cmd c =
110110+ Printf.printf "[%4.2f] $ %s\n" c.time (String.concat " " c.cmd)
111111+112112+(** Returns the [k] commands that took the most time for a given subcommand. *)
113113+let k_longest_commands cmd k =
114114+ filter_commands cmd
115115+ |> List.sort (fun a b -> Float.compare b.time a.time)
116116+ |> List.filteri (fun i _ -> i < k)
+47
js_top_worker/bin/util.ml
···11+open Bos
22+module StringSet = Set.Make (String)
33+module StringMap = Map.Make (String)
44+55+let lines_of_channel ic =
66+ let rec inner acc =
77+ try
88+ let l = input_line ic in
99+ inner (l :: acc)
1010+ with End_of_file -> List.rev acc
1111+ in
1212+ inner []
1313+1414+let lines_of_process cmd =
1515+ match OS.Cmd.(run_out ~err:err_null cmd |> to_lines) with
1616+ | Ok x -> x
1717+ | Error (`Msg e) -> failwith ("Error: " ^ e)
1818+1919+let mkdir_p d =
2020+ let segs =
2121+ Fpath.segs (Fpath.normalize d) |> List.filter (fun s -> String.length s > 0)
2222+ in
2323+ let _ =
2424+ List.fold_left
2525+ (fun path seg ->
2626+ let d = Fpath.(path // v seg) in
2727+ try
2828+ Unix.mkdir (Fpath.to_string d) 0o755;
2929+ d
3030+ with
3131+ | Unix.Unix_error (Unix.EEXIST, _, _) -> d
3232+ | exn -> raise exn)
3333+ (Fpath.v ".") segs
3434+ in
3535+ ()
3636+3737+let write_file filename lines =
3838+ let dir = fst (Fpath.split_base filename) in
3939+ mkdir_p dir;
4040+ let oc = open_out (Fpath.to_string filename) in
4141+ List.iter (fun line -> Printf.fprintf oc "%s\n" line) lines;
4242+ close_out oc
4343+4444+let cp src dst =
4545+ assert (
4646+ lines_of_process Cmd.(v "cp" % Fpath.to_string src % Fpath.to_string dst)
4747+ = [])
+47
js_top_worker/bin/worker_pool.ml
···11+(* Worker pool *)
22+open Eio
33+44+type request = {
55+ description : string;
66+ request : Bos.Cmd.t;
77+ output_file : Fpath.t option;
88+}
99+1010+type response = (Run.t, exn) result
1111+type resolver = response Eio.Promise.u
1212+type t = (request * resolver) Eio.Stream.t
1313+1414+let stream : t = Eio.Stream.create 0
1515+let handle_job env request output_file = Run.run env request output_file
1616+1717+exception Worker_failure of Run.t
1818+1919+let rec run_worker env id : unit =
2020+ let { request; output_file; description = _ }, reply =
2121+ Eio.Stream.take stream
2222+ in
2323+ (try
2424+ let result = handle_job env request output_file in
2525+ match result.status with
2626+ | `Exited 0 -> Promise.resolve reply (Ok result)
2727+ | _ -> Promise.resolve_error reply (Worker_failure result)
2828+ with e -> Promise.resolve_error reply e);
2929+ run_worker env id
3030+3131+let submit description request output_file =
3232+ let reply, resolve_reply = Promise.create () in
3333+ Eio.Stream.add stream ({ description; request; output_file }, resolve_reply);
3434+ Promise.await reply
3535+3636+let start_workers env sw n =
3737+ let spawn_worker name =
3838+ Fiber.fork_daemon ~sw (fun () ->
3939+ try
4040+ run_worker env name;
4141+ `Stop_daemon
4242+ with Stdlib.Exit -> `Stop_daemon)
4343+ in
4444+ for i = 0 to n - 1 do
4545+ spawn_worker i
4646+ done;
4747+ ()
+203
js_top_worker/client/ocaml-worker.d.ts
···11+/**
22+ * OCaml Worker Client TypeScript Declarations
33+ */
44+55+export interface InitConfig {
66+ /** Findlib packages to require */
77+ findlib_requires: string[];
88+ /** URL to dynamic CMIs for stdlib */
99+ stdlib_dcs?: string;
1010+ /** URL to findlib_index file */
1111+ findlib_index?: string;
1212+}
1313+1414+export interface Position {
1515+ /** Character number */
1616+ pos_cnum: number;
1717+ /** Line number */
1818+ pos_lnum: number;
1919+ /** Beginning of line offset */
2020+ pos_bol: number;
2121+}
2222+2323+export interface Location {
2424+ /** Start position */
2525+ loc_start: Position;
2626+ /** End position */
2727+ loc_end: Position;
2828+}
2929+3030+export interface MimeVal {
3131+ /** MIME type */
3232+ mime_type: string;
3333+ /** Data content */
3434+ data: string;
3535+}
3636+3737+export interface Output {
3838+ /** Cell identifier */
3939+ cell_id: number;
4040+ /** Standard output */
4141+ stdout: string;
4242+ /** Standard error */
4343+ stderr: string;
4444+ /** OCaml pretty-printed output */
4545+ caml_ppf: string;
4646+ /** MIME values */
4747+ mime_vals: MimeVal[];
4848+}
4949+5050+export interface CompletionEntry {
5151+ /** Completion name */
5252+ name: string;
5353+ /** Kind (Value, Module, Type, etc.) */
5454+ kind: string;
5555+ /** Description */
5656+ desc: string;
5757+ /** Additional info */
5858+ info: string;
5959+ /** Whether deprecated */
6060+ deprecated: boolean;
6161+}
6262+6363+export interface Completions {
6464+ /** Cell identifier */
6565+ cell_id: number;
6666+ /** Completions data */
6767+ completions: {
6868+ /** Start position */
6969+ from: number;
7070+ /** End position */
7171+ to: number;
7272+ /** Completion entries */
7373+ entries: CompletionEntry[];
7474+ };
7575+}
7676+7777+export interface Error {
7878+ /** Error kind */
7979+ kind: string;
8080+ /** Error location */
8181+ loc: Location;
8282+ /** Main error message */
8383+ main: string;
8484+ /** Sub-messages */
8585+ sub: string[];
8686+ /** Error source */
8787+ source: string;
8888+}
8989+9090+export interface ErrorList {
9191+ /** Cell identifier */
9292+ cell_id: number;
9393+ /** Errors */
9494+ errors: Error[];
9595+}
9696+9797+export interface TypeInfo {
9898+ /** Type location */
9999+ loc: Location;
100100+ /** Type string */
101101+ type_str: string;
102102+ /** Tail position info */
103103+ tail: string;
104104+}
105105+106106+export interface TypesResult {
107107+ /** Cell identifier */
108108+ cell_id: number;
109109+ /** Type information */
110110+ types: TypeInfo[];
111111+}
112112+113113+export interface EnvResult {
114114+ /** Environment ID */
115115+ env_id: string;
116116+}
117117+118118+export interface OutputAt {
119119+ /** Cell identifier */
120120+ cell_id: number;
121121+ /** Character position after phrase (pos_cnum) */
122122+ loc: number;
123123+ /** OCaml pretty-printed output for this phrase */
124124+ caml_ppf: string;
125125+ /** MIME values for this phrase */
126126+ mime_vals: MimeVal[];
127127+}
128128+129129+export interface OcamlWorkerOptions {
130130+ /** Timeout in milliseconds (default: 30000) */
131131+ timeout?: number;
132132+ /** Callback for incremental output after each phrase */
133133+ onOutputAt?: (output: OutputAt) => void;
134134+}
135135+136136+export class OcamlWorker {
137137+ /**
138138+ * Create a new OCaml worker client.
139139+ * @param workerUrl - URL to the worker script
140140+ * @param options - Options
141141+ */
142142+ constructor(workerUrl: string, options?: OcamlWorkerOptions);
143143+144144+ /**
145145+ * Initialize the worker.
146146+ * @param config - Initialization configuration
147147+ */
148148+ init(config: InitConfig): Promise<void>;
149149+150150+ /**
151151+ * Wait for the worker to be ready.
152152+ */
153153+ waitReady(): Promise<void>;
154154+155155+ /**
156156+ * Evaluate OCaml code.
157157+ * @param code - OCaml code to evaluate
158158+ * @param envId - Environment ID (default: 'default')
159159+ */
160160+ eval(code: string, envId?: string): Promise<Output>;
161161+162162+ /**
163163+ * Get completions at a position.
164164+ * @param source - Source code
165165+ * @param position - Cursor position (character offset)
166166+ * @param envId - Environment ID (default: 'default')
167167+ */
168168+ complete(source: string, position: number, envId?: string): Promise<Completions>;
169169+170170+ /**
171171+ * Get type information at a position.
172172+ * @param source - Source code
173173+ * @param position - Cursor position (character offset)
174174+ * @param envId - Environment ID (default: 'default')
175175+ */
176176+ typeAt(source: string, position: number, envId?: string): Promise<TypesResult>;
177177+178178+ /**
179179+ * Get errors for source code.
180180+ * @param source - Source code
181181+ * @param envId - Environment ID (default: 'default')
182182+ */
183183+ errors(source: string, envId?: string): Promise<ErrorList>;
184184+185185+ /**
186186+ * Create a new execution environment.
187187+ * @param envId - Environment ID
188188+ */
189189+ createEnv(envId: string): Promise<EnvResult>;
190190+191191+ /**
192192+ * Destroy an execution environment.
193193+ * @param envId - Environment ID
194194+ */
195195+ destroyEnv(envId: string): Promise<EnvResult>;
196196+197197+ /**
198198+ * Terminate the worker.
199199+ */
200200+ terminate(): void;
201201+}
202202+203203+export default OcamlWorker;
+446
js_top_worker/client/ocaml-worker.js
···11+/**
22+ * OCaml Worker Client
33+ *
44+ * A JavaScript client library for communicating with the OCaml toplevel web worker.
55+ *
66+ * @example
77+ * ```javascript
88+ * import { OcamlWorker } from './ocaml-worker.js';
99+ *
1010+ * const worker = new OcamlWorker('worker.js');
1111+ *
1212+ * await worker.init({
1313+ * findlib_requires: [],
1414+ * findlib_index: 'findlib_index'
1515+ * });
1616+ *
1717+ * const result = await worker.eval('let x = 1 + 2;;');
1818+ * console.log(result.caml_ppf); // "val x : int = 3"
1919+ * ```
2020+ */
2121+2222+/**
2323+ * @typedef {Object} InitConfig
2424+ * @property {string[]} findlib_requires - Findlib packages to require
2525+ * @property {string} [stdlib_dcs] - URL to dynamic CMIs for stdlib
2626+ * @property {string} [findlib_index] - URL to findlib_index file
2727+ */
2828+2929+/**
3030+ * @typedef {Object} Position
3131+ * @property {number} pos_cnum - Character number
3232+ * @property {number} pos_lnum - Line number
3333+ * @property {number} pos_bol - Beginning of line offset
3434+ */
3535+3636+/**
3737+ * @typedef {Object} Location
3838+ * @property {Position} loc_start - Start position
3939+ * @property {Position} loc_end - End position
4040+ */
4141+4242+/**
4343+ * @typedef {Object} MimeVal
4444+ * @property {string} mime_type - MIME type
4545+ * @property {string} data - Data content
4646+ */
4747+4848+/**
4949+ * @typedef {Object} Output
5050+ * @property {number} cell_id - Cell identifier
5151+ * @property {string} stdout - Standard output
5252+ * @property {string} stderr - Standard error
5353+ * @property {string} caml_ppf - OCaml pretty-printed output
5454+ * @property {MimeVal[]} mime_vals - MIME values
5555+ */
5656+5757+/**
5858+ * @typedef {Object} CompletionEntry
5959+ * @property {string} name - Completion name
6060+ * @property {string} kind - Kind (Value, Module, Type, etc.)
6161+ * @property {string} desc - Description
6262+ * @property {string} info - Additional info
6363+ * @property {boolean} deprecated - Whether deprecated
6464+ */
6565+6666+/**
6767+ * @typedef {Object} Completions
6868+ * @property {number} cell_id - Cell identifier
6969+ * @property {Object} completions - Completions data
7070+ * @property {number} completions.from - Start position
7171+ * @property {number} completions.to - End position
7272+ * @property {CompletionEntry[]} completions.entries - Completion entries
7373+ */
7474+7575+/**
7676+ * @typedef {Object} Error
7777+ * @property {string} kind - Error kind
7878+ * @property {Location} loc - Error location
7979+ * @property {string} main - Main error message
8080+ * @property {string[]} sub - Sub-messages
8181+ * @property {string} source - Error source
8282+ */
8383+8484+/**
8585+ * @typedef {Object} TypeInfo
8686+ * @property {Location} loc - Type location
8787+ * @property {string} type_str - Type string
8888+ * @property {string} tail - Tail position info
8989+ */
9090+9191+/**
9292+ * @typedef {Object} OutputAt
9393+ * @property {number} cell_id - Cell identifier
9494+ * @property {number} loc - Character position after phrase (pos_cnum)
9595+ * @property {string} caml_ppf - OCaml pretty-printed output for this phrase
9696+ * @property {MimeVal[]} mime_vals - MIME values for this phrase
9797+ */
9898+9999+export class OcamlWorker {
100100+ /**
101101+ * Create the worker blob URL with proper base URL setup.
102102+ * The worker needs __global_rel_url to find its resources.
103103+ * @private
104104+ */
105105+ static _createWorkerUrl(baseUrl) {
106106+ // Convert relative URL to absolute - importScripts in blob workers needs absolute URLs
107107+ const absoluteBase = new URL(baseUrl, window.location.href).href;
108108+ // Remove the trailing /worker.js to get the base directory
109109+ const baseDir = absoluteBase.replace(/\/worker\.js$/, '');
110110+ const content = `globalThis.__global_rel_url="${baseDir}"\nimportScripts("${absoluteBase}");`;
111111+ return URL.createObjectURL(new Blob([content], { type: "text/javascript" }));
112112+ }
113113+114114+ /**
115115+ * Create a worker from a findlib_index URL.
116116+ * The findlib_index JSON contains compiler info (version, content_hash) and
117117+ * META file paths. This is the single entry point for discovery.
118118+ * @param {string} indexUrl - URL to findlib_index (e.g., '/jtw-output/u/<hash>/findlib_index')
119119+ * @param {string} baseOutputUrl - Base URL of the jtw-output directory (e.g., '/jtw-output')
120120+ * @param {Object} [options] - Options passed to OcamlWorker constructor
121121+ * @returns {Promise<{worker: OcamlWorker, findlib_index: string, stdlib_dcs: string}>}
122122+ */
123123+ static async fromIndex(indexUrl, baseOutputUrl, options = {}) {
124124+ const resp = await fetch(indexUrl);
125125+ if (!resp.ok) throw new Error(`Failed to fetch findlib_index: ${resp.status}`);
126126+ const index = await resp.json();
127127+ const compiler = index.compiler;
128128+ if (!compiler) throw new Error('No compiler info in findlib_index');
129129+ const ver = compiler.version;
130130+ const hash = compiler.content_hash;
131131+ const workerUrl = `${baseOutputUrl}/compiler/${ver}/${hash}/worker.js`;
132132+ const worker = new OcamlWorker(workerUrl, options);
133133+ return { worker, findlib_index: indexUrl, stdlib_dcs: 'lib/ocaml/dynamic_cmis.json' };
134134+ }
135135+136136+ /**
137137+ * Create a new OCaml worker client.
138138+ * @param {string} workerUrl - URL to the worker script (e.g., '_opam/worker.js')
139139+ * @param {Object} [options] - Options
140140+ * @param {number} [options.timeout=30000] - Timeout in milliseconds
141141+ * @param {function(OutputAt): void} [options.onOutputAt] - Callback for incremental output
142142+ */
143143+ constructor(workerUrl, options = {}) {
144144+ const blobUrl = OcamlWorker._createWorkerUrl(workerUrl);
145145+ this.worker = new Worker(blobUrl);
146146+ this.timeout = options.timeout || 30000;
147147+ this.onOutputAt = options.onOutputAt || null;
148148+ this.cellIdCounter = 0;
149149+ this.pendingRequests = new Map();
150150+ this.readyPromise = null;
151151+ this.readyResolve = null;
152152+ this.isReady = false;
153153+154154+ this.worker.onmessage = (event) => this._handleMessage(event.data);
155155+ this.worker.onerror = (error) => this._handleError(error);
156156+ }
157157+158158+ /**
159159+ * Handle incoming messages from the worker.
160160+ * @private
161161+ */
162162+ _handleMessage(data) {
163163+ const msg = typeof data === 'string' ? JSON.parse(data) : data;
164164+165165+ switch (msg.type) {
166166+ case 'ready':
167167+ this.isReady = true;
168168+ if (this.readyResolve) {
169169+ this.readyResolve();
170170+ this.readyResolve = null;
171171+ }
172172+ break;
173173+174174+ case 'init_error':
175175+ if (this.readyResolve) {
176176+ // Convert to rejection
177177+ const reject = this.pendingRequests.get('init')?.reject;
178178+ if (reject) {
179179+ reject(new Error(msg.message));
180180+ this.pendingRequests.delete('init');
181181+ }
182182+ }
183183+ break;
184184+185185+ case 'output_at':
186186+ // Incremental output - accumulate caml_ppf for final output
187187+ if (!this._accumulatedOutput) {
188188+ this._accumulatedOutput = new Map();
189189+ }
190190+ {
191191+ const cellId = msg.cell_id;
192192+ const prev = this._accumulatedOutput.get(cellId) || '';
193193+ this._accumulatedOutput.set(cellId, prev + (msg.caml_ppf || ''));
194194+ }
195195+ if (this.onOutputAt) {
196196+ this.onOutputAt(msg);
197197+ }
198198+ break;
199199+200200+ case 'output':
201201+ // Merge accumulated incremental caml_ppf into the final output
202202+ if (this._accumulatedOutput && this._accumulatedOutput.has(msg.cell_id)) {
203203+ const accumulated = this._accumulatedOutput.get(msg.cell_id);
204204+ if (accumulated && (!msg.caml_ppf || msg.caml_ppf === '')) {
205205+ msg.caml_ppf = accumulated;
206206+ }
207207+ this._accumulatedOutput.delete(msg.cell_id);
208208+ }
209209+ this._resolveRequest(msg.cell_id, msg);
210210+ break;
211211+ case 'completions':
212212+ case 'types':
213213+ case 'errors':
214214+ case 'eval_error':
215215+ this._resolveRequest(msg.cell_id, msg);
216216+ break;
217217+218218+ case 'env_created':
219219+ case 'env_destroyed':
220220+ this._resolveRequest(msg.env_id, msg);
221221+ break;
222222+223223+ default:
224224+ console.warn('Unknown message type:', msg.type);
225225+ }
226226+ }
227227+228228+ /**
229229+ * Handle worker errors.
230230+ * @private
231231+ */
232232+ _handleError(error) {
233233+ console.error('Worker error:', error);
234234+ // Reject all pending requests
235235+ for (const [key, { reject }] of this.pendingRequests) {
236236+ reject(error);
237237+ }
238238+ this.pendingRequests.clear();
239239+ }
240240+241241+ /**
242242+ * Resolve a pending request.
243243+ * @private
244244+ */
245245+ _resolveRequest(id, msg) {
246246+ const pending = this.pendingRequests.get(id);
247247+ if (pending) {
248248+ clearTimeout(pending.timeoutId);
249249+ if (msg.type === 'eval_error') {
250250+ pending.reject(new Error(msg.message));
251251+ } else {
252252+ pending.resolve(msg);
253253+ }
254254+ this.pendingRequests.delete(id);
255255+ }
256256+ }
257257+258258+ /**
259259+ * Send a message to the worker and wait for a response.
260260+ * @private
261261+ */
262262+ _send(msg, id) {
263263+ return new Promise((resolve, reject) => {
264264+ const timeoutId = setTimeout(() => {
265265+ this.pendingRequests.delete(id);
266266+ reject(new Error('Request timeout'));
267267+ }, this.timeout);
268268+269269+ this.pendingRequests.set(id, { resolve, reject, timeoutId });
270270+ this.worker.postMessage(JSON.stringify(msg));
271271+ });
272272+ }
273273+274274+ /**
275275+ * Get the next cell ID.
276276+ * @private
277277+ */
278278+ _nextCellId() {
279279+ return ++this.cellIdCounter;
280280+ }
281281+282282+ /**
283283+ * Initialize the worker.
284284+ * @param {InitConfig} config - Initialization configuration
285285+ * @returns {Promise<void>}
286286+ */
287287+ async init(config) {
288288+ // Set up ready promise
289289+ this.readyPromise = new Promise((resolve, reject) => {
290290+ this.readyResolve = resolve;
291291+ this.pendingRequests.set('init', { resolve, reject, timeoutId: null });
292292+ });
293293+294294+ // Set timeout for init
295295+ const timeoutId = setTimeout(() => {
296296+ this.pendingRequests.delete('init');
297297+ if (this.readyResolve) {
298298+ this.readyResolve = null;
299299+ }
300300+ throw new Error('Init timeout');
301301+ }, this.timeout);
302302+303303+ const pending = this.pendingRequests.get('init');
304304+ if (pending) {
305305+ pending.timeoutId = timeoutId;
306306+ }
307307+308308+ // Send init message
309309+ this.worker.postMessage(JSON.stringify({
310310+ type: 'init',
311311+ findlib_requires: config.findlib_requires || [],
312312+ stdlib_dcs: config.stdlib_dcs || null,
313313+ findlib_index: config.findlib_index || null,
314314+ }));
315315+316316+ // Wait for ready
317317+ await this.readyPromise;
318318+ clearTimeout(timeoutId);
319319+ this.pendingRequests.delete('init');
320320+ }
321321+322322+ /**
323323+ * Wait for the worker to be ready.
324324+ * @returns {Promise<void>}
325325+ */
326326+ async waitReady() {
327327+ if (this.isReady) return;
328328+ if (this.readyPromise) {
329329+ await this.readyPromise;
330330+ }
331331+ }
332332+333333+ /**
334334+ * Evaluate OCaml code.
335335+ * @param {string} code - OCaml code to evaluate
336336+ * @param {string} [envId='default'] - Environment ID
337337+ * @returns {Promise<Output>}
338338+ */
339339+ async eval(code, envId = 'default') {
340340+ await this.waitReady();
341341+ const cellId = this._nextCellId();
342342+ return this._send({
343343+ type: 'eval',
344344+ cell_id: cellId,
345345+ env_id: envId,
346346+ code: code,
347347+ }, cellId);
348348+ }
349349+350350+ /**
351351+ * Get completions at a position.
352352+ * @param {string} source - Source code
353353+ * @param {number} position - Cursor position (character offset)
354354+ * @param {string} [envId='default'] - Environment ID
355355+ * @returns {Promise<Completions>}
356356+ */
357357+ async complete(source, position, envId = 'default') {
358358+ await this.waitReady();
359359+ const cellId = this._nextCellId();
360360+ return this._send({
361361+ type: 'complete',
362362+ cell_id: cellId,
363363+ env_id: envId,
364364+ source: source,
365365+ position: position,
366366+ }, cellId);
367367+ }
368368+369369+ /**
370370+ * Get type information at a position.
371371+ * @param {string} source - Source code
372372+ * @param {number} position - Cursor position (character offset)
373373+ * @param {string} [envId='default'] - Environment ID
374374+ * @returns {Promise<{cell_id: number, types: TypeInfo[]}>}
375375+ */
376376+ async typeAt(source, position, envId = 'default') {
377377+ await this.waitReady();
378378+ const cellId = this._nextCellId();
379379+ return this._send({
380380+ type: 'type_at',
381381+ cell_id: cellId,
382382+ env_id: envId,
383383+ source: source,
384384+ position: position,
385385+ }, cellId);
386386+ }
387387+388388+ /**
389389+ * Get errors for source code.
390390+ * @param {string} source - Source code
391391+ * @param {string} [envId='default'] - Environment ID
392392+ * @returns {Promise<{cell_id: number, errors: Error[]}>}
393393+ */
394394+ async errors(source, envId = 'default') {
395395+ await this.waitReady();
396396+ const cellId = this._nextCellId();
397397+ return this._send({
398398+ type: 'errors',
399399+ cell_id: cellId,
400400+ env_id: envId,
401401+ source: source,
402402+ }, cellId);
403403+ }
404404+405405+ /**
406406+ * Create a new execution environment.
407407+ * @param {string} envId - Environment ID
408408+ * @returns {Promise<{env_id: string}>}
409409+ */
410410+ async createEnv(envId) {
411411+ await this.waitReady();
412412+ return this._send({
413413+ type: 'create_env',
414414+ env_id: envId,
415415+ }, envId);
416416+ }
417417+418418+ /**
419419+ * Destroy an execution environment.
420420+ * @param {string} envId - Environment ID
421421+ * @returns {Promise<{env_id: string}>}
422422+ */
423423+ async destroyEnv(envId) {
424424+ await this.waitReady();
425425+ return this._send({
426426+ type: 'destroy_env',
427427+ env_id: envId,
428428+ }, envId);
429429+ }
430430+431431+ /**
432432+ * Terminate the worker.
433433+ */
434434+ terminate() {
435435+ this.worker.terminate();
436436+ // Reject all pending requests
437437+ for (const [key, { reject, timeoutId }] of this.pendingRequests) {
438438+ clearTimeout(timeoutId);
439439+ reject(new Error('Worker terminated'));
440440+ }
441441+ this.pendingRequests.clear();
442442+ }
443443+}
444444+445445+// Also export as default
446446+export default OcamlWorker;
···11+# js_top_worker Investigation Report
22+33+This document captures research findings for the communication layer redesign.
44+55+## Phase 0.1: Wire Format Research
66+77+### Goal
88+99+Find a suitable serialization format for bidirectional typed messaging between frontend (browser) and backend (WebWorker/remote).
1010+1111+### Requirements
1212+1313+- Binary format preferred (compact, fast)
1414+- Type-safe OCaml codec (define once, use for both encode/decode)
1515+- js_of_ocaml compatible
1616+- Support for structured data (records, variants, arrays, maps)
1717+1818+### Options Evaluated
1919+2020+| Library | Format | js_of_ocaml | Notes |
2121+|---------|--------|-------------|-------|
2222+| ocaml-rpc (current) | JSON-RPC | Yes | Request-response only, no push |
2323+| jsont | JSON | Yes (via brr) | Type-safe combinators, JSON only |
2424+| msgpck | MessagePack | Likely (pure OCaml) | Less active |
2525+| cbor | CBOR | Likely (pure OCaml) | Basic API |
2626+| **cbort** | CBOR | Yes (via zarith_stubs_js) | Type-safe combinators, RFC 8949 |
2727+2828+### Recommendation: cbort
2929+3030+The [cbort](https://tangled.org/@anil.recoil.org/ocaml-cbort.git) library by Anil Madhavapeddy is the best choice:
3131+3232+1. **Type-safe combinators** following the jsont pattern - define codecs once, use bidirectionally
3333+2. **CBOR format** (RFC 8949) - compact binary, smaller than JSON, widely supported
3434+3. **js_of_ocaml compatible** via zarith_stubs_js for arbitrary-precision integers
3535+4. **Built on bytesrw** for efficient streaming I/O
3636+5. **Path-aware error messages** for debugging decode failures
3737+3838+#### Example Codec Definition
3939+4040+```ocaml
4141+open Cbort
4242+4343+type person = { name : string; age : int }
4444+4545+let person_codec =
4646+ let open Obj in
4747+ let* name = mem "name" (fun p -> p.name) string in
4848+ let* age = mem "age" (fun p -> p.age) int in
4949+ return { name; age }
5050+ |> finish
5151+5252+(* Encode to CBOR bytes *)
5353+let encoded = encode_string person_codec { name = "Alice"; age = 30 }
5454+5555+(* Decode from CBOR bytes *)
5656+let decoded = decode_string person_codec encoded
5757+```
5858+5959+#### Dependencies
6060+6161+- `bytesrw >= 0.2` - Pure OCaml streaming I/O
6262+- `zarith >= 1.12` - Arbitrary precision integers (uses zarith_stubs_js for JS)
6363+- `crowbar` - Fuzz testing (dev only)
6464+6565+#### Installation
6666+6767+Currently available from tangled.org:
6868+```
6969+git clone https://tangled.org/@anil.recoil.org/ocaml-cbort.git
7070+```
7171+7272+Will need pin-depends in dune-project until published to opam.
7373+7474+### Jupyter Protocol Reference
7575+7676+For comparison, Jupyter uses:
7777+- **JSON** for message content
7878+- **ZeroMQ** for transport (multipart messages)
7979+- **MIME types** for rich output (text/plain, text/html, image/png, etc.)
8080+8181+Key Jupyter message types:
8282+- `execute_request` / `execute_reply` - Code execution
8383+- `stream` - stdout/stderr output
8484+- `display_data` - MIME-typed rich output
8585+- `comm_open` / `comm_msg` - Bidirectional widget communication
8686+8787+Our design will follow similar patterns but use CBOR instead of JSON.
8888+8989+---
9090+9191+## Phase 0.2: Findlib Investigation
9292+9393+### Goal
9494+9595+Understand what real `findlib.top` does and whether to integrate it or improve `findlibish`.
9696+9797+### Current Implementation: findlibish
9898+9999+The project has a custom `findlibish.ml` (221 lines) that:
100100+101101+1. Parses META files using `Fl_metascanner`
102102+2. Builds a library dependency graph
103103+3. Resolves `#require` requests
104104+4. Loads `.cma.js` archives via `import_scripts`
105105+5. Fetches `dynamic_cmis.json` for type information
106106+107107+Key differences from real findlib:
108108+- No `topfind` file mechanism
109109+- No `#list`, `#camlp4o`, etc. directives
110110+- Hardcoded list of "preloaded" packages (compiler-libs, merlin, etc.)
111111+- URL-based fetching instead of filesystem access
112112+113113+### Real Findlib Behavior (from source analysis)
114114+115115+Studied [ocamlfind source](https://github.com/ocaml/ocamlfind) - specifically `src/findlib/topfind.ml.in`.
116116+117117+#### Directive Registration
118118+119119+Findlib registers directives by adding to `Toploop.directive_table`:
120120+121121+```ocaml
122122+Hashtbl.add
123123+ Toploop.directive_table
124124+ "require"
125125+ (Toploop.Directive_string
126126+ (fun s -> protect load_deeply (Fl_split.in_words s)))
127127+```
128128+129129+#### Package Loading (`load` function)
130130+131131+The `load` function performs these steps:
132132+1. Get package directory via `Findlib.package_directory pkg`
133133+2. Add directory to search path via `Topdirs.dir_directory d`
134134+3. Get `archive` property from META file
135135+4. Load archives via `Topdirs.dir_load Format.std_formatter archive`
136136+5. Handle PPX properties (if defined)
137137+6. Record package as loaded via `Findlib.record_package`
138138+139139+#### Deep Loading (`load_deeply` function)
140140+141141+```ocaml
142142+let load_deeply pkglist =
143143+ (* Get the sorted list of ancestors *)
144144+ let eff_pkglist =
145145+ Findlib.package_deep_ancestors !predicates pkglist in
146146+ (* Check for error properties *)
147147+ List.iter (fun pkg ->
148148+ try let error = Findlib.package_property !predicates pkg "error" in
149149+ failwith ("Error from package `" ^ pkg ^ "': " ^ error)
150150+ with Not_found -> ()) eff_pkglist ;
151151+ (* Load the packages in turn: *)
152152+ load eff_pkglist
153153+```
154154+155155+#### Key Mechanisms
156156+157157+| Findlib | findlibish | Notes |
158158+|---------|------------|-------|
159159+| `Topdirs.dir_load` | `import_scripts` | Native .cma vs .cma.js |
160160+| `Topdirs.dir_directory` | N/A | Search path management |
161161+| `Findlib.package_directory` | URL-based | Filesystem vs HTTP |
162162+| Predicate system | Hardcoded | `["byte"; "toploop"]` etc. |
163163+| `Findlib.record_package` | `loaded` mutable field | Track loaded packages |
164164+165165+### Recommendation
166166+167167+**Keep findlibish but improve it**. The architectures are fundamentally different:
168168+169169+1. **Findlib**: Native bytecode loading, filesystem access, Toploop integration
170170+2. **findlibish**: JavaScript module loading, URL fetching, WebWorker context
171171+172172+Key improvements to make:
173173+1. Add `.mli` file documenting the API
174174+2. Support `#list` directive for discoverability
175175+3. Better error messages when packages not found
176176+4. Add test to verify `preloaded` list matches build (see below)
177177+5. Add predicate support for conditional archives
178178+179179+#### Preloaded List Synchronization
180180+181181+The `preloaded` list in `findlibish.ml` must match packages linked into the
182182+worker via dune. Currently this is manually maintained and can drift.
183183+184184+**Solution**: Add a test that verifies consistency:
185185+- Query actually-linked packages (via `Findlib.recorded_packages()` or similar)
186186+- Compare against `preloaded` list
187187+- Fail with clear message if they differ
188188+189189+This catches drift without adding build-time complexity. The current list also
190190+has duplicates (`js_of_ocaml-ppx`, `findlib`) that should be cleaned up.
191191+192192+---
193193+194194+## Phase 0.3: Environment Model Research
195195+196196+### Goal
197197+198198+Understand how to support multiple isolated execution environments (like mdx `x-ocaml` blocks).
199199+200200+### Current State
201201+202202+The project already has cell ID support:
203203+- `opt_id` parameter on API calls
204204+- `Cell__<id>` modules for cell outputs
205205+- `failed_cells` tracking for dependency management
206206+- `mangle_toplevel` adds `open Cell__<dep>` for dependencies
207207+208208+### MDX Implementation (from source analysis)
209209+210210+Studied [mdx source](https://github.com/realworldocaml/mdx) - specifically `lib/top/mdx_top.ml`.
211211+212212+MDX implements environment isolation by capturing and restoring Toploop state:
213213+214214+```ocaml
215215+(* Environment storage: name -> (type_env, binding_names, runtime_values) *)
216216+let envs = Hashtbl.create 8
217217+218218+(* Extract user-defined bindings from environment summary *)
219219+let env_deps env =
220220+ let names = save_summary [] (Env.summary env) in
221221+ let objs = List.map Toploop.getvalue names in
222222+ (env, names, objs)
223223+224224+(* Restore environment state *)
225225+let load_env env names objs =
226226+ Toploop.toplevel_env := env;
227227+ List.iter2 Toploop.setvalue names objs
228228+229229+(* Execute code in a named environment *)
230230+let in_env e f =
231231+ let env_name = Mdx.Ocaml_env.name e in
232232+ let env, names, objs =
233233+ try Hashtbl.find envs env_name
234234+ with Not_found -> env_deps !default_env
235235+ in
236236+ load_env env names objs;
237237+ let res = f () in
238238+ (* Save updated state *)
239239+ Hashtbl.replace envs env_name (env_deps !Toploop.toplevel_env);
240240+ res
241241+```
242242+243243+#### Key Toploop State Components
244244+245245+| Component | Access Method | Description |
246246+|-----------|---------------|-------------|
247247+| Type environment | `Toploop.toplevel_env` | Type bindings, modules |
248248+| Runtime values | `Toploop.getvalue`/`setvalue` | Actual OCaml values |
249249+| Environment summary | `Env.summary` | List of binding operations |
250250+251251+#### MDX's Strategy
252252+253253+1. **Shared base**: All environments start from `default_env` (initial Toploop state)
254254+2. **Capture on exit**: After execution, save `(env, names, objs)` tuple
255255+3. **Restore on entry**: Before execution, restore the saved state
256256+4. **Hashtable storage**: Environments keyed by string name
257257+258258+### Implications for js_top_worker
259259+260260+The MDX approach works because it runs in a native OCaml process with mutable global state. For WebWorker:
261261+262262+1. **Same approach possible**: We have Toploop in js_of_ocaml-toplevel
263263+2. **Memory concern**: Each environment stores captured values - could grow large
264264+3. **No true fork**: Can't fork WebWorker, must use save/restore pattern
265265+4. **Cell IDs vs Environments**: Current cell system is different - cells can depend on each other, environments are isolated
266266+267267+### x-ocaml Implementation (better than mdx)
268268+269269+Studied [x-ocaml](https://github.com/art-w/x-ocaml) by @art-w - cleaner approach.
270270+271271+#### Value Capture with Env.diff
272272+273273+```ocaml
274274+module Value_env = struct
275275+ type t = Obj.t String_map.t
276276+277277+ let capture t idents =
278278+ List.fold_left (fun t ident ->
279279+ let name = Translmod.toplevel_name ident in
280280+ let v = Topeval.getvalue name in
281281+ String_map.add name v t
282282+ ) t idents
283283+284284+ let restore t =
285285+ String_map.iter (fun name v -> Topeval.setvalue name v) t
286286+end
287287+```
288288+289289+Key insight: Uses `Env.diff previous_env current_env` to get only NEW bindings,
290290+rather than walking the full environment summary like mdx does.
291291+292292+#### Stack-based Environment Management
293293+294294+```ocaml
295295+module Environment = struct
296296+ let environments = ref [] (* stack of (id, typing_env, value_env) *)
297297+298298+ let reset id =
299299+ (* Walk stack until we find id, restore that state *)
300300+ environments := go id !environments
301301+302302+ let capture id =
303303+ let idents = Env.diff previous_env !Toploop.toplevel_env in
304304+ let values = Value_env.capture previous_values idents in
305305+ environments := (id, !Toploop.toplevel_env, values) :: !environments
306306+end
307307+```
308308+309309+Benefits:
310310+- Can backtrack to any previous checkpoint
311311+- Only captures incremental changes (memory efficient)
312312+- Simple integer IDs
313313+314314+#### PPX Integration
315315+316316+```ocaml
317317+(* Capture all registered PPX rewriters *)
318318+let ppx_rewriters = ref []
319319+320320+let () =
321321+ Ast_mapper.register_function :=
322322+ fun _ f -> ppx_rewriters := f :: !ppx_rewriters
323323+324324+(* Apply during phrase preprocessing *)
325325+let preprocess_phrase phrase =
326326+ match phrase with
327327+ | Ptop_def str -> Ptop_def (preprocess_structure str)
328328+ | Ptop_dir _ as x -> x
329329+```
330330+331331+ppxlib bridge (`ppxlib_register.ml`):
332332+```ocaml
333333+let () = Ast_mapper.register "ppxlib" mapper
334334+```
335335+336336+### Recommended Design
337337+338338+Adopt x-ocaml's core patterns, adapted for js_top_worker's purpose as a
339339+reusable backend library:
340340+341341+**From x-ocaml (adopt directly)**:
342342+1. **Incremental capture** via `Env.diff` - replaces current cell wrapping
343343+2. **PPX via `Ast_mapper.register_function`** override
344344+3. **ppxlib bridge** for modern PPX ecosystem
345345+346346+**Adapted for js_top_worker**:
347347+1. **Named environments** instead of pure stack (multiple notebooks can coexist)
348348+2. **MIME output API** generalizing x-ocaml's `output_html`
349349+3. **cbort protocol** instead of Marshal (type-safe, browser-friendly)
350350+351351+**API sketch**:
352352+```ocaml
353353+type env_id = string
354354+355355+(* Environment management *)
356356+val create_env : ?base:env_id -> env_id -> unit
357357+val checkpoint : env_id -> unit (* capture current state *)
358358+val reset : env_id -> unit (* restore to last checkpoint *)
359359+val destroy_env : env_id -> unit
360360+361361+(* Execution *)
362362+val exec : env:env_id -> string -> exec_result
363363+364364+(* MIME output (callable from user code) *)
365365+val display : ?mime_type:string -> string -> unit
366366+```
367367+368368+This gives us x-ocaml's simplicity while supporting:
369369+- Multiple concurrent environments (different notebooks)
370370+- Checkpoint/reset within an environment (cell re-execution)
371371+- Rich output beyond just HTML
372372+373373+---
374374+375375+## Phase 0.4: Existing Art Review
376376+377377+### Projects Analyzed
378378+379379+| Project | URL | Architecture |
380380+|---------|-----|--------------|
381381+| ocaml-jupyter | https://github.com/akabe/ocaml-jupyter | Native OCaml + ZeroMQ |
382382+| js_of_ocaml toplevel | https://ocsigen.org/js_of_ocaml | Browser + js_of_ocaml |
383383+| sketch.sh | https://github.com/Sketch-sh/sketch-sh | Browser + WebWorker |
384384+| utop | https://github.com/ocaml-community/utop | Native OCaml + terminal |
385385+386386+### ocaml-jupyter
387387+388388+**Architecture**: Native OCaml kernel communicating via ZeroMQ (Jupyter protocol v5.2).
389389+390390+**Key components**:
391391+- `jupyter` - Core protocol implementation
392392+- `jupyter.notebook` - Rich output API (HTML, markdown, images, LaTeX)
393393+- `jupyter.comm` - Bidirectional widget communication
394394+395395+**Rich output**: Programmatic generation via `jupyter.notebook` library:
396396+```ocaml
397397+(* Example from jupyter.notebook *)
398398+Jupyter_notebook.display "text/html" "<b>Hello</b>"
399399+```
400400+401401+**Code completion**: Merlin integration, reads `.merlin` files.
402402+403403+**Takeaway**: Good reference for MIME output API and comm protocol design.
404404+405405+### js_of_ocaml Toplevel
406406+407407+**Architecture**: OCaml bytecode compiled to JavaScript, runs in browser.
408408+409409+**Build flags**:
410410+```bash
411411+js_of_ocaml --toplevel --linkall +weak.js +toplevel.js +dynlink.js
412412+```
413413+414414+**Library loading**: Two approaches:
415415+1. Compile libraries into toplevel directly
416416+2. Load dynamically via `--extern-fs` pseudo-filesystem
417417+418418+**Takeaway**: Foundation of our project. We already use js_of_ocaml-toplevel.
419419+420420+### Sketch.sh
421421+422422+**Architecture**: Browser-based notebook using js_of_ocaml toplevel in WebWorker.
423423+424424+**Key insight**: "rtop-evaluator loads refmt & js_of_ocaml compiler as a web worker"
425425+426426+**Features**:
427427+- Multiple OCaml versions (4.06.1, 4.13.1, 5.3.0)
428428+- Reason syntax support via refmt
429429+- Notebook-style cells with inline evaluation
430430+- OCaml 5 effects support (continuation-based in JS)
431431+432432+**Limitations**:
433433+- No BuckleScript modules (Js module)
434434+- Belt library support added later
435435+436436+**Takeaway**: Similar architecture to js_top_worker. Good reference for multi-version support.
437437+438438+### utop
439439+440440+**Architecture**: Enhanced native OCaml toplevel with:
441441+- Line editing (lambda-term)
442442+- History
443443+- Context-sensitive completion
444444+- Colors
445445+446446+**Features relevant to us**:
447447+- `UTop.set_create_implicits` - Auto-generate module interfaces
448448+- Merlin integration for completion
449449+- PPX rewriter support
450450+451451+**Takeaway**: Reference for toplevel UX features (completion, error formatting).
452452+453453+### Comparison Summary
454454+455455+| Feature | ocaml-jupyter | sketch.sh | js_top_worker |
456456+|---------|---------------|-----------|---------------|
457457+| Runtime | Native | Browser/Worker | Browser/Worker |
458458+| Protocol | Jupyter/ZMQ | Custom | RPC (current) |
459459+| Rich output | MIME via API | Limited | MIME (planned) |
460460+| Widgets | jupyter.comm | No | Planned |
461461+| Multi-env | No | No | Planned |
462462+| Completion | Merlin | Basic | Merlin |
463463+464464+### Key Lessons
465465+466466+1. **MIME output**: jupyter.notebook provides good API pattern
467467+2. **Widget comm**: jupyter.comm shows bidirectional messaging
468468+3. **WebWorker**: sketch.sh validates our architecture choice
469469+4. **Environment isolation**: None of these support it - opportunity for differentiation
470470+471471+---
472472+473473+## Open Questions
474474+475475+1. **Widget state persistence**: How long should widget state live? Per-session? Per-environment?
476476+477477+2. **Streaming output**: Should stdout/stderr be pushed incrementally or batched?
478478+479479+3. **PPX scope**: When a PPX is installed, should it apply to:
480480+ - All environments?
481481+ - Just the current environment?
482482+ - Configurable?
483483+484484+4. **Error recovery**: If a cell fails, how do dependent cells behave?
485485+ - Current: tracked in `failed_cells` set
486486+ - Desired: TBD
487487+488488+---
489489+490490+## Summary of Findings
491491+492492+### Wire Format Decision: cbort
493493+494494+Use [cbort](https://tangled.org/@anil.recoil.org/ocaml-cbort.git) for CBOR-based typed messaging:
495495+- Type-safe combinators (jsont-style)
496496+- Binary format (compact, fast)
497497+- js_of_ocaml compatible via zarith_stubs_js
498498+499499+### Findlib Decision: Keep findlibish
500500+501501+The current `findlibish.ml` is appropriate for WebWorker context:
502502+- URL-based package loading (not filesystem)
503503+- JavaScript module loading via `import_scripts`
504504+- Add `.mli` file and improve error handling
505505+- Add test to verify preloaded list matches build
506506+507507+### Environment Model Decision: x-ocaml-style capture/restore
508508+509509+Adopt [x-ocaml](https://github.com/art-w/x-ocaml)'s approach:
510510+- **`Env.diff`** for incremental capture (only new bindings)
511511+- **`Topeval.getvalue`/`setvalue`** for runtime values
512512+- **Named environments** (adapting x-ocaml's integer stack)
513513+- **PPX via `Ast_mapper.register_function`** override
514514+515515+This replaces the current cell module wrapping approach with something simpler
516516+and more powerful (supports checkpoint/reset, not just forward execution).
517517+518518+### Key Differentiators
519519+520520+Features that set js_top_worker apart:
521521+1. **Multiple named environments** - Not supported by competitors
522522+2. **CBOR wire format** - More efficient than JSON/Marshal
523523+3. **Bidirectional widgets** - Like Jupyter but in browser
524524+4. **PPX support** - Via x-ocaml's pattern + ppxlib bridge
525525+5. **Reusable backend** - Library for others to build on
526526+527527+---
528528+529529+## Next Steps
530530+531531+### Immediate (Phase 1)
532532+533533+1. **Add cbort dependency**: Pin-depends in dune-project
534534+2. **Define message types**: Simple ADT like x-ocaml, encoded with cbort
535535+ ```ocaml
536536+ type request =
537537+ | Setup
538538+ | Eval of { env : string; code : string }
539539+ | Merlin of { env : string; action : Merlin_protocol.action }
540540+ | Checkpoint of { env : string }
541541+ | Reset of { env : string }
542542+543543+ type response =
544544+ | Setup_complete
545545+ | Output of { env : string; loc : int; data : output list }
546546+ | Eval_complete of { env : string; result : exec_result }
547547+ | Merlin_response of Merlin_protocol.answer
548548+ ```
549549+3. **Replace RPC with simple message handling**: Like x-ocaml's pattern match
550550+4. ~~**Remove compile_js**: Delete unused method~~ ✓ Done
551551+552552+### Short-term (Phase 2)
553553+554554+5. **Environment isolation**: x-ocaml's `Env.diff` + `Topeval.getvalue/setvalue`
555555+6. **PPX support**: `Ast_mapper.register_function` override + ppxlib bridge
556556+7. **Add .mli files**: `impl.mli`, `findlibish.mli`
557557+8. **CI setup**: GitHub Actions for OCaml 5.2+
558558+9. **Preloaded list test**: Verify sync with build
559559+560560+### Medium-term (Phase 3)
561561+562562+10. **MIME output API**: Generalize x-ocaml's `output_html` pattern
563563+11. **Widget protocol**: Bidirectional comm for interactive widgets
564564+12. **OCamlformat integration**: Auto-format like x-ocaml
565565+566566+---
567567+568568+*Last updated: 2026-01-20*
+93
js_top_worker/docs/technical-qa.md
···11+# Technical Q&A Log
22+33+This file records technical questions and answers about the codebase, along with verification steps taken to ensure accuracy.
44+55+---
66+77+## 2026-02-06: Is js_of_ocaml compilation deterministic?
88+99+**Question**: Is js_of_ocaml compilation deterministic? If we rebuild the same package, will the `.cma.js` file have the same content hash? This matters for using content hashes as cache-busting URLs.
1010+1111+**Answer**: Yes, js_of_ocaml compilation is deterministic. Given the same inputs (bytecode, debug info, compiler version, flags), it produces byte-for-byte identical JavaScript output. This is confirmed by both the js_of_ocaml maintainer (hhugo) and empirical testing.
1212+1313+**Evidence**:
1414+1515+1. **Maintainer confirmation** (GitHub issue ocsigen/js_of_ocaml#1297): hhugo (Hugo Heuzard, core maintainer) stated: "Js_of_ocaml produces JS from ocaml bytecode and uses debug info (from the bytecode) to recover variable names. The renaming algo is deterministic. You should expect the jsoo build to be reproducible."
1616+1717+2. **Source code analysis**: The `js_output.ml` file in the compiler converts internal Hashtbl structures to sorted lists before output generation:
1818+ ```ocaml
1919+ let hashtbl_to_list htb =
2020+ String.Hashtbl.fold (fun k v l -> (k, v) :: l) htb []
2121+ |> List.sort ~cmp:(fun (_, a) (_, b) -> compare a b)
2222+ |> List.map ~f:fst
2323+ ```
2424+ This ensures deterministic output regardless of Hashtbl iteration order.
2525+2626+3. **No embedded non-deterministic data**: Grep of `.cma.js` files found no embedded timestamps, build paths, random values, or other non-deterministic content.
2727+2828+4. **Empirical testing** (OCaml 5.4.0, js_of_ocaml 6.2.0): Four consecutive `dune clean && dune build` cycles (including one with `-j 1`) produced byte-for-byte identical `.cma.js` files:
2929+ - `stdlib.cma.js`: `496346f4...` (all 4 builds)
3030+ - `lwt.cma.js`: `e65a4a54...` (all 4 builds)
3131+ - `rpclib.cma.js`: `ffaa5ffc...` (all 4 builds)
3232+ - `js_of_ocaml.cma.js`: `4169ea91...` (all 4 builds)
3333+3434+**Caveats**:
3535+3636+- **Different OCaml compiler versions** will produce different bytecode, which leads to different `.cma.js` output. Content hashes are stable only when the full toolchain is pinned.
3737+- **Different js_of_ocaml versions** or different compiler flags (e.g., `--opt 3` vs default) will produce different output.
3838+- **Dune parallel build bug** (dune#3863): On OCaml < 4.11, parallel builds could produce non-deterministic `.cmo` files due to debug info sensitivity. This is fixed in OCaml 4.11+ (we use 5.4.0).
3939+- **`dune-build-info`**: If a package uses `dune-build-info`, the VCS revision can be embedded in the binary, but this does not affect `.cma.js` compilation for libraries that don't use it.
4040+4141+**Conclusion**: Content hashes of `.cma.js` files are safe to use for cache-busting URLs, provided the OCaml toolchain version and js_of_ocaml version are held constant (which they are within a single ohc layer build).
4242+4343+**Verification Steps**:
4444+- Searched web for "js_of_ocaml deterministic", "js_of_ocaml reproducible build"
4545+- Read GitHub issue ocsigen/js_of_ocaml#1297 and all comments
4646+- Analyzed js_of_ocaml compiler source (`generate.ml`, `js_output.ml`) for non-determinism
4747+- Performed 4 clean rebuilds and compared SHA-256 hashes
4848+- Tested both default parallelism and `-j 1` single-core builds
4949+- Grepped `.cma.js` output for embedded paths, timestamps, dates
5050+5151+---
5252+5353+## 2026-01-20: What does `--include-runtime` do in js_of_ocaml?
5454+5555+**Question**: What does the `--include-runtime` argument actually do when compiling with js_of_ocaml?
5656+5757+**Answer**: The `--include-runtime` flag embeds library-specific JS stubs (from the library's `runtime.js` files) into the compiled output. It does NOT include the full js_of_ocaml runtime.
5858+5959+When used with `--toplevel`, it:
6060+1. Takes the library's `runtime.js` stubs (e.g., `+base/runtime.js`)
6161+2. Embeds them in the compiled `.js` file
6262+3. Registers them on `jsoo_runtime` via `Object.assign()`
6363+6464+This allows separate compilation where each library's `.cma.js` file carries its own stubs, rather than requiring all stubs to be bundled into the main toplevel.
6565+6666+**Verification Steps**:
6767+6868+1. **File size comparison**: Compiled `base.cma.js` with and without `--include-runtime`
6969+ - With: 629KB
7070+ - Without: 626KB
7171+ - Difference: ~3KB (just the stubs, not the full runtime)
7272+7373+2. **Searched for runtime functions**:
7474+ ```bash
7575+ grep -c "function caml_call_gen" base.cma.js
7676+ # Result: 0 definitions, 215 references
7777+7878+ grep -c "function caml_register_global" base.cma.js
7979+ # Result: 0 definitions, 146 references
8080+ ```
8181+ This confirms the core runtime is NOT included.
8282+8383+3. **Found stub registration pattern**:
8484+ ```javascript
8585+ Object.assign(a.jsoo_runtime, {Base_am_testing: m, Base_hash_stubs: n, ...})
8686+ ```
8787+ This shows how stubs are registered on the global `jsoo_runtime` object.
8888+8989+4. **Runtime test**: The Node.js test in `test/node/` successfully loads `base` and uses functions that depend on JS stubs (hash functions), confirming the stubs work correctly when embedded this way.
9090+9191+**Related**: js_of_ocaml PR #1509 added support for this feature in toplevel mode.
9292+9393+---
+296
js_top_worker/docs/test-gaps-design.md
···11+# Test Gap Analysis and Design
22+33+## Current State
44+55+### Existing Infrastructure
66+77+| Type | Location | Framework | Status |
88+|------|----------|-----------|--------|
99+| Node.js tests | `test/node/` | OCaml → js_of_ocaml → Node.js | ✅ Integrated in dune |
1010+| Cram tests | `test/cram/` | Shell + unix_worker/client | ✅ Integrated in dune |
1111+| Unit tests | `test/libtest/` | ppx_expect | ✅ Integrated in dune |
1212+| Browser tests | `test/browser/` | Playwright | ❌ **Not integrated** |
1313+1414+### Browser Test Files (exist but not wired up)
1515+1616+```
1717+test/browser/
1818+├── package.json # Playwright dependency
1919+├── run_tests.js # Playwright runner (serves files, runs browser)
2020+├── test.html # Test harness HTML
2121+├── client_test.ml # OCaml test code (needs compilation)
2222+├── test_worker.ml # Test worker (needs compilation)
2323+├── test_features.js # Feature tests (MIME, autocomplete, etc.)
2424+├── test_env_isolation.js # Environment isolation test
2525+└── test_demo.js # Demo page test
2626+```
2727+2828+## Critical Gaps
2929+3030+### 1. Cell Dependencies
3131+3232+**Current coverage:** Linear chain only (`c1 → c2 → c3 → c4`)
3333+3434+**Missing scenarios:**
3535+3636+```
3737+A. Diamond dependency:
3838+ c1 (type t = int)
3939+ ↓ ↓
4040+ c2 (x:t) c3 (y:t)
4141+ ↓ ↓
4242+ c4 (x + y)
4343+4444+B. Missing dependency:
4545+ c2 depends on ["c1"] but c1 doesn't exist → should error gracefully
4646+4747+C. Circular reference handling:
4848+ c1 depends on ["c2"], c2 depends on ["c1"] → should detect/reject
4949+5050+D. Dependency update propagation:
5151+ c1 changes → c2, c3 that depend on c1 should see new types
5252+5353+E. Type shadowing across cells:
5454+ c1: type t = int
5555+ c2: type t = string (depends on c1)
5656+ c3: uses t (depends on c1, c2) → which t?
5757+```
5858+5959+### 2. Error Recovery
6060+6161+**Missing scenarios:**
6262+6363+```
6464+A. Syntax errors:
6565+ - Unterminated string/comment
6666+ - Mismatched brackets
6767+ - Invalid tokens
6868+6969+B. Type errors with recovery:
7070+ - First phrase errors, second should still work
7171+ - Error in middle of multi-phrase input
7272+7373+C. Runtime errors:
7474+ - Stack overflow (deep recursion)
7575+ - Out of memory (large data structures)
7676+ - Division by zero
7777+7878+D. Toplevel state corruption:
7979+ - Can we continue after an error?
8080+ - Is state consistent after partial execution?
8181+```
8282+8383+### 3. Browser/WebWorker Integration
8484+8585+**Problem:** Tests exist but aren't run by `dune runtest`
8686+8787+**Current workflow (manual):**
8888+```bash
8989+cd test/browser
9090+npm install
9191+# Manually build OCaml files somehow
9292+npm test
9393+```
9494+9595+**Needed workflow:**
9696+```bash
9797+dune runtest # Should include browser tests
9898+```
9999+100100+## Proposed Design
101101+102102+### Browser Test Integration
103103+104104+#### Option A: Playwright in dune (Recommended)
105105+106106+```
107107+test/browser/dune:
108108+─────────────────────
109109+(executable
110110+ (name client_test)
111111+ (modes js)
112112+ (libraries js_top_worker-client lwt js_of_ocaml))
113113+114114+(executable
115115+ (name test_worker)
116116+ (modes js)
117117+ (libraries js_top_worker-web ...))
118118+119119+(rule
120120+ (alias runtest)
121121+ (deps
122122+ client_test.bc.js
123123+ test_worker.bc.js
124124+ test.html
125125+ (:runner run_tests.js))
126126+ (action
127127+ (run node %{runner})))
128128+```
129129+130130+**Pros:**
131131+- Integrated into normal `dune runtest`
132132+- OCaml files compiled automatically
133133+- Playwright handles browser lifecycle
134134+135135+**Cons:**
136136+- Requires Node.js + Playwright installed
137137+- Slower than headless Node tests
138138+139139+#### Option B: Separate browser test target
140140+141141+```bash
142142+dune runtest # Node + cram tests only
143143+dune runtest @browser # Browser tests (when Playwright available)
144144+```
145145+146146+**Pros:**
147147+- CI can skip browser tests if Playwright not available
148148+- Faster default test runs
149149+150150+**Cons:**
151151+- Easy to forget to run browser tests
152152+153153+#### Recommendation: Option B with CI integration
154154+155155+- Default `dune runtest` excludes browser tests
156156+- `dune runtest @browser` for browser tests
157157+- CI runs both
158158+159159+### Cell Dependency Tests
160160+161161+Add to `test/node/node_dependency_test.ml`:
162162+163163+```ocaml
164164+(* Test diamond dependencies *)
165165+let test_diamond rpc =
166166+ (* c1: base type *)
167167+ let* _ = query_errors rpc "" (Some "c1") [] false "type point = {x:int; y:int};;" in
168168+169169+ (* c2, c3: both depend on c1 *)
170170+ let* _ = query_errors rpc "" (Some "c2") ["c1"] false "let origin : point = {x=0;y=0};;" in
171171+ let* _ = query_errors rpc "" (Some "c3") ["c1"] false "let unit_x : point = {x=1;y=0};;" in
172172+173173+ (* c4: depends on c2 and c3 *)
174174+ let* errors = query_errors rpc "" (Some "c4") ["c2";"c3"] false
175175+ "let add p1 p2 = {x=p1.x+p2.x; y=p1.y+p2.y};; add origin unit_x;;" in
176176+177177+ assert (List.length errors = 0);
178178+ Lwt.return (Ok ())
179179+180180+(* Test missing dependency *)
181181+let test_missing_dep rpc =
182182+ let* errors = query_errors rpc "" (Some "c2") ["nonexistent"] false "let x = 1;;" in
183183+ (* Should either error or work without the dep *)
184184+ ...
185185+186186+(* Test dependency update *)
187187+let test_dep_update rpc =
188188+ let* _ = query_errors rpc "" (Some "c1") [] false "type t = int;;" in
189189+ let* _ = query_errors rpc "" (Some "c2") ["c1"] false "let x : t = 42;;" in
190190+191191+ (* Update c1 *)
192192+ let* _ = query_errors rpc "" (Some "c1") [] false "type t = string;;" in
193193+194194+ (* c2 should now have error (42 is not string) *)
195195+ let* errors = query_errors rpc "" (Some "c2") ["c1"] false "let x : t = 42;;" in
196196+ assert (List.length errors > 0);
197197+ Lwt.return (Ok ())
198198+```
199199+200200+### Error Recovery Tests
201201+202202+Add to `test/node/node_error_test.ml`:
203203+204204+```ocaml
205205+(* Test recovery after syntax error *)
206206+let test_syntax_recovery rpc =
207207+ (* First phrase has error *)
208208+ let* _ = exec rpc "" "let x = ;;" in (* syntax error *)
209209+210210+ (* Second phrase should still work *)
211211+ let* result = exec rpc "" "let y = 42;;" in
212212+ assert (result.caml_ppf |> Option.is_some);
213213+ Lwt.return (Ok ())
214214+215215+(* Test partial execution *)
216216+let test_partial_exec rpc =
217217+ (* Multi-phrase where second fails *)
218218+ let* result = exec rpc "" "let a = 1;; let b : string = a;; let c = 3;;" in
219219+ (* a should be defined, b should error, c may or may not run *)
220220+ ...
221221+```
222222+223223+### Findlib Tests
224224+225225+Add more packages to cram tests:
226226+227227+```
228228+test/cram/findlib.t/run.t:
229229+──────────────────────────
230230+# Test loading multiple packages with dependencies
231231+$ unix_client exec_toplevel '' '#require "lwt";; #require "lwt.unix";;'
232232+$ unix_client exec_toplevel '' 'Lwt_main.run (Lwt.return 42);;'
233233+234234+# Test package with PPX
235235+$ unix_client exec_toplevel '' '#require "ppx_deriving.show";;'
236236+$ unix_client exec_toplevel '' 'type t = A | B [@@deriving show];; show_t A;;'
237237+238238+# Test package not found
239239+$ unix_client exec_toplevel '' '#require "nonexistent_package_12345";;'
240240+```
241241+242242+## Implementation Plan
243243+244244+### Phase 1: Browser Test Integration ✅ COMPLETED
245245+246246+1. ✅ Added `test/browser/dune` to compile OCaml test files
247247+2. ✅ Added `@browser` and `@runbrowser` aliases for Playwright tests
248248+3. ✅ Fixed test_worker.ml to include `js_of_ocaml-toplevel` library
249249+4. ✅ All browser tests pass (6/6)
250250+251251+**Key fix:** The test worker needed `js_of_ocaml-toplevel` in libraries to properly
252252+initialize the OCaml toplevel for code compilation.
253253+254254+### Phase 2: Cell Dependency Tests ✅ COMPLETED
255255+256256+1. ✅ Created `test/node/node_dependency_test.ml`
257257+2. ✅ Added tests for:
258258+ - Linear dependencies (c1 → c2 → c3 → c4)
259259+ - Diamond dependencies (d1 → d2,d3 → d4)
260260+ - Missing dependencies (errors properly when referencing non-existent cells)
261261+ - Dependency update propagation (type changes in d1 affect d2)
262262+ - Type shadowing across cells
263263+ - Complex dependency graphs with modules
264264+3. ✅ Added to dune build with expected output
265265+266266+**Key finding:** Dependencies are NOT transitive. If cell d4 needs types from d1
267267+through d2/d3, it must explicitly list d1 in its dependency array.
268268+269269+All 26 dependency tests pass.
270270+271271+### Phase 3: Error Recovery Tests (pending)
272272+273273+1. Create `test/node/node_error_test.ml`
274274+2. Test syntax errors, type errors, runtime errors
275275+3. Test state consistency after errors
276276+277277+### Phase 4: Expanded Findlib Tests (pending)
278278+279279+1. Add `test/cram/findlib.t/`
280280+2. Test more packages (lwt, ppx_deriving, etc.)
281281+3. Test error cases
282282+283283+## Decisions
284284+285285+1. **Browser test alias:** Separate `@browser` alias (not in default `runtest`)
286286+287287+2. **Browsers:** Chrome only for now
288288+289289+3. **Cell dependency semantics:**
290290+ - Circular deps → Error (unbound module)
291291+ - Missing deps → Error (unbound module)
292292+ - Dependencies are explicit, not transitive
293293+294294+4. **Error recovery:** TBD - needs investigation
295295+296296+5. **CI:** Browser tests advisory-only initially
···11+open Js_top_worker_rpc
22+module M = Idl.IdM (* Server is synchronous *)
33+module IdlM = Idl.Make (M)
44+module Client = Toplevel_api_gen.Make (IdlM.GenClient ())
55+module Cmds = Toplevel_api_gen.Make (Cmdlinergen.Gen ())
66+77+(* Use a binary 16-byte length to frame RPC messages *)
88+let binary_rpc path (call : Rpc.call) : Rpc.response =
99+ let sockaddr = Unix.ADDR_UNIX path in
1010+ let s = Unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
1111+ Unix.connect s sockaddr;
1212+ let ic = Unix.in_channel_of_descr s in
1313+ let oc = Unix.out_channel_of_descr s in
1414+ let msg_buf = Transport.Json.string_of_call call in
1515+ let len = Printf.sprintf "%016d" (String.length msg_buf) in
1616+ output_string oc len;
1717+ output_string oc msg_buf;
1818+ flush oc;
1919+ let len_buf = Bytes.make 16 '\000' in
2020+ really_input ic len_buf 0 16;
2121+ let len = int_of_string (Bytes.unsafe_to_string len_buf) in
2222+ let msg_buf = Bytes.make len '\000' in
2323+ really_input ic msg_buf 0 len;
2424+ let (response : Rpc.response) =
2525+ Transport.Json.response_of_string (Bytes.unsafe_to_string msg_buf)
2626+ in
2727+ response
2828+2929+let cli () =
3030+ let default =
3131+ Cmdliner.Term.(ret (const (fun _ -> `Help (`Pager, None)) $ const ()))
3232+ in
3333+ let info = Cmdliner.Cmd.info "cli" ~version:"1.6.1" ~doc:"a cli for an API" in
3434+ let rpc = binary_rpc Toplevel_api_gen.sockpath in
3535+ let cmds =
3636+ List.map
3737+ (fun t ->
3838+ let term, info = t rpc in
3939+ Cmdliner.(Cmd.v info Term.(term $ const ())))
4040+ (Cmds.implementation ())
4141+ in
4242+ let cmd = Cmdliner.Cmd.group ~default info cmds in
4343+ exit (Cmdliner.Cmd.eval cmd)
4444+4545+let () = cli ()
+213
js_top_worker/example/unix_worker.ml
···11+(* Unix worker *)
22+open Js_top_worker
33+open Impl
44+55+let capture f () =
66+ let stdout_backup = Unix.dup ~cloexec:true Unix.stdout in
77+ let stderr_backup = Unix.dup ~cloexec:true Unix.stderr in
88+ let filename_out = Filename.temp_file "ocaml-mdx-" ".stdout" in
99+ let filename_err = Filename.temp_file "ocaml-mdx-" ".stderr" in
1010+ let fd_out =
1111+ Unix.openfile filename_out
1212+ Unix.[ O_WRONLY; O_CREAT; O_TRUNC; O_CLOEXEC ]
1313+ 0o600
1414+ in
1515+ let fd_err =
1616+ Unix.openfile filename_err
1717+ Unix.[ O_WRONLY; O_CREAT; O_TRUNC; O_CLOEXEC ]
1818+ 0o600
1919+ in
2020+ Unix.dup2 ~cloexec:false fd_out Unix.stdout;
2121+ Unix.dup2 ~cloexec:false fd_err Unix.stderr;
2222+ let ic_out = open_in filename_out in
2323+ let ic_err = open_in filename_err in
2424+ let capture oc ic fd buf =
2525+ flush oc;
2626+ let len = Unix.lseek fd 0 Unix.SEEK_CUR in
2727+ Buffer.add_channel buf ic len
2828+ in
2929+ Fun.protect
3030+ (fun () ->
3131+ let x = f () in
3232+ let buf_out = Buffer.create 1024 in
3333+ let buf_err = Buffer.create 1024 in
3434+ capture stdout ic_out fd_out buf_out;
3535+ capture stderr ic_err fd_err buf_err;
3636+ ( {
3737+ Impl.stdout = Buffer.contents buf_out;
3838+ stderr = Buffer.contents buf_err;
3939+ },
4040+ x ))
4141+ ~finally:(fun () ->
4242+ close_in_noerr ic_out;
4343+ close_in_noerr ic_out;
4444+ Unix.close fd_out;
4545+ Unix.close fd_err;
4646+ Unix.dup2 ~cloexec:false stdout_backup Unix.stdout;
4747+ Unix.dup2 ~cloexec:false stderr_backup Unix.stderr;
4848+ Unix.close stdout_backup;
4949+ Unix.close stderr_backup;
5050+ Sys.remove filename_out;
5151+ Sys.remove filename_err)
5252+5353+let ( let* ) = Lwt.bind
5454+5555+let rec read_exact s buf off len =
5656+ if len <= 0 then Lwt.return ()
5757+ else
5858+ let* n = Lwt_unix.read s buf off len in
5959+ if n = 0 then Lwt.fail End_of_file
6060+ else read_exact s buf (off + n) (len - n)
6161+6262+let binary_handler process s =
6363+ (* Read a 16 byte length encoded as a string *)
6464+ let len_buf = Bytes.make 16 '\000' in
6565+ let* () = read_exact s len_buf 0 16 in
6666+ let len = int_of_string (Bytes.unsafe_to_string len_buf) in
6767+ let msg_buf = Bytes.make len '\000' in
6868+ let* () = read_exact s msg_buf 0 len in
6969+ let* result = process msg_buf in
7070+ let len_buf = Printf.sprintf "%016d" (String.length result) in
7171+ let* _ = Lwt_unix.write s (Bytes.of_string len_buf) 0 16 in
7272+ let* _ = Lwt_unix.write s (Bytes.of_string result) 0 (String.length result) in
7373+ Lwt.return ()
7474+7575+let mkdir_rec dir perm =
7676+ let rec p_mkdir dir =
7777+ let p_name = Filename.dirname dir in
7878+ if p_name <> "/" && p_name <> "." then p_mkdir p_name;
7979+ try Unix.mkdir dir perm with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
8080+ in
8181+ p_mkdir dir
8282+8383+let serve_requests rpcfn path ~ready_fd =
8484+ let ( let* ) = Lwt.bind in
8585+ (try Unix.unlink path with Unix.Unix_error (Unix.ENOENT, _, _) -> ());
8686+ mkdir_rec (Filename.dirname path) 0o0755;
8787+ let sock = Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in
8888+ let* () = Lwt_unix.bind sock (Unix.ADDR_UNIX path) in
8989+ Lwt_unix.listen sock 5;
9090+ (* Signal readiness via pipe to parent process *)
9191+ (match ready_fd with
9292+ | Some fd ->
9393+ ignore (Unix.write fd (Bytes.of_string "R") 0 1);
9494+ Unix.close fd
9595+ | None -> ());
9696+ let rec loop () =
9797+ let* this_connection, _ = Lwt_unix.accept sock in
9898+ let* () =
9999+ Lwt.finalize
100100+ (fun () ->
101101+ (* Here I am calling M.run to make sure that I am running the process,
102102+ this is not much of a problem with IdM or ExnM, but in general you
103103+ should ensure that the computation is started by a runner. *)
104104+ binary_handler rpcfn this_connection)
105105+ (fun () -> Lwt_unix.close this_connection)
106106+ in
107107+ loop ()
108108+ in
109109+ loop ()
110110+111111+let handle_findlib_error = function
112112+ | Failure msg -> Printf.fprintf stderr "%s" msg
113113+ | Fl_package_base.No_such_package (pkg, reason) ->
114114+ Printf.fprintf stderr "No such package: %s%s\n" pkg
115115+ (if reason <> "" then " - " ^ reason else "")
116116+ | Fl_package_base.Package_loop pkg ->
117117+ Printf.fprintf stderr "Package requires itself: %s\n" pkg
118118+ | exn -> raise exn
119119+120120+module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
121121+122122+module S : Impl.S = struct
123123+ type findlib_t = unit
124124+125125+ let capture = capture
126126+ let sync_get _ = None
127127+ let async_get _ = Lwt.return (Error (`Msg "Not implemented"))
128128+ let create_file ~name:_ ~content:_ = failwith "Not implemented"
129129+130130+ let import_scripts urls =
131131+ if List.length urls > 0 then failwith "Not implemented" else ()
132132+133133+ let init_function _ () = failwith "Not implemented"
134134+ let findlib_init _ = Lwt.return ()
135135+ let get_stdlib_dcs _uri = []
136136+137137+ let require _ () packages =
138138+ try
139139+ let eff_packages =
140140+ Findlib.package_deep_ancestors !Topfind.predicates packages
141141+ in
142142+ Topfind.load eff_packages;
143143+ []
144144+ with exn ->
145145+ handle_findlib_error exn;
146146+ []
147147+148148+ let path = "/tmp"
149149+end
150150+151151+module U = Impl.Make (S)
152152+153153+(* let test () =
154154+ let _x = Compmisc.initial_env in
155155+ let oc = open_out "/tmp/unix_worker.ml" in
156156+ Printf.fprintf oc "let x=1;;\n";
157157+ close_out oc;
158158+ let unit_info = Unit_info.make ~source_file:"/tmp/unix_worker.ml" "/tmp/unix_worker" in
159159+ try
160160+ let _ast = Pparse.parse_implementation ~tool_name:"worker" "/tmp/unix_worker.ml" in
161161+ let _ = Typemod.type_implementation unit_info (Compmisc.initial_env ()) _ast in
162162+ ()
163163+ with exn ->
164164+ Printf.eprintf "error: %s\n%!" (Printexc.to_string exn);
165165+ let ppf = Format.err_formatter in
166166+ let _ = Location.report_exception ppf exn in
167167+ () *)
168168+169169+let start_server ~ready_fd =
170170+ let open U in
171171+ Logs.set_reporter (Logs_fmt.reporter ());
172172+ Logs.set_level (Some Logs.Warning);
173173+ Server.init (IdlM.T.lift init);
174174+ Server.create_env (IdlM.T.lift create_env);
175175+ Server.destroy_env (IdlM.T.lift destroy_env);
176176+ Server.list_envs (IdlM.T.lift list_envs);
177177+ Server.setup (IdlM.T.lift setup);
178178+ Server.exec execute;
179179+ Server.complete_prefix complete_prefix;
180180+ Server.query_errors query_errors;
181181+ Server.type_enclosing type_enclosing;
182182+ Server.exec_toplevel exec_toplevel;
183183+ let rpc_fn = IdlM.server Server.implementation in
184184+ let process x =
185185+ let open Lwt in
186186+ let _, call = Js_top_worker_rpc.Transport.Json.id_and_call_of_string (Bytes.unsafe_to_string x) in
187187+ rpc_fn call >>= fun response ->
188188+ Js_top_worker_rpc.Transport.Json.string_of_response ~id:(Rpc.Int 0L) response |> return
189189+ in
190190+ serve_requests process Js_top_worker_rpc.Toplevel_api_gen.sockpath ~ready_fd
191191+192192+let () =
193193+ (* Fork so parent only exits once child is ready to accept connections *)
194194+ let read_fd, write_fd = Unix.pipe ~cloexec:false () in
195195+ match Unix.fork () with
196196+ | 0 ->
197197+ (* Child: close read end and detach from terminal *)
198198+ Unix.close read_fd;
199199+ (* Redirect stdout/stderr to /dev/null so parent's $() can complete *)
200200+ let dev_null = Unix.openfile "/dev/null" [Unix.O_RDWR] 0 in
201201+ Unix.dup2 dev_null Unix.stdout;
202202+ Unix.dup2 dev_null Unix.stderr;
203203+ Unix.close dev_null;
204204+ (* Run server, signal via write end *)
205205+ Lwt_main.run (start_server ~ready_fd:(Some write_fd))
206206+ | child_pid ->
207207+ (* Parent: close write end, wait for ready signal, print child PID, exit *)
208208+ Unix.close write_fd;
209209+ let buf = Bytes.create 1 in
210210+ ignore (Unix.read read_fd buf 0 1);
211211+ Unix.close read_fd;
212212+ Printf.printf "%d\n%!" child_pid
213213+ (* Parent exits here, child continues serving *)
···11+let logfn = ref (fun (_ : string) -> ())
22+33+module Param = struct
44+ type 'a t = {
55+ name : string option;
66+ description : string list;
77+ typedef : 'a Rpc.Types.def;
88+ version : Rpc.Version.t option;
99+ }
1010+1111+ type boxed = Boxed : 'a t -> boxed
1212+1313+ let mk ?name ?description ?version typedef =
1414+ let description =
1515+ match description with
1616+ | Some d -> d
1717+ | None -> typedef.Rpc.Types.description
1818+ in
1919+ { name; description; version; typedef }
2020+end
2121+2222+module Error = struct
2323+ type 'a t = {
2424+ def : 'a Rpc.Types.def;
2525+ raiser : 'a -> exn;
2626+ matcher : exn -> 'a option;
2727+ }
2828+2929+ module type ERROR = sig
3030+ type t
3131+3232+ val t : t Rpc.Types.def
3333+ val internal_error_of : exn -> t option
3434+ end
3535+3636+ module Make (T : ERROR) = struct
3737+ exception Exn of T.t
3838+3939+ let () =
4040+ let printer = function
4141+ | Exn x ->
4242+ Some
4343+ (Printf.sprintf "IDL Error: %s"
4444+ (Rpcmarshal.marshal T.t.Rpc.Types.ty x |> Rpc.to_string))
4545+ | _ -> None
4646+ in
4747+ Printexc.register_printer printer
4848+4949+ let error =
5050+ {
5151+ def = T.t;
5252+ raiser = (function e -> Exn e);
5353+ matcher = (function Exn e -> Some e | e -> T.internal_error_of e);
5454+ }
5555+ end
5656+end
5757+5858+module Interface = struct
5959+ type description = {
6060+ name : string;
6161+ namespace : string option;
6262+ description : string list;
6363+ version : Rpc.Version.t;
6464+ }
6565+end
6666+6767+module type RPC = sig
6868+ type implementation
6969+ type 'a res
7070+ type ('a, 'b) comp
7171+ type _ fn
7272+7373+ val implement : Interface.description -> implementation
7474+ val ( @-> ) : 'a Param.t -> 'b fn -> ('a -> 'b) fn
7575+ val returning : 'a Param.t -> 'b Error.t -> ('a, 'b) comp fn
7676+ val declare : string -> string list -> 'a fn -> 'a res
7777+ val declare_notification : string -> string list -> 'a fn -> 'a res
7878+end
7979+8080+module type MONAD = sig
8181+ type 'a t
8282+8383+ val return : 'a -> 'a t
8484+ val bind : 'a t -> ('a -> 'b t) -> 'b t
8585+ val fail : exn -> 'a t
8686+end
8787+8888+exception MarshalError of string
8989+exception UnknownMethod of string
9090+exception UnboundImplementation of string list
9191+exception NoDescription
9292+9393+let get_wire_name description name =
9494+ match description with
9595+ | None -> name
9696+ | Some d -> (
9797+ match d.Interface.namespace with
9898+ | Some ns -> Printf.sprintf "%s.%s" ns name
9999+ | None -> name)
100100+101101+let get_arg call has_named name is_opt =
102102+ match (has_named, name, call.Rpc.params) with
103103+ | true, Some n, Rpc.Dict named :: unnamed -> (
104104+ match List.partition (fun (x, _) -> x = n) named with
105105+ | (_, arg) :: dups, others when is_opt ->
106106+ Ok
107107+ ( Rpc.Enum [ arg ],
108108+ { call with Rpc.params = Rpc.Dict (dups @ others) :: unnamed } )
109109+ | (_, arg) :: dups, others ->
110110+ Ok
111111+ (arg, { call with Rpc.params = Rpc.Dict (dups @ others) :: unnamed })
112112+ | [], _others when is_opt -> Ok (Rpc.Enum [], call)
113113+ | _, _ -> Error (`Msg (Printf.sprintf "Expecting named argument '%s'" n)))
114114+ | true, None, Rpc.Dict named :: unnamed -> (
115115+ match unnamed with
116116+ | head :: tail ->
117117+ Ok (head, { call with Rpc.params = Rpc.Dict named :: tail })
118118+ | _ -> Error (`Msg "Incorrect number of arguments"))
119119+ | true, _, _ ->
120120+ Error
121121+ (`Msg
122122+ "Marshalling error: Expecting dict as first argument when named \
123123+ parameters exist")
124124+ | false, None, head :: tail -> Ok (head, { call with Rpc.params = tail })
125125+ | false, None, [] -> Error (`Msg "Incorrect number of arguments")
126126+ | false, Some _, _ -> failwith "Can't happen by construction"
127127+128128+module Make (M : MONAD) = struct
129129+ module type RPCTRANSFORMER = sig
130130+ type 'a box
131131+ type ('a, 'b) resultb = ('a, 'b) result box
132132+ type rpcfn = Rpc.call -> Rpc.response M.t
133133+134134+ val lift : ('a -> 'b M.t) -> 'a -> 'b box
135135+ val bind : 'a box -> ('a -> 'b M.t) -> 'b box
136136+ val return : 'a -> 'a box
137137+ val get : 'a box -> 'a M.t
138138+ val ( !@ ) : 'a box -> 'a M.t
139139+ val put : 'a M.t -> 'a box
140140+ val ( ~@ ) : 'a M.t -> 'a box
141141+ end
142142+143143+ module T = struct
144144+ type 'a box = { box : 'a M.t }
145145+ type ('a, 'b) resultb = ('a, 'b) result box
146146+ type rpcfn = Rpc.call -> Rpc.response M.t
147147+148148+ let lift f x = { box = f x }
149149+ let bind { box = x } f = { box = M.bind x f }
150150+ let return x = { box = M.return x }
151151+ let get { box = x } = x
152152+ let ( !@ ) = get
153153+ let put x = { box = x }
154154+ let ( ~@ ) = put
155155+ end
156156+157157+ type client_implementation = unit
158158+ type server_implementation = (string, T.rpcfn option) Hashtbl.t
159159+160160+ module ErrM : sig
161161+ val return : 'a -> ('a, 'b) T.resultb
162162+ val return_err : 'b -> ('a, 'b) T.resultb
163163+164164+ val checked_bind :
165165+ ('a, 'b) T.resultb ->
166166+ ('a -> ('c, 'd) T.resultb) ->
167167+ ('b -> ('c, 'd) T.resultb) ->
168168+ ('c, 'd) T.resultb
169169+170170+ val bind :
171171+ ('a, 'b) T.resultb -> ('a -> ('c, 'b) T.resultb) -> ('c, 'b) T.resultb
172172+173173+ val ( >>= ) :
174174+ ('a, 'b) T.resultb -> ('a -> ('c, 'b) T.resultb) -> ('c, 'b) T.resultb
175175+ end = struct
176176+ let return x = T.put (M.return (Ok x))
177177+ let return_err e = T.put (M.return (Error e))
178178+179179+ let checked_bind x f f1 =
180180+ T.bind x T.(function Ok x -> !@(f x) | Error x -> !@(f1 x))
181181+182182+ let bind x f = checked_bind x f return_err
183183+ let ( >>= ) x f = bind x f
184184+ end
185185+186186+ module GenClient () = struct
187187+ type implementation = client_implementation
188188+ type 'a res = T.rpcfn -> 'a
189189+ type ('a, 'b) comp = ('a, 'b) T.resultb
190190+191191+ type _ fn =
192192+ | Function : 'a Param.t * 'b fn -> ('a -> 'b) fn
193193+ | Returning : ('a Param.t * 'b Error.t) -> ('a, 'b) comp fn
194194+195195+ let description = ref None
196196+ let strict = ref false
197197+ let make_strict () = strict := true
198198+199199+ let implement x =
200200+ description := Some x;
201201+ ()
202202+203203+ let returning a err = Returning (a, err)
204204+ let ( @-> ) t f = Function (t, f)
205205+206206+ let declare_ is_notification name _ ty (rpc : T.rpcfn) =
207207+ let rec inner :
208208+ type b. (string * Rpc.t) list option * Rpc.t list -> b fn -> b =
209209+ fun (named, unnamed) -> function
210210+ | Function (t, f) -> (
211211+ let cur_named = match named with Some l -> l | None -> [] in
212212+ fun v ->
213213+ match t.Param.name with
214214+ | Some n -> (
215215+ match (t.Param.typedef.Rpc.Types.ty, v) with
216216+ | Rpc.Types.Option ty, Some v' ->
217217+ let marshalled = Rpcmarshal.marshal ty v' in
218218+ inner (Some ((n, marshalled) :: cur_named), unnamed) f
219219+ | Rpc.Types.Option _ty, None ->
220220+ inner (Some cur_named, unnamed) f
221221+ | ty, v ->
222222+ let marshalled = Rpcmarshal.marshal ty v in
223223+ inner (Some ((n, marshalled) :: cur_named), unnamed) f)
224224+ | None ->
225225+ let marshalled =
226226+ Rpcmarshal.marshal t.Param.typedef.Rpc.Types.ty v
227227+ in
228228+ inner (named, marshalled :: unnamed) f)
229229+ | Returning (t, e) ->
230230+ let wire_name = get_wire_name !description name in
231231+ let args =
232232+ match named with
233233+ | None -> List.rev unnamed
234234+ | Some l -> Rpc.Dict l :: List.rev unnamed
235235+ in
236236+ let call' = Rpc.call wire_name args in
237237+ let call = { call' with is_notification } in
238238+ let rpc = T.put (rpc call) in
239239+ let res =
240240+ T.bind rpc (fun r ->
241241+ if r.Rpc.success then
242242+ match
243243+ Rpcmarshal.unmarshal t.Param.typedef.Rpc.Types.ty
244244+ r.Rpc.contents
245245+ with
246246+ | Ok x -> M.return (Ok x)
247247+ | Error (`Msg x) -> M.fail (MarshalError x)
248248+ else
249249+ match
250250+ Rpcmarshal.unmarshal e.Error.def.Rpc.Types.ty
251251+ r.Rpc.contents
252252+ with
253253+ | Ok x ->
254254+ if !strict then M.fail (e.Error.raiser x)
255255+ else M.return (Error x)
256256+ | Error (`Msg x) -> M.fail (MarshalError x))
257257+ in
258258+ res
259259+ in
260260+ inner (None, []) ty
261261+262262+ let declare_notification name a ty (rpc : T.rpcfn) =
263263+ declare_ true name a ty rpc
264264+265265+ let declare name a ty (rpc : T.rpcfn) = declare_ false name a ty rpc
266266+ end
267267+268268+ let server hashtbl =
269269+ let impl = Hashtbl.create (Hashtbl.length hashtbl) in
270270+ let unbound_impls =
271271+ Hashtbl.fold
272272+ (fun key fn acc ->
273273+ match fn with
274274+ | None -> key :: acc
275275+ | Some fn ->
276276+ Hashtbl.add impl key fn;
277277+ acc)
278278+ hashtbl []
279279+ in
280280+ if unbound_impls <> [] then raise (UnboundImplementation unbound_impls);
281281+ fun call ->
282282+ let fn =
283283+ try Hashtbl.find impl call.Rpc.name
284284+ with Not_found ->
285285+ !logfn "1";
286286+ Hashtbl.iter
287287+ (fun key _ ->
288288+ !logfn ("method: " ^ key ^ (Hashtbl.hash key |> string_of_int));
289289+ !logfn key)
290290+ impl;
291291+ let _h = Hashtbl.hash call.Rpc.name in
292292+293293+ !logfn
294294+ (Printf.sprintf "Unknown method: %s %d" call.Rpc.name
295295+ (Hashtbl.hash call.Rpc.name));
296296+ !logfn call.Rpc.name;
297297+ raise (UnknownMethod call.Rpc.name)
298298+ in
299299+ fn call
300300+301301+ let combine hashtbls =
302302+ let result = Hashtbl.create 16 in
303303+ List.iter (Hashtbl.iter (fun k v -> Hashtbl.add result k v)) hashtbls;
304304+ result
305305+306306+ module GenServer () = struct
307307+ type implementation = server_implementation
308308+ type ('a, 'b) comp = ('a, 'b) T.resultb
309309+ type 'a res = 'a -> unit
310310+311311+ type _ fn =
312312+ | Function : 'a Param.t * 'b fn -> ('a -> 'b) fn
313313+ | Returning : ('a Param.t * 'b Error.t) -> ('a, 'b) comp fn
314314+315315+ let funcs = Hashtbl.create 20
316316+ let description = ref None
317317+318318+ let implement x =
319319+ description := Some x;
320320+ funcs
321321+322322+ let returning a b = Returning (a, b)
323323+ let ( @-> ) t f = Function (t, f)
324324+325325+ let rec has_named_args : type a. a fn -> bool = function
326326+ | Function (t, f) -> (
327327+ match t.Param.name with Some _ -> true | None -> has_named_args f)
328328+ | Returning (_, _) -> false
329329+330330+ let declare_ : bool -> string -> string list -> 'a fn -> 'a res =
331331+ fun is_notification name _ ty ->
332332+ let ( >>= ) = M.bind in
333333+ (* We do not know the wire name yet as the description may still be unset *)
334334+ Hashtbl.add funcs name None;
335335+ fun impl ->
336336+ (* Sanity check: ensure the description has been set before we declare
337337+ any RPCs. Here we raise an exception immediately and let everything fail. *)
338338+ (match !description with Some _ -> () | None -> raise NoDescription);
339339+ let rpcfn =
340340+ let has_named = has_named_args ty in
341341+ let rec inner : type a. a fn -> a -> T.rpcfn =
342342+ fun f impl call ->
343343+ match f with
344344+ | Function (t, f) -> (
345345+ let is_opt =
346346+ match t.Param.typedef.Rpc.Types.ty with
347347+ | Rpc.Types.Option _ -> true
348348+ | _ -> false
349349+ in
350350+ (match get_arg call has_named t.Param.name is_opt with
351351+ | Ok (x, y) -> M.return (x, y)
352352+ | Error (`Msg m) -> M.fail (MarshalError m))
353353+ >>= fun (arg_rpc, call') ->
354354+ let z =
355355+ Rpcmarshal.unmarshal t.Param.typedef.Rpc.Types.ty arg_rpc
356356+ in
357357+ match z with
358358+ | Ok arg -> inner f (impl arg) call'
359359+ | Error (`Msg m) -> M.fail (MarshalError m))
360360+ | Returning (t, e) ->
361361+ T.bind impl (function
362362+ | Ok x ->
363363+ let res =
364364+ Rpc.success
365365+ (Rpcmarshal.marshal t.Param.typedef.Rpc.Types.ty x)
366366+ in
367367+ M.return { res with is_notification }
368368+ | Error y ->
369369+ let res =
370370+ Rpc.failure
371371+ (Rpcmarshal.marshal e.Error.def.Rpc.Types.ty y)
372372+ in
373373+ M.return { res with is_notification })
374374+ |> T.get
375375+ in
376376+ inner ty impl
377377+ in
378378+ Hashtbl.remove funcs name;
379379+ (* The wire name might be different from the name *)
380380+ let wire_name = get_wire_name !description name in
381381+ Hashtbl.add funcs wire_name (Some rpcfn)
382382+383383+ let declare_notification name a ty = declare_ true name a ty
384384+ let declare name a ty = declare_ false name a ty
385385+ end
386386+end
387387+388388+module ExnM = struct
389389+ type 'a t = V of 'a | E of exn
390390+391391+ let return x = V x
392392+ let lift f x = match f x with y -> V y | exception e -> E e
393393+ let bind x (f : 'a -> 'b t) : 'b t = match x with V x -> f x | E e -> E e
394394+ let ( >>= ) = bind
395395+ let fail e = E e
396396+ let run = function V x -> x | E e -> raise e
397397+end
398398+399399+module IdM = struct
400400+ type 'a t = T of 'a
401401+402402+ let return x = T x
403403+ let lift f x = T (f x)
404404+ let bind (T x) f = f x
405405+ let ( >>= ) = bind
406406+ let fail e = raise e
407407+ let run (T x) = x
408408+end
409409+410410+(* A default error variant as an example. In real code, this is more easily expressed by using the PPX:
411411+ type default_error = InternalError of string [@@deriving rpcty]
412412+*)
413413+module DefaultError = struct
414414+ type t = InternalError of string
415415+416416+ exception InternalErrorExn of string
417417+418418+ let internalerror : (string, t) Rpc.Types.tag =
419419+ let open Rpc.Types in
420420+ {
421421+ tname = "InternalError";
422422+ tdescription = [ "Internal Error" ];
423423+ tversion = Some (1, 0, 0);
424424+ tcontents = Basic String;
425425+ tpreview = (function InternalError s -> Some s);
426426+ treview = (fun s -> InternalError s);
427427+ }
428428+429429+ (* And then we can create the 'variant' type *)
430430+ let t : t Rpc.Types.variant =
431431+ let open Rpc.Types in
432432+ {
433433+ vname = "t";
434434+ variants = [ BoxedTag internalerror ];
435435+ vversion = Some (1, 0, 0);
436436+ vdefault = Some (InternalError "Unknown error tag!");
437437+ vconstructor =
438438+ (fun s t ->
439439+ match s with
440440+ | "InternalError" -> (
441441+ match t.tget (Basic String) with
442442+ | Ok s -> Ok (internalerror.treview s)
443443+ | Error y -> Error y)
444444+ | s -> Error (`Msg (Printf.sprintf "Unknown tag '%s'" s)));
445445+ }
446446+447447+ let def =
448448+ let open Rpc.Types in
449449+ {
450450+ name = "default_error";
451451+ description = [ "Errors declared as part of the interface" ];
452452+ ty = Variant t;
453453+ }
454454+455455+ let err =
456456+ let open Error in
457457+ {
458458+ def;
459459+ raiser = (function InternalError s -> raise (InternalErrorExn s));
460460+ matcher =
461461+ (function InternalErrorExn s -> Some (InternalError s) | _ -> None);
462462+ }
463463+end
464464+465465+module Exn = struct
466466+ type rpcfn = Rpc.call -> Rpc.response
467467+ type client_implementation = unit
468468+ type server_implementation = (string, rpcfn option) Hashtbl.t
469469+470470+ module GenClient (R : sig
471471+ val rpc : rpcfn
472472+ end) =
473473+ struct
474474+ type implementation = client_implementation
475475+ type ('a, 'b) comp = 'a
476476+ type 'a res = 'a
477477+478478+ type _ fn =
479479+ | Function : 'a Param.t * 'b fn -> ('a -> 'b) fn
480480+ | Returning : ('a Param.t * 'b Error.t) -> ('a, _) comp fn
481481+482482+ let description = ref None
483483+484484+ let implement x =
485485+ description := Some x;
486486+ ()
487487+488488+ let returning a err = Returning (a, err)
489489+ let ( @-> ) t f = Function (t, f)
490490+491491+ let declare_ is_notification name _ ty =
492492+ let rec inner :
493493+ type b. (string * Rpc.t) list option * Rpc.t list -> b fn -> b =
494494+ fun (named, unnamed) -> function
495495+ | Function (t, f) -> (
496496+ let cur_named = match named with Some l -> l | None -> [] in
497497+ fun v ->
498498+ match t.Param.name with
499499+ | Some n -> (
500500+ match (t.Param.typedef.Rpc.Types.ty, v) with
501501+ | Rpc.Types.Option ty, Some v' ->
502502+ let marshalled = Rpcmarshal.marshal ty v' in
503503+ inner (Some ((n, marshalled) :: cur_named), unnamed) f
504504+ | Rpc.Types.Option _ty, None ->
505505+ inner (Some cur_named, unnamed) f
506506+ | ty, v ->
507507+ let marshalled = Rpcmarshal.marshal ty v in
508508+ inner (Some ((n, marshalled) :: cur_named), unnamed) f)
509509+ | None ->
510510+ let marshalled =
511511+ Rpcmarshal.marshal t.Param.typedef.Rpc.Types.ty v
512512+ in
513513+ inner (named, marshalled :: unnamed) f)
514514+ | Returning (t, e) -> (
515515+ let wire_name = get_wire_name !description name in
516516+ let args =
517517+ match named with
518518+ | None -> List.rev unnamed
519519+ | Some l -> Rpc.Dict l :: List.rev unnamed
520520+ in
521521+ let call' = Rpc.call wire_name args in
522522+ let call = { call' with is_notification } in
523523+ let r = R.rpc call in
524524+ if r.Rpc.success then
525525+ match
526526+ Rpcmarshal.unmarshal t.Param.typedef.Rpc.Types.ty r.Rpc.contents
527527+ with
528528+ | Ok x -> x
529529+ | Error (`Msg x) -> raise (MarshalError x)
530530+ else
531531+ match
532532+ Rpcmarshal.unmarshal e.Error.def.Rpc.Types.ty r.Rpc.contents
533533+ with
534534+ | Ok x -> raise (e.Error.raiser x)
535535+ | Error (`Msg x) -> raise (MarshalError x))
536536+ in
537537+ inner (None, []) ty
538538+539539+ let declare name a ty = declare_ false name a ty
540540+ let declare_notification name a ty = declare_ true name a ty
541541+ end
542542+543543+ let server hashtbl =
544544+ let impl = Hashtbl.create (Hashtbl.length hashtbl) in
545545+ let unbound_impls =
546546+ Hashtbl.fold
547547+ (fun key fn acc ->
548548+ match fn with
549549+ | None -> key :: acc
550550+ | Some fn ->
551551+ Hashtbl.add impl key fn;
552552+ acc)
553553+ hashtbl []
554554+ in
555555+ if unbound_impls <> [] then raise (UnboundImplementation unbound_impls);
556556+ fun call ->
557557+ let fn =
558558+ try Hashtbl.find impl call.Rpc.name
559559+ with Not_found ->
560560+ !logfn "2";
561561+ Hashtbl.iter (fun key _ -> !logfn ("method: " ^ key)) impl;
562562+ !logfn (Printf.sprintf "Unknown method: %s" call.Rpc.name);
563563+ raise (UnknownMethod call.Rpc.name)
564564+ in
565565+ fn call
566566+567567+ let combine hashtbls =
568568+ let result = Hashtbl.create 16 in
569569+ List.iter (Hashtbl.iter (fun k v -> Hashtbl.add result k v)) hashtbls;
570570+ result
571571+572572+ module GenServer () = struct
573573+ type implementation = server_implementation
574574+ type ('a, 'b) comp = 'a
575575+ type 'a res = 'a -> unit
576576+577577+ type _ fn =
578578+ | Function : 'a Param.t * 'b fn -> ('a -> 'b) fn
579579+ | Returning : ('a Param.t * 'b Error.t) -> ('a, _) comp fn
580580+581581+ let funcs = Hashtbl.create 20
582582+ let description = ref None
583583+584584+ let implement x =
585585+ description := Some x;
586586+ funcs
587587+588588+ let returning a b = Returning (a, b)
589589+ let ( @-> ) t f = Function (t, f)
590590+591591+ type boxed_error = BoxedError : 'a Error.t -> boxed_error
592592+593593+ let rec get_error_ty : type a. a fn -> boxed_error = function
594594+ | Function (_, f) -> get_error_ty f
595595+ | Returning (_, e) -> BoxedError e
596596+597597+ let rec has_named_args : type a. a fn -> bool = function
598598+ | Function (t, f) -> (
599599+ match t.Param.name with Some _ -> true | None -> has_named_args f)
600600+ | Returning (_, _) -> false
601601+602602+ let declare_ : bool -> string -> string list -> 'a fn -> 'a res =
603603+ fun is_notification name _ ty ->
604604+ (* We do not know the wire name yet as the description may still be unset *)
605605+ Hashtbl.add funcs name None;
606606+ fun impl ->
607607+ (* Sanity check: ensure the description has been set before we declare
608608+ any RPCs *)
609609+ (match !description with Some _ -> () | None -> raise NoDescription);
610610+ let rpcfn =
611611+ let has_named = has_named_args ty in
612612+ let rec inner : type a. a fn -> a -> Rpc.call -> Rpc.response =
613613+ fun f impl call ->
614614+ try
615615+ match f with
616616+ | Function (t, f) ->
617617+ let is_opt =
618618+ match t.Param.typedef.Rpc.Types.ty with
619619+ | Rpc.Types.Option _ -> true
620620+ | _ -> false
621621+ in
622622+ let arg_rpc, call' =
623623+ match get_arg call has_named t.Param.name is_opt with
624624+ | Ok (x, y) -> (x, y)
625625+ | Error (`Msg m) -> raise (MarshalError m)
626626+ in
627627+ let z =
628628+ Rpcmarshal.unmarshal t.Param.typedef.Rpc.Types.ty arg_rpc
629629+ in
630630+ let arg =
631631+ match z with
632632+ | Ok arg -> arg
633633+ | Error (`Msg m) -> raise (MarshalError m)
634634+ in
635635+ inner f (impl arg) call'
636636+ | Returning (t, _) ->
637637+ let call =
638638+ Rpc.success
639639+ (Rpcmarshal.marshal t.Param.typedef.Rpc.Types.ty impl)
640640+ in
641641+ { call with is_notification }
642642+ with e -> (
643643+ let (BoxedError error_ty) = get_error_ty f in
644644+ match error_ty.Error.matcher e with
645645+ | Some y ->
646646+ Rpc.failure
647647+ (Rpcmarshal.marshal error_ty.Error.def.Rpc.Types.ty y)
648648+ | None -> raise e)
649649+ in
650650+ inner ty impl
651651+ in
652652+ Hashtbl.remove funcs name;
653653+ (* The wire name might be different from the name *)
654654+ let wire_name = get_wire_name !description name in
655655+ Hashtbl.add funcs wire_name (Some rpcfn)
656656+657657+ let declare name a ty = declare_ true name a ty
658658+ let declare_notification name a ty = declare_ false name a ty
659659+ end
660660+end
+312
js_top_worker/idl/_old/jsonrpc.ml
···11+(*
22+ * Copyright (c) 2006-2009 Citrix Systems Inc.
33+ * Copyright (c) 2006-2014 Thomas Gazagnaire <thomas@gazagnaire.org>
44+ *
55+ * Permission to use, copy, modify, and distribute this software for any
66+ * purpose with or without fee is hereby granted, provided that the above
77+ * copyright notice and this permission notice appear in all copies.
88+ *
99+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616+ *)
1717+1818+open Rpc
1919+2020+module Yojson_private = struct
2121+ include Yojson.Safe
2222+2323+ let from_string ?(strict = true) ?buf ?fname ?lnum s =
2424+ let open Yojson in
2525+ try
2626+ let lexbuf = Lexing.from_string s in
2727+ let v = init_lexer ?buf ?fname ?lnum () in
2828+ if strict then from_lexbuf v lexbuf else from_lexbuf v ~stream:true lexbuf
2929+ with End_of_input -> json_error "Blank input data"
3030+end
3131+3232+module Y = Yojson_private
3333+module U = Yojson.Basic.Util
3434+3535+type version = V1 | V2
3636+3737+let rec rpc_to_json t =
3838+ match t with
3939+ | Int i -> `Intlit (Int64.to_string i)
4040+ | Int32 i -> `Int (Int32.to_int i)
4141+ | Bool b -> `Bool b
4242+ | Float r -> `Float r
4343+ | String s -> `String s
4444+ | DateTime d -> `String d
4545+ | Base64 b -> `String b
4646+ | Null -> `Null
4747+ | Enum a -> `List (Rpcmarshal.tailrec_map rpc_to_json a)
4848+ | Dict a ->
4949+ `Assoc (Rpcmarshal.tailrec_map (fun (k, v) -> (k, rpc_to_json v)) a)
5050+5151+exception JsonToRpcError of Y.t
5252+5353+let rec json_to_rpc t =
5454+ match t with
5555+ | `Intlit i -> Int (Int64.of_string i)
5656+ | `Int i -> Int (Int64.of_int i)
5757+ | `Bool b -> Bool b
5858+ | `Float r -> Float r
5959+ | `String s -> (* TODO: check if it is a DateTime *) String s
6060+ (* | DateTime d -> `String d *)
6161+ (* | Base64 b -> `String b *)
6262+ | `Null -> Null
6363+ | `List a -> Enum (Rpcmarshal.tailrec_map json_to_rpc a)
6464+ | `Assoc a ->
6565+ Dict (Rpcmarshal.tailrec_map (fun (k, v) -> (k, json_to_rpc v)) a)
6666+ | unsupported -> raise (JsonToRpcError unsupported)
6767+6868+let to_fct t f = rpc_to_json t |> Y.to_string |> f
6969+let to_buffer t buf = to_fct t (fun s -> Buffer.add_string buf s)
7070+let to_string t = rpc_to_json t |> Y.to_string
7171+7272+let to_a ~empty ~append t =
7373+ let buf = empty () in
7474+ to_fct t (fun s -> append buf s);
7575+ buf
7676+7777+let new_id =
7878+ let count = ref 0L in
7979+ fun () ->
8080+ count := Int64.add 1L !count;
8181+ !count
8282+8383+let string_of_call ?(version = V1) call =
8484+ let json =
8585+ match version with
8686+ | V1 -> [ ("method", String call.name); ("params", Enum call.params) ]
8787+ | V2 ->
8888+ let params =
8989+ match call.params with [ Dict x ] -> Dict x | _ -> Enum call.params
9090+ in
9191+ [
9292+ ("jsonrpc", String "2.0");
9393+ ("method", String call.name);
9494+ ("params", params);
9595+ ]
9696+ in
9797+ let json =
9898+ if not call.is_notification then json @ [ ("id", Int (new_id ())) ]
9999+ else json
100100+ in
101101+ to_string (Dict json)
102102+103103+let json_of_response ?(id = Int 0L) version response =
104104+ if response.Rpc.success then
105105+ match version with
106106+ | V1 ->
107107+ Dict [ ("result", response.Rpc.contents); ("error", Null); ("id", id) ]
108108+ | V2 ->
109109+ Dict
110110+ [
111111+ ("jsonrpc", String "2.0");
112112+ ("result", response.Rpc.contents);
113113+ ("id", id);
114114+ ]
115115+ else
116116+ match version with
117117+ | V1 ->
118118+ Dict [ ("result", Null); ("error", response.Rpc.contents); ("id", id) ]
119119+ | V2 ->
120120+ Dict
121121+ [
122122+ ("jsonrpc", String "2.0");
123123+ ("error", response.Rpc.contents);
124124+ ("id", id);
125125+ ]
126126+127127+let json_of_error_object ?(data = None) code message =
128128+ let data_json = match data with Some d -> [ ("data", d) ] | None -> [] in
129129+ Dict ([ ("code", Int code); ("message", String message) ] @ data_json)
130130+131131+let string_of_response ?(id = Int 0L) ?(version = V1) response =
132132+ let json = json_of_response ~id version response in
133133+ to_string json
134134+135135+let a_of_response ?(id = Int 0L) ?(version = V1) ~empty ~append response =
136136+ let json = json_of_response ~id version response in
137137+ to_a ~empty ~append json
138138+139139+let of_string ?(strict = true) s = s |> Y.from_string ~strict |> json_to_rpc
140140+141141+let of_a ~next_char b =
142142+ let buf = Buffer.create 2048 in
143143+ let rec acc () =
144144+ match next_char b with
145145+ | Some c ->
146146+ Buffer.add_char buf c;
147147+ acc ()
148148+ | None -> ()
149149+ in
150150+ acc ();
151151+ Buffer.contents buf |> of_string
152152+153153+let get' name dict = try Some (List.assoc name dict) with Not_found -> None
154154+155155+exception Malformed_method_request of string
156156+exception Malformed_method_response of string
157157+exception Missing_field of string
158158+159159+let get name dict =
160160+ match get' name dict with
161161+ | None ->
162162+ if Rpc.get_debug () then
163163+ Printf.eprintf "%s was not found in the dictionary\n" name;
164164+ raise (Missing_field name)
165165+ | Some v -> v
166166+167167+let version_id_and_call_of_string_option str =
168168+ try
169169+ match of_string str with
170170+ | Dict d ->
171171+ let name =
172172+ match get "method" d with
173173+ | String s -> s
174174+ | _ ->
175175+ raise
176176+ (Malformed_method_request
177177+ "Invalid field 'method' in request body")
178178+ in
179179+ let version =
180180+ match get' "jsonrpc" d with
181181+ | None -> V1
182182+ | Some (String "2.0") -> V2
183183+ | _ ->
184184+ raise
185185+ (Malformed_method_request
186186+ "Invalid field 'jsonrpc' in request body")
187187+ in
188188+ let params =
189189+ match version with
190190+ | V1 -> (
191191+ match get "params" d with
192192+ | Enum l -> l
193193+ | _ ->
194194+ raise
195195+ (Malformed_method_request
196196+ "Invalid field 'params' in request body"))
197197+ | V2 -> (
198198+ match get' "params" d with
199199+ | None | Some Null -> []
200200+ | Some (Enum l) -> l
201201+ | Some (Dict l) -> [ Dict l ]
202202+ | _ ->
203203+ raise
204204+ (Malformed_method_request
205205+ "Invalid field 'params' in request body"))
206206+ in
207207+ let id =
208208+ match get' "id" d with
209209+ | None | Some Null -> None (* is a notification *)
210210+ | Some (Int a) -> Some (Int a)
211211+ | Some (String a) -> Some (String a)
212212+ | Some _ ->
213213+ raise
214214+ (Malformed_method_request "Invalid field 'id' in request body")
215215+ in
216216+ let c = call name params in
217217+ (version, id, { c with is_notification = id == None })
218218+ | _ -> raise (Malformed_method_request "Invalid request body")
219219+ with
220220+ | Missing_field field ->
221221+ raise
222222+ (Malformed_method_request
223223+ (Printf.sprintf "Required field %s is missing" field))
224224+ | JsonToRpcError json ->
225225+ raise
226226+ (Malformed_method_request
227227+ (Printf.sprintf "Unable to parse %s" (Y.to_string json)))
228228+229229+let version_id_and_call_of_string s =
230230+ let version, id_, call = version_id_and_call_of_string_option s in
231231+ match id_ with
232232+ | Some id -> (version, id, call)
233233+ | None ->
234234+ raise (Malformed_method_request "Invalid field 'id' in request body")
235235+236236+let call_of_string str =
237237+ let _, _, call = version_id_and_call_of_string str in
238238+ call
239239+240240+(* This functions parses the json and tries to extract a valid jsonrpc response
241241+ * (See http://www.jsonrpc.org/ for the exact specs). *)
242242+let get_response extractor str =
243243+ try
244244+ match extractor str with
245245+ | Dict d -> (
246246+ let _ =
247247+ match get "id" d with
248248+ | Int _ as x -> x
249249+ | String _ as y -> y
250250+ | _ -> raise (Malformed_method_response "id")
251251+ in
252252+ match get' "jsonrpc" d with
253253+ | None -> (
254254+ let result = get "result" d in
255255+ let error = get "error" d in
256256+ match (result, error) with
257257+ | v, Null -> success v
258258+ | Null, v -> failure v
259259+ | x, y ->
260260+ raise
261261+ (Malformed_method_response
262262+ (Printf.sprintf "<result=%s><error=%s>" (Rpc.to_string x)
263263+ (Rpc.to_string y))))
264264+ | Some (String "2.0") -> (
265265+ let result = get' "result" d in
266266+ let error = get' "error" d in
267267+ match (result, error) with
268268+ | Some v, None -> success v
269269+ | None, Some v -> (
270270+ match v with
271271+ | Dict err ->
272272+ let (_ : int64) =
273273+ match get "code" err with
274274+ | Int i -> i
275275+ | _ -> raise (Malformed_method_response "Error code")
276276+ in
277277+ let _ =
278278+ match get "message" err with
279279+ | String s -> s
280280+ | _ -> raise (Malformed_method_response "Error message")
281281+ in
282282+ failure v
283283+ | _ -> raise (Malformed_method_response "Error object"))
284284+ | Some x, Some y ->
285285+ raise
286286+ (Malformed_method_response
287287+ (Printf.sprintf "<result=%s><error=%s>" (Rpc.to_string x)
288288+ (Rpc.to_string y)))
289289+ | None, None ->
290290+ raise
291291+ (Malformed_method_response
292292+ (Printf.sprintf "neither <result> nor <error> was found")))
293293+ | _ -> raise (Malformed_method_response "jsonrpc"))
294294+ | rpc ->
295295+ raise
296296+ (Malformed_method_response
297297+ (Printf.sprintf "<response_of_stream(%s)>" (to_string rpc)))
298298+ with
299299+ | Missing_field field ->
300300+ raise
301301+ (Malformed_method_response (Printf.sprintf "<%s was not found>" field))
302302+ | JsonToRpcError json ->
303303+ raise
304304+ (Malformed_method_response
305305+ (Printf.sprintf "<unable to parse %s>" (Y.to_string json)))
306306+307307+let response_of_string ?(strict = true) str =
308308+ get_response (of_string ~strict) str
309309+310310+let response_of_in_channel channel =
311311+ let of_channel s = s |> Y.from_channel |> json_to_rpc in
312312+ get_response of_channel channel
+343
js_top_worker/idl/_old/rpc.ml
···11+(*
22+ * Copyright (c) 2006-2009 Citrix Systems Inc.
33+ * Copyright (c) 2006-2014 Thomas Gazagnaire <thomas@gazagnaire.org>
44+ *
55+ * Permission to use, copy, modify, and distribute this software for any
66+ * purpose with or without fee is hereby granted, provided that the above
77+ * copyright notice and this permission notice appear in all copies.
88+ *
99+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616+ *)
1717+1818+let debug = ref false
1919+let set_debug x = debug := x
2020+let get_debug () = !debug
2121+2222+type msg = [ `Msg of string ]
2323+2424+type t =
2525+ | Int of int64
2626+ | Int32 of int32
2727+ | Bool of bool
2828+ | Float of float
2929+ | String of string
3030+ | DateTime of string
3131+ | Enum of t list
3232+ | Dict of (string * t) list
3333+ | Base64 of string
3434+ | Null
3535+3636+module Version = struct
3737+ type t = int * int * int
3838+3939+ let compare (x, y, z) (x', y', z') =
4040+ let cmp a b fn () =
4141+ let c = compare a b in
4242+ if c <> 0 then c else fn ()
4343+ in
4444+ cmp x x' (cmp y y' (cmp z z' (fun () -> 0))) ()
4545+end
4646+4747+module Types = struct
4848+ type _ basic =
4949+ | Int : int basic
5050+ | Int32 : int32 basic
5151+ | Int64 : int64 basic
5252+ | Bool : bool basic
5353+ | Float : float basic
5454+ | String : string basic
5555+ | Char : char basic
5656+5757+ type _ typ =
5858+ | Basic : 'a basic -> 'a typ
5959+ | DateTime : string typ
6060+ | Base64 : string typ
6161+ | Array : 'a typ -> 'a array typ
6262+ | List : 'a typ -> 'a list typ
6363+ | Dict : 'a basic * 'b typ -> ('a * 'b) list typ
6464+ | Unit : unit typ
6565+ | Option : 'a typ -> 'a option typ
6666+ | Tuple : 'a typ * 'b typ -> ('a * 'b) typ
6767+ | Tuple3 : 'a typ * 'b typ * 'c typ -> ('a * 'b * 'c) typ
6868+ | Tuple4 : 'a typ * 'b typ * 'c typ * 'd typ -> ('a * 'b * 'c * 'd) typ
6969+ | Struct : 'a structure -> 'a typ
7070+ | Variant : 'a variant -> 'a typ
7171+ | Abstract : 'a abstract -> 'a typ
7272+7373+ (* A type definition has a name and description *)
7474+ and 'a def = { name : string; description : string list; ty : 'a typ }
7575+ and boxed_def = BoxedDef : 'a def -> boxed_def
7676+7777+ and ('a, 's) field = {
7878+ fname : string;
7979+ fdescription : string list;
8080+ fversion : Version.t option;
8181+ field : 'a typ;
8282+ fdefault : 'a option;
8383+ fget : 's -> 'a;
8484+ (* Lenses *)
8585+ fset : 'a -> 's -> 's;
8686+ }
8787+8888+ and 'a boxed_field = BoxedField : ('a, 's) field -> 's boxed_field
8989+ and field_getter = { field_get : 'a. string -> 'a typ -> ('a, msg) result }
9090+9191+ and 'a structure = {
9292+ sname : string;
9393+ fields : 'a boxed_field list;
9494+ version : Version.t option;
9595+ constructor : field_getter -> ('a, msg) result;
9696+ }
9797+9898+ and ('a, 's) tag = {
9999+ tname : string;
100100+ tdescription : string list;
101101+ tversion : Version.t option;
102102+ tcontents : 'a typ;
103103+ tpreview : 's -> 'a option;
104104+ treview : 'a -> 's;
105105+ }
106106+107107+ and 'a boxed_tag = BoxedTag : ('a, 's) tag -> 's boxed_tag
108108+ and tag_getter = { tget : 'a. 'a typ -> ('a, msg) result }
109109+110110+ and 'a variant = {
111111+ vname : string;
112112+ variants : 'a boxed_tag list;
113113+ vdefault : 'a option;
114114+ vversion : Version.t option;
115115+ vconstructor : string -> tag_getter -> ('a, msg) result;
116116+ }
117117+118118+ and 'a abstract = {
119119+ aname : string;
120120+ test_data : 'a list;
121121+ rpc_of : 'a -> t;
122122+ of_rpc : t -> ('a, msg) result;
123123+ }
124124+125125+ let int = { name = "int"; ty = Basic Int; description = [ "Native integer" ] }
126126+127127+ let int32 =
128128+ { name = "int32"; ty = Basic Int32; description = [ "32-bit integer" ] }
129129+130130+ let int64 =
131131+ { name = "int64"; ty = Basic Int64; description = [ "64-bit integer" ] }
132132+133133+ let bool = { name = "bool"; ty = Basic Bool; description = [ "Boolean" ] }
134134+135135+ let float =
136136+ {
137137+ name = "float";
138138+ ty = Basic Float;
139139+ description = [ "Floating-point number" ];
140140+ }
141141+142142+ let string =
143143+ { name = "string"; ty = Basic String; description = [ "String" ] }
144144+145145+ let char = { name = "char"; ty = Basic Char; description = [ "Char" ] }
146146+ let unit = { name = "unit"; ty = Unit; description = [ "Unit" ] }
147147+148148+ let default_types =
149149+ [
150150+ BoxedDef int;
151151+ BoxedDef int32;
152152+ BoxedDef int64;
153153+ BoxedDef bool;
154154+ BoxedDef float;
155155+ BoxedDef string;
156156+ BoxedDef char;
157157+ BoxedDef unit;
158158+ ]
159159+end
160160+161161+exception Runtime_error of string * t
162162+exception Runtime_exception of string * string
163163+164164+let map_strings sep fn l = String.concat sep (List.map fn l)
165165+166166+let rec to_string t =
167167+ let open Printf in
168168+ match t with
169169+ | Int i -> sprintf "I(%Li)" i
170170+ | Int32 i -> sprintf "I32(%li)" i
171171+ | Bool b -> sprintf "B(%b)" b
172172+ | Float f -> sprintf "F(%g)" f
173173+ | String s -> sprintf "S(%s)" s
174174+ | DateTime s -> sprintf "D(%s)" s
175175+ | Enum ts -> sprintf "[%s]" (map_strings ";" to_string ts)
176176+ | Dict ts ->
177177+ sprintf "{%s}"
178178+ (map_strings ";" (fun (s, t) -> sprintf "%s:%s" s (to_string t)) ts)
179179+ | Base64 s -> sprintf "B64(%s)" s
180180+ | Null -> "N"
181181+182182+let rpc_of_t x = x
183183+let rpc_of_int64 i = Int i
184184+let rpc_of_int32 i = Int (Int64.of_int32 i)
185185+let rpc_of_int i = Int (Int64.of_int i)
186186+let rpc_of_bool b = Bool b
187187+let rpc_of_float f = Float f
188188+let rpc_of_string s = String s
189189+let rpc_of_dateTime s = DateTime s
190190+let rpc_of_base64 s = Base64 s
191191+let rpc_of_unit () = Null
192192+let rpc_of_char x = Int (Int64.of_int (Char.code x))
193193+194194+let int64_of_rpc = function
195195+ | Int i -> i
196196+ | String s -> Int64.of_string s
197197+ | x -> failwith (Printf.sprintf "Expected int64, got '%s'" (to_string x))
198198+199199+let int32_of_rpc = function
200200+ | Int i -> Int64.to_int32 i
201201+ | String s -> Int32.of_string s
202202+ | x -> failwith (Printf.sprintf "Expected int32, got '%s'" (to_string x))
203203+204204+let int_of_rpc = function
205205+ | Int i -> Int64.to_int i
206206+ | String s -> int_of_string s
207207+ | x -> failwith (Printf.sprintf "Expected int, got '%s'" (to_string x))
208208+209209+let bool_of_rpc = function
210210+ | Bool b -> b
211211+ | x -> failwith (Printf.sprintf "Expected bool, got '%s'" (to_string x))
212212+213213+let float_of_rpc = function
214214+ | Float f -> f
215215+ | Int i -> Int64.to_float i
216216+ | Int32 i -> Int32.to_float i
217217+ | String s -> float_of_string s
218218+ | x -> failwith (Printf.sprintf "Expected float, got '%s'" (to_string x))
219219+220220+let string_of_rpc = function
221221+ | String s -> s
222222+ | x -> failwith (Printf.sprintf "Expected string, got '%s'" (to_string x))
223223+224224+let dateTime_of_rpc = function
225225+ | DateTime s -> s
226226+ | x -> failwith (Printf.sprintf "Expected DateTime, got '%s'" (to_string x))
227227+228228+let base64_of_rpc = function _ -> failwith "Base64 Unhandled"
229229+230230+let unit_of_rpc = function
231231+ | Null -> ()
232232+ | x -> failwith (Printf.sprintf "Expected unit, got '%s'" (to_string x))
233233+234234+let char_of_rpc x =
235235+ let x = int_of_rpc x in
236236+ if x < 0 || x > 255 then failwith (Printf.sprintf "Char out of range (%d)" x)
237237+ else Char.chr x
238238+239239+let t_of_rpc t = t
240240+241241+let lowerfn = function
242242+ | String s -> String (String.lowercase_ascii s)
243243+ | Enum (String s :: ss) -> Enum (String (String.lowercase_ascii s) :: ss)
244244+ | x -> x
245245+246246+module ResultUnmarshallers = struct
247247+ let error_msg m = Error (`Msg m)
248248+ let ok x = Ok x
249249+250250+ let int64_of_rpc = function
251251+ | Int i -> ok i
252252+ | String s -> (
253253+ try ok (Int64.of_string s)
254254+ with _ ->
255255+ error_msg (Printf.sprintf "Expected int64, got string '%s'" s))
256256+ | x -> error_msg (Printf.sprintf "Expected int64, got '%s'" (to_string x))
257257+258258+ let int32_of_rpc = function
259259+ | Int i -> ok (Int64.to_int32 i)
260260+ | String s -> (
261261+ try ok (Int32.of_string s)
262262+ with _ ->
263263+ error_msg (Printf.sprintf "Expected int32, got string '%s'" s))
264264+ | x -> error_msg (Printf.sprintf "Expected int32, got '%s'" (to_string x))
265265+266266+ let int_of_rpc = function
267267+ | Int i -> ok (Int64.to_int i)
268268+ | String s -> (
269269+ try ok (int_of_string s)
270270+ with _ -> error_msg (Printf.sprintf "Expected int, got string '%s'" s))
271271+ | x -> error_msg (Printf.sprintf "Expected int, got '%s'" (to_string x))
272272+273273+ let bool_of_rpc = function
274274+ | Bool b -> ok b
275275+ | x -> error_msg (Printf.sprintf "Expected bool, got '%s'" (to_string x))
276276+277277+ let float_of_rpc = function
278278+ | Float f -> ok f
279279+ | Int i -> ok (Int64.to_float i)
280280+ | Int32 i -> ok (Int32.to_float i)
281281+ | String s -> (
282282+ try ok (float_of_string s)
283283+ with _ ->
284284+ error_msg (Printf.sprintf "Expected float, got string '%s'" s))
285285+ | x -> error_msg (Printf.sprintf "Expected float, got '%s'" (to_string x))
286286+287287+ let string_of_rpc = function
288288+ | String s -> ok s
289289+ | x -> error_msg (Printf.sprintf "Expected string, got '%s'" (to_string x))
290290+291291+ let dateTime_of_rpc = function
292292+ | DateTime s -> ok s
293293+ | x ->
294294+ error_msg (Printf.sprintf "Expected DateTime, got '%s'" (to_string x))
295295+296296+ let base64_of_rpc = function _ -> error_msg "Base64 Unhandled"
297297+298298+ let unit_of_rpc = function
299299+ | Null -> ok ()
300300+ | x -> error_msg (Printf.sprintf "Expected unit, got '%s'" (to_string x))
301301+302302+ let char_of_rpc x =
303303+ match int_of_rpc x with
304304+ | Ok x ->
305305+ if x < 0 || x > 255 then
306306+ error_msg (Printf.sprintf "Char out of range (%d)" x)
307307+ else ok (Char.chr x)
308308+ | Error y -> Error y
309309+310310+ let t_of_rpc t = ok t
311311+end
312312+313313+let struct_extend rpc default_rpc =
314314+ match (rpc, default_rpc) with
315315+ | Dict real, Dict default_fields ->
316316+ Dict
317317+ (List.fold_left
318318+ (fun real (f, default) ->
319319+ if List.mem_assoc f real then real else (f, default) :: real)
320320+ real default_fields)
321321+ | _, _ -> rpc
322322+323323+type callback = string list -> t -> unit
324324+type call = { name : string; params : t list; is_notification : bool }
325325+326326+let call name params = { name; params; is_notification = false }
327327+let notification name params = { name; params; is_notification = true }
328328+329329+let string_of_call call =
330330+ Printf.sprintf "-> %s(%s)" call.name
331331+ (String.concat "," (List.map to_string call.params))
332332+333333+type response = { success : bool; contents : t; is_notification : bool }
334334+335335+let string_of_response response =
336336+ Printf.sprintf "<- %s(%s)"
337337+ (if response.success then "success" else "failure")
338338+ (to_string response.contents)
339339+340340+(* is_notification is to be set as true only if the call was a notification *)
341341+342342+let success v = { success = true; contents = v; is_notification = false }
343343+let failure v = { success = false; contents = v; is_notification = false }
+203
js_top_worker/idl/_old/rpc.mli
···11+(*
22+ * Copyright (c) 2006-2009 Citrix Systems Inc.
33+ * Copyright (c) 2006-2014 Thomas Gazagnaire <thomas@gazagnaire.org>
44+ *
55+ * Permission to use, copy, modify, and distribute this software for any
66+ * purpose with or without fee is hereby granted, provided that the above
77+ * copyright notice and this permission notice appear in all copies.
88+ *
99+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616+ *)
1717+1818+type msg = [ `Msg of string ]
1919+(** {2 Value} *)
2020+2121+type t =
2222+ | Int of int64
2323+ | Int32 of int32
2424+ | Bool of bool
2525+ | Float of float
2626+ | String of string
2727+ | DateTime of string
2828+ | Enum of t list
2929+ | Dict of (string * t) list
3030+ | Base64 of string
3131+ | Null
3232+3333+val to_string : t -> string
3434+3535+module Version : sig
3636+ type t = int * int * int
3737+3838+ val compare : t -> t -> int
3939+end
4040+4141+(** {2 Type declarations} *)
4242+module Types : sig
4343+ type _ basic =
4444+ | Int : int basic
4545+ | Int32 : int32 basic
4646+ | Int64 : int64 basic
4747+ | Bool : bool basic
4848+ | Float : float basic
4949+ | String : string basic
5050+ | Char : char basic
5151+5252+ type _ typ =
5353+ | Basic : 'a basic -> 'a typ
5454+ | DateTime : string typ
5555+ | Base64 : string typ
5656+ | Array : 'a typ -> 'a array typ
5757+ | List : 'a typ -> 'a list typ
5858+ | Dict : 'a basic * 'b typ -> ('a * 'b) list typ
5959+ | Unit : unit typ
6060+ | Option : 'a typ -> 'a option typ
6161+ | Tuple : 'a typ * 'b typ -> ('a * 'b) typ
6262+ | Tuple3 : 'a typ * 'b typ * 'c typ -> ('a * 'b * 'c) typ
6363+ | Tuple4 : 'a typ * 'b typ * 'c typ * 'd typ -> ('a * 'b * 'c * 'd) typ
6464+ | Struct : 'a structure -> 'a typ
6565+ | Variant : 'a variant -> 'a typ
6666+ | Abstract : 'a abstract -> 'a typ
6767+6868+ and 'a def = { name : string; description : string list; ty : 'a typ }
6969+ and boxed_def = BoxedDef : 'a def -> boxed_def
7070+7171+ and ('a, 's) field = {
7272+ fname : string;
7373+ fdescription : string list;
7474+ fversion : Version.t option;
7575+ field : 'a typ;
7676+ fdefault : 'a option;
7777+ fget : 's -> 'a;
7878+ fset : 'a -> 's -> 's;
7979+ }
8080+8181+ and 'a boxed_field = BoxedField : ('a, 's) field -> 's boxed_field
8282+ and field_getter = { field_get : 'a. string -> 'a typ -> ('a, msg) result }
8383+8484+ and 'a structure = {
8585+ sname : string;
8686+ fields : 'a boxed_field list;
8787+ version : Version.t option;
8888+ constructor : field_getter -> ('a, msg) result;
8989+ }
9090+9191+ and ('a, 's) tag = {
9292+ tname : string;
9393+ tdescription : string list;
9494+ tversion : Version.t option;
9595+ tcontents : 'a typ;
9696+ tpreview : 's -> 'a option;
9797+ (* Prism *)
9898+ treview : 'a -> 's;
9999+ }
100100+101101+ and 'a boxed_tag = BoxedTag : ('a, 's) tag -> 's boxed_tag
102102+ and tag_getter = { tget : 'a. 'a typ -> ('a, msg) result }
103103+104104+ and 'a variant = {
105105+ vname : string;
106106+ variants : 'a boxed_tag list;
107107+ vdefault : 'a option;
108108+ vversion : Version.t option;
109109+ vconstructor : string -> tag_getter -> ('a, msg) result;
110110+ }
111111+112112+ and 'a abstract = {
113113+ aname : string;
114114+ test_data : 'a list;
115115+ rpc_of : 'a -> t;
116116+ of_rpc : t -> ('a, msg) result;
117117+ }
118118+119119+ val int : int def
120120+ val int32 : int32 def
121121+ val int64 : int64 def
122122+ val bool : bool def
123123+ val float : float def
124124+ val string : string def
125125+ val char : char def
126126+ val unit : unit def
127127+ val default_types : boxed_def list
128128+end
129129+130130+(** {2 Basic constructors} *)
131131+132132+val rpc_of_int64 : int64 -> t
133133+val rpc_of_int32 : int32 -> t
134134+val rpc_of_int : int -> t
135135+val rpc_of_bool : bool -> t
136136+val rpc_of_float : float -> t
137137+val rpc_of_string : string -> t
138138+val rpc_of_dateTime : string -> t
139139+val rpc_of_base64 : string -> t
140140+val rpc_of_t : t -> t
141141+val rpc_of_unit : unit -> t
142142+val rpc_of_char : char -> t
143143+val int64_of_rpc : t -> int64
144144+val int32_of_rpc : t -> int32
145145+val int_of_rpc : t -> int
146146+val bool_of_rpc : t -> bool
147147+val float_of_rpc : t -> float
148148+val string_of_rpc : t -> string
149149+val dateTime_of_rpc : t -> string
150150+val base64_of_rpc : t -> string
151151+val t_of_rpc : t -> t
152152+val char_of_rpc : t -> char
153153+val unit_of_rpc : t -> unit
154154+155155+module ResultUnmarshallers : sig
156156+ val int64_of_rpc : t -> (int64, msg) result
157157+ val int32_of_rpc : t -> (int32, msg) result
158158+ val int_of_rpc : t -> (int, msg) result
159159+ val bool_of_rpc : t -> (bool, msg) result
160160+ val float_of_rpc : t -> (float, msg) result
161161+ val string_of_rpc : t -> (string, msg) result
162162+ val dateTime_of_rpc : t -> (string, msg) result
163163+ val base64_of_rpc : t -> (string, msg) result
164164+ val t_of_rpc : t -> (t, msg) result
165165+ val unit_of_rpc : t -> (unit, msg) result
166166+ val char_of_rpc : t -> (char, msg) result
167167+end
168168+169169+(** {2 Calls} *)
170170+171171+type callback = string list -> t -> unit
172172+type call = { name : string; params : t list; is_notification : bool }
173173+174174+val call : string -> t list -> call
175175+val notification : string -> t list -> call
176176+val string_of_call : call -> string
177177+178178+(** {2 Responses} *)
179179+180180+type response = { success : bool; contents : t; is_notification : bool }
181181+182182+val string_of_response : response -> string
183183+val success : t -> response
184184+val failure : t -> response
185185+186186+(** {2 Run-time errors} *)
187187+188188+exception Runtime_error of string * t
189189+exception Runtime_exception of string * string
190190+191191+val set_debug : bool -> unit
192192+(** {2 Debug options} *)
193193+194194+val get_debug : unit -> bool
195195+196196+val lowerfn : t -> t
197197+(** Helper *)
198198+199199+val struct_extend : t -> t -> t
200200+(** [struct_extend rpc1 rpc2] first checks that [rpc1] and [rpc2] are both
201201+ * dictionaries. If this is the case then [struct_extend] will create a new
202202+ * [Rpc.t] which contains all key-value pairs from [rpc1], as well as all
203203+ * key-value pairs from [rpc2] for which the key does not exist in [rpc1]. *)
+271
js_top_worker/idl/_old/rpcmarshal.ml
···11+(* Basic type definitions *)
22+open Rpc.Types
33+44+type err = [ `Msg of string ]
55+66+let tailrec_map f l = List.rev_map f l |> List.rev
77+let ( >>| ) x f = match x with Ok x -> Ok (f x) | Error y -> Error y
88+let ( >>= ) x f = match x with Ok x -> f x | Error y -> Error y
99+let return x = Ok x
1010+let ok x = Ok x
1111+1212+let rec unmarshal : type a. a typ -> Rpc.t -> (a, err) result =
1313+ fun t v ->
1414+ let open Rpc in
1515+ let open Rpc.ResultUnmarshallers in
1616+ let list_helper typ l =
1717+ List.fold_left
1818+ (fun acc v ->
1919+ match (acc, unmarshal typ v) with
2020+ | Ok a, Ok v -> Ok (v :: a)
2121+ | _, Error (`Msg s) ->
2222+ Error
2323+ (`Msg
2424+ (Printf.sprintf
2525+ "Failed to unmarshal array: %s (when unmarshalling: %s)" s
2626+ (Rpc.to_string v)))
2727+ | x, _ -> x)
2828+ (Ok []) l
2929+ >>| List.rev
3030+ in
3131+ match t with
3232+ | Basic Int -> int_of_rpc v
3333+ | Basic Int32 -> int32_of_rpc v
3434+ | Basic Int64 -> int64_of_rpc v
3535+ | Basic Bool -> bool_of_rpc v
3636+ | Basic Float -> float_of_rpc v
3737+ | Basic String -> string_of_rpc v
3838+ | Basic Char -> int_of_rpc v >>| Char.chr
3939+ | DateTime -> dateTime_of_rpc v
4040+ | Base64 -> base64_of_rpc v
4141+ | Array typ -> (
4242+ match v with
4343+ | Enum xs -> list_helper typ xs >>| Array.of_list
4444+ | _ -> Error (`Msg "Expecting Array"))
4545+ | List (Tuple (Basic String, typ)) -> (
4646+ match v with
4747+ | Dict xs ->
4848+ let keys = tailrec_map fst xs in
4949+ let vs = tailrec_map snd xs in
5050+ list_helper typ vs >>= fun vs -> return (List.combine keys vs)
5151+ | _ -> Error (`Msg "Unhandled"))
5252+ | Dict (basic, typ) -> (
5353+ match v with
5454+ | Dict xs -> (
5555+ match basic with
5656+ | String ->
5757+ let keys = tailrec_map fst xs in
5858+ let vs = tailrec_map snd xs in
5959+ list_helper typ vs >>= fun vs -> return (List.combine keys vs)
6060+ | _ -> Error (`Msg "Expecting something other than a Dict type"))
6161+ | _ -> Error (`Msg "Unhandled"))
6262+ | List typ -> (
6363+ match v with
6464+ | Enum xs -> list_helper typ xs
6565+ | _ -> Error (`Msg "Expecting array"))
6666+ | Unit -> unit_of_rpc v
6767+ | Option t -> (
6868+ match v with
6969+ | Enum [ x ] -> unmarshal t x >>= fun x -> return (Some x)
7070+ | Enum [] -> return None
7171+ | y ->
7272+ Error
7373+ (`Msg
7474+ (Printf.sprintf "Expecting an Enum value, got '%s'"
7575+ (Rpc.to_string y))))
7676+ | Tuple (t1, t2) -> (
7777+ match (v, t2) with
7878+ | Rpc.Enum list, Tuple (_, _) ->
7979+ unmarshal t1 (List.hd list) >>= fun v1 ->
8080+ unmarshal t2 (Rpc.Enum (List.tl list)) >>= fun v2 -> Ok (v1, v2)
8181+ | Rpc.Enum [ x; y ], _ ->
8282+ unmarshal t1 x >>= fun v1 ->
8383+ unmarshal t2 y >>= fun v2 -> Ok (v1, v2)
8484+ | Rpc.Enum _, _ -> Error (`Msg "Too many items in a tuple!")
8585+ | _, _ -> Error (`Msg "Expecting Rpc.Enum when unmarshalling a tuple"))
8686+ | Tuple3 (t1, t2, t3) -> (
8787+ match v with
8888+ | Rpc.Enum [ x; y; z ] ->
8989+ unmarshal t1 x >>= fun v1 ->
9090+ unmarshal t2 y >>= fun v2 ->
9191+ unmarshal t3 z >>= fun v3 -> Ok (v1, v2, v3)
9292+ | Rpc.Enum _ ->
9393+ Error (`Msg "Expecting precisely 3 items when unmarshalling a Tuple3")
9494+ | _ -> Error (`Msg "Expecting Rpc.Enum when unmarshalling a tuple3"))
9595+ | Tuple4 (t1, t2, t3, t4) -> (
9696+ match v with
9797+ | Rpc.Enum [ x; y; z; a ] ->
9898+ unmarshal t1 x >>= fun v1 ->
9999+ unmarshal t2 y >>= fun v2 ->
100100+ unmarshal t3 z >>= fun v3 ->
101101+ unmarshal t4 a >>= fun v4 -> Ok (v1, v2, v3, v4)
102102+ | Rpc.Enum _ ->
103103+ Error
104104+ (`Msg
105105+ "Expecting precisely 4 items in an Enum when unmarshalling a \
106106+ Tuple4")
107107+ | _ -> Error (`Msg "Expecting Rpc.Enum when unmarshalling a tuple4"))
108108+ | Struct { constructor; sname; _ } -> (
109109+ match v with
110110+ | Rpc.Dict keys' ->
111111+ let keys =
112112+ List.map (fun (s, v) -> (String.lowercase_ascii s, v)) keys'
113113+ in
114114+ constructor
115115+ {
116116+ field_get =
117117+ (let x : type a. string -> a typ -> (a, Rpc.msg) result =
118118+ fun s ty ->
119119+ let s = String.lowercase_ascii s in
120120+ match ty with
121121+ | Option x -> (
122122+ try
123123+ List.assoc s keys |> unmarshal x >>= fun o ->
124124+ return (Some o)
125125+ with _ -> return None)
126126+ | y -> (
127127+ try List.assoc s keys |> unmarshal y
128128+ with Not_found ->
129129+ Error
130130+ (`Msg
131131+ (Printf.sprintf
132132+ "No value found for key: '%s' when \
133133+ unmarshalling '%s'"
134134+ s sname)))
135135+ in
136136+ x);
137137+ }
138138+ | _ ->
139139+ Error
140140+ (`Msg
141141+ (Printf.sprintf "Expecting Rpc.Dict when unmarshalling a '%s'"
142142+ sname)))
143143+ | Variant { vconstructor; _ } ->
144144+ (match v with
145145+ | Rpc.String name -> ok (name, Rpc.Null)
146146+ | Rpc.Enum [ Rpc.String name; contents ] -> ok (name, contents)
147147+ | _ ->
148148+ Error (`Msg "Expecting String or Enum when unmarshalling a variant"))
149149+ >>= fun (name, contents) ->
150150+ let constr = { tget = (fun typ -> unmarshal typ contents) } in
151151+ vconstructor name constr
152152+ | Abstract { of_rpc; _ } -> of_rpc v
153153+154154+let rec marshal : type a. a typ -> a -> Rpc.t =
155155+ fun t v ->
156156+ let open Rpc in
157157+ let rpc_of_basic : type a. a basic -> a -> Rpc.t =
158158+ fun t v ->
159159+ match t with
160160+ | Int -> rpc_of_int v
161161+ | Int32 -> rpc_of_int32 v
162162+ | Int64 -> rpc_of_int64 v
163163+ | Bool -> rpc_of_bool v
164164+ | Float -> rpc_of_float v
165165+ | String -> rpc_of_string v
166166+ | Char -> rpc_of_int (Char.code v)
167167+ in
168168+ match t with
169169+ | Basic t -> rpc_of_basic t v
170170+ | DateTime -> rpc_of_dateTime v
171171+ | Base64 -> rpc_of_base64 v
172172+ | Array typ -> Enum (tailrec_map (marshal typ) (Array.to_list v))
173173+ | List (Tuple (Basic String, typ)) ->
174174+ Dict (tailrec_map (fun (x, y) -> (x, marshal typ y)) v)
175175+ | List typ -> Enum (tailrec_map (marshal typ) v)
176176+ | Dict (String, typ) ->
177177+ Rpc.Dict (tailrec_map (fun (k, v) -> (k, marshal typ v)) v)
178178+ | Dict (basic, typ) ->
179179+ Rpc.Enum
180180+ (tailrec_map
181181+ (fun (k, v) -> Rpc.Enum [ rpc_of_basic basic k; marshal typ v ])
182182+ v)
183183+ | Unit -> rpc_of_unit v
184184+ | Option ty ->
185185+ Rpc.Enum (match v with Some x -> [ marshal ty x ] | None -> [])
186186+ | Tuple (x, (Tuple (_, _) as y)) -> (
187187+ match marshal y (snd v) with
188188+ | Rpc.Enum xs -> Rpc.Enum (marshal x (fst v) :: xs)
189189+ | _ -> failwith "Marshalling a tuple should always give an Enum")
190190+ | Tuple (x, y) -> Rpc.Enum [ marshal x (fst v); marshal y (snd v) ]
191191+ | Tuple3 (x, y, z) ->
192192+ let vx, vy, vz = v in
193193+ Rpc.Enum [ marshal x vx; marshal y vy; marshal z vz ]
194194+ | Tuple4 (x, y, z, a) ->
195195+ let vx, vy, vz, va = v in
196196+ Rpc.Enum [ marshal x vx; marshal y vy; marshal z vz; marshal a va ]
197197+ | Struct { fields; _ } ->
198198+ let fields =
199199+ List.fold_left
200200+ (fun acc f ->
201201+ match f with
202202+ | BoxedField f -> (
203203+ let value = marshal f.field (f.fget v) in
204204+ match (f.field, value) with
205205+ | Option _, Rpc.Enum [] -> acc
206206+ | Option _, Rpc.Enum [ x ] -> (f.fname, x) :: acc
207207+ | _, _ -> (f.fname, value) :: acc))
208208+ [] fields
209209+ in
210210+ Rpc.Dict fields
211211+ | Variant { variants; _ } ->
212212+ List.fold_left
213213+ (fun acc t ->
214214+ match t with
215215+ | BoxedTag t -> (
216216+ match t.tpreview v with
217217+ | Some x -> (
218218+ match marshal t.tcontents x with
219219+ | Rpc.Null -> Rpc.String t.tname
220220+ | y -> Rpc.Enum [ Rpc.String t.tname; y ])
221221+ | None -> acc))
222222+ Rpc.Null variants
223223+ | Abstract { rpc_of; _ } -> rpc_of v
224224+225225+let ocaml_of_basic : type a. a basic -> string = function
226226+ | Int64 -> "int64"
227227+ | Int32 -> "int32"
228228+ | Int -> "int"
229229+ | String -> "string"
230230+ | Float -> "float"
231231+ | Bool -> "bool"
232232+ | Char -> "char"
233233+234234+let rec ocaml_of_t : type a. a typ -> string = function
235235+ | Basic b -> ocaml_of_basic b
236236+ | DateTime -> "string"
237237+ | Base64 -> "base64"
238238+ | Array t -> ocaml_of_t t ^ " list"
239239+ | List t -> ocaml_of_t t ^ " list"
240240+ | Dict (b, t) ->
241241+ Printf.sprintf "(%s * %s) list" (ocaml_of_basic b) (ocaml_of_t t)
242242+ | Unit -> "unit"
243243+ | Option t -> ocaml_of_t t ^ " option"
244244+ | Tuple (a, b) -> Printf.sprintf "(%s * %s)" (ocaml_of_t a) (ocaml_of_t b)
245245+ | Tuple3 (a, b, c) ->
246246+ Printf.sprintf "(%s * %s * %s)" (ocaml_of_t a) (ocaml_of_t b)
247247+ (ocaml_of_t c)
248248+ | Tuple4 (a, b, c, d) ->
249249+ Printf.sprintf "(%s * %s * %s * %s)" (ocaml_of_t a) (ocaml_of_t b)
250250+ (ocaml_of_t c) (ocaml_of_t d)
251251+ | Struct { fields; _ } ->
252252+ let fields =
253253+ List.map
254254+ (function
255255+ | BoxedField f ->
256256+ Printf.sprintf "%s: %s;" f.fname (ocaml_of_t f.field))
257257+ fields
258258+ in
259259+ Printf.sprintf "{ %s }" (String.concat " " fields)
260260+ | Variant { variants; _ } ->
261261+ let tags =
262262+ List.map
263263+ (function
264264+ | BoxedTag t ->
265265+ Printf.sprintf "| %s (%s) (** %s *)" t.tname
266266+ (ocaml_of_t t.tcontents)
267267+ (String.concat " " t.tdescription))
268268+ variants
269269+ in
270270+ String.concat " " tags
271271+ | Abstract _ -> "<abstract>"
···11+(** Worker rpc *)
22+33+(** Functions to facilitate RPC calls to web workers. *)
44+55+module Worker = Brr_webworkers.Worker
66+open Brr_io
77+open Js_top_worker_rpc
88+99+(** The assumption made in this module is that RPCs are answered in the order
1010+ they are made. *)
1111+1212+type context = {
1313+ worker : Worker.t;
1414+ timeout : int;
1515+ timeout_fn : unit -> unit;
1616+ waiting : ((Rpc.response, exn) Result.t Lwt_mvar.t * int) Queue.t;
1717+}
1818+1919+type rpc = Rpc.call -> Rpc.response Lwt.t
2020+2121+exception Timeout
2222+2323+(* let log s = Js_of_ocaml.Firebug.console##log (Js_of_ocaml.Js.string s) *)
2424+2525+let demux context msg =
2626+ Lwt.async (fun () ->
2727+ match Queue.take_opt context.waiting with
2828+ | None -> Lwt.return ()
2929+ | Some (mv, outstanding_execution) ->
3030+ Brr.G.stop_timer outstanding_execution;
3131+ let msg = Message.Ev.data (Brr.Ev.as_type msg) in
3232+ Js_of_ocaml.Console.console##log
3333+ (Js_of_ocaml.Js.string
3434+ "Client received the following, to be converted to an OCaml \
3535+ string");
3636+ Js_of_ocaml.Console.console##log msg;
3737+ let msg = Js_of_ocaml.Js.to_string msg in
3838+ (* log (Printf.sprintf "Client received: %s" msg); *)
3939+ Lwt_mvar.put mv (Ok (Transport.Json.response_of_string msg)))
4040+4141+let rpc : context -> Rpc.call -> Rpc.response Lwt.t =
4242+ fun context call ->
4343+ let open Lwt in
4444+ let jv = Transport.Json.string_of_call call |> Js_of_ocaml.Js.string in
4545+ (* log (Printf.sprintf "Client sending: %s" jv); *)
4646+ let mv = Lwt_mvar.create_empty () in
4747+ let outstanding_execution =
4848+ Brr.G.set_timeout ~ms:context.timeout (fun () ->
4949+ Lwt.async (fun () -> Lwt_mvar.put mv (Error Timeout));
5050+ Worker.terminate context.worker;
5151+ context.timeout_fn ())
5252+ in
5353+ Queue.push (mv, outstanding_execution) context.waiting;
5454+ Worker.post context.worker jv;
5555+ Lwt_mvar.take mv >>= fun r ->
5656+ match r with
5757+ | Ok jv ->
5858+ let response = jv in
5959+ Lwt.return response
6060+ | Error exn -> Lwt.fail exn
6161+6262+let start url timeout timeout_fn : rpc =
6363+ let worker = Worker.create (Jstr.v url) in
6464+ let context = { worker; timeout; timeout_fn; waiting = Queue.create () } in
6565+ let _listener =
6666+ Brr.Ev.listen Message.Ev.message (demux context) (Worker.as_target worker)
6767+ in
6868+ rpc context
6969+7070+module Rpc_lwt = Idl.Make (Lwt)
7171+module Wraw = Toplevel_api_gen.Make (Rpc_lwt.GenClient ())
7272+7373+module W : sig
7474+ type init_config = Toplevel_api_gen.init_config
7575+ type err = Toplevel_api_gen.err
7676+ type exec_result = Toplevel_api_gen.exec_result
7777+7878+ val init :
7979+ rpc ->
8080+ Toplevel_api_gen.init_config ->
8181+ (unit, Toplevel_api_gen.err) result Lwt.t
8282+8383+ val create_env :
8484+ rpc ->
8585+ string ->
8686+ (unit, Toplevel_api_gen.err) result Lwt.t
8787+8888+ val destroy_env :
8989+ rpc ->
9090+ string ->
9191+ (unit, Toplevel_api_gen.err) result Lwt.t
9292+9393+ val list_envs :
9494+ rpc ->
9595+ (string list, Toplevel_api_gen.err) result Lwt.t
9696+9797+ val setup :
9898+ rpc ->
9999+ string ->
100100+ (Toplevel_api_gen.exec_result, Toplevel_api_gen.err) result Lwt.t
101101+102102+ val exec :
103103+ rpc ->
104104+ string ->
105105+ string ->
106106+ (Toplevel_api_gen.exec_result, Toplevel_api_gen.err) result Lwt.t
107107+108108+ val exec_toplevel :
109109+ rpc ->
110110+ string ->
111111+ string ->
112112+ (Toplevel_api_gen.exec_toplevel_result, Toplevel_api_gen.err) result Lwt.t
113113+114114+ val query_errors :
115115+ rpc ->
116116+ string ->
117117+ string option ->
118118+ string list ->
119119+ bool ->
120120+ string ->
121121+ (Toplevel_api_gen.error list, Toplevel_api_gen.err) result Lwt.t
122122+end = struct
123123+ type init_config = Toplevel_api_gen.init_config
124124+ type err = Toplevel_api_gen.err
125125+ type exec_result = Toplevel_api_gen.exec_result
126126+127127+ let init rpc a = Wraw.init rpc a |> Rpc_lwt.T.get
128128+ let create_env rpc env_id = Wraw.create_env rpc env_id |> Rpc_lwt.T.get
129129+ let destroy_env rpc env_id = Wraw.destroy_env rpc env_id |> Rpc_lwt.T.get
130130+ let list_envs rpc = Wraw.list_envs rpc () |> Rpc_lwt.T.get
131131+ let setup rpc env_id = Wraw.setup rpc env_id |> Rpc_lwt.T.get
132132+ let exec rpc env_id phrase = Wraw.exec rpc env_id phrase |> Rpc_lwt.T.get
133133+ let exec_toplevel rpc env_id script = Wraw.exec_toplevel rpc env_id script |> Rpc_lwt.T.get
134134+135135+ let query_errors rpc env_id id deps is_toplevel doc =
136136+ Wraw.query_errors rpc env_id id deps is_toplevel doc |> Rpc_lwt.T.get
137137+end
+76
js_top_worker/idl/js_top_worker_client.mli
···11+(* Worker_rpc *)
22+33+open Js_top_worker_rpc
44+55+(** Functions to facilitate RPC calls to web workers. *)
66+77+exception Timeout
88+(** When RPC calls take too long, the Lwt promise is set to failed state with
99+ this exception. *)
1010+1111+type rpc = Rpc.call -> Rpc.response Lwt.t
1212+(** RPC function for communicating with the worker. This is used by each RPC
1313+ function declared in {!W} *)
1414+1515+val start : string -> int -> (unit -> unit) -> rpc
1616+(** [start url timeout timeout_fn] initialises a web worker from [url] and
1717+ starts communications with it. [timeout] is the number of seconds to wait
1818+ for a response from any RPC before raising an error, and [timeout_fn] is
1919+ called when a timeout occurs. Returns the {!type-rpc} function used in the
2020+ RPC calls. *)
2121+2222+module W : sig
2323+ (** {2 Type declarations}
2424+2525+ The following types are redeclared here for convenience. *)
2626+2727+ type init_config = Toplevel_api_gen.init_config
2828+ type err = Toplevel_api_gen.err
2929+ type exec_result = Toplevel_api_gen.exec_result
3030+3131+ (** {2 RPC calls}
3232+3333+ The first parameter of these calls is the rpc function returned by
3434+ {!val-start}. If any of these calls fails to receive a response from the
3535+ worker by the timeout set in the {!val-start} call, the {!Lwt} thread will
3636+ be {{!Lwt.fail}failed}. *)
3737+3838+ val init : rpc -> init_config -> (unit, err) result Lwt.t
3939+ (** Initialise the toplevel. This must be called before any other API. *)
4040+4141+ val create_env : rpc -> string -> (unit, err) result Lwt.t
4242+ (** Create a new isolated execution environment with the given ID. *)
4343+4444+ val destroy_env : rpc -> string -> (unit, err) result Lwt.t
4545+ (** Destroy an execution environment. *)
4646+4747+ val list_envs : rpc -> (string list, err) result Lwt.t
4848+ (** List all existing environment IDs. *)
4949+5050+ val setup : rpc -> string -> (exec_result, err) result Lwt.t
5151+ (** Start the toplevel for the given environment. If [env_id] is empty string,
5252+ uses the default environment. Return value is the initial blurb printed
5353+ when starting a toplevel. Note that the toplevel must be initialised first. *)
5454+5555+ val exec : rpc -> string -> string -> (exec_result, err) result Lwt.t
5656+ (** Execute a phrase using the toplevel. If [env_id] is empty string, uses the
5757+ default environment. The toplevel must have been initialised first. *)
5858+5959+ val exec_toplevel :
6060+ rpc ->
6161+ string ->
6262+ string ->
6363+ (Toplevel_api_gen.exec_toplevel_result, err) result Lwt.t
6464+ (** Execute a toplevel script. If [env_id] is empty string, uses the default
6565+ environment. The toplevel must have been initialised first. *)
6666+6767+ val query_errors :
6868+ rpc ->
6969+ string ->
7070+ string option ->
7171+ string list ->
7272+ bool ->
7373+ string ->
7474+ (Toplevel_api_gen.error list, err) result Lwt.t
7575+ (** Query the toplevel for errors. [env_id] specifies the environment. *)
7676+end
+97
js_top_worker/idl/js_top_worker_client_fut.ml
···11+(** Worker rpc *)
22+33+(** Functions to facilitate RPC calls to web workers. *)
44+55+module Worker = Brr_webworkers.Worker
66+open Brr_io
77+open Js_top_worker_rpc
88+99+(** The assumption made in this module is that RPCs are answered in the order
1010+ they are made. *)
1111+1212+type context = {
1313+ worker : Worker.t;
1414+ timeout : int;
1515+ timeout_fn : unit -> unit;
1616+ waiting : (((Rpc.response, exn) Result.t -> unit) * int) Queue.t;
1717+}
1818+1919+type rpc = Rpc.call -> Rpc.response Fut.t
2020+2121+exception Timeout
2222+2323+(* let log s = Js_of_ocaml.Firebug.console##log (Js_of_ocaml.Js.string s) *)
2424+2525+let demux context msg =
2626+ match Queue.take_opt context.waiting with
2727+ | None -> ()
2828+ | Some (mv, outstanding_execution) ->
2929+ Brr.G.stop_timer outstanding_execution;
3030+ let msg = Message.Ev.data (Brr.Ev.as_type msg) in
3131+ (* Js_of_ocaml.Console.console##log (Js_of_ocaml.Js.string "Client received the following, to be converted to an OCaml string"); *)
3232+ (* Js_of_ocaml.Console.console##log msg; *)
3333+ let msg = Js_of_ocaml.Js.to_string msg in
3434+ (* log (Printf.sprintf "Client received: %s" msg); *)
3535+ mv (Ok (Transport.Json.response_of_string msg))
3636+3737+let rpc : context -> Rpc.call -> Rpc.response Fut.t =
3838+ fun context call ->
3939+ let open Fut.Syntax in
4040+ let jv = Transport.Json.string_of_call call |> Js_of_ocaml.Js.string in
4141+ (* log (Printf.sprintf "Client sending: %s" jv); *)
4242+ let v, mv = Fut.create () in
4343+ let outstanding_execution =
4444+ Brr.G.set_timeout ~ms:context.timeout (fun () ->
4545+ mv (Error Timeout);
4646+ Worker.terminate context.worker;
4747+ context.timeout_fn ())
4848+ in
4949+ Queue.push (mv, outstanding_execution) context.waiting;
5050+ Worker.post context.worker jv;
5151+ let* r = v in
5252+ match r with
5353+ | Ok jv ->
5454+ let response = jv in
5555+ Fut.return response
5656+ | Error exn -> raise exn
5757+5858+let start url timeout timeout_fn : rpc =
5959+ let worker = Worker.create (Jstr.v url) in
6060+ let context = { worker; timeout; timeout_fn; waiting = Queue.create () } in
6161+ let _listener =
6262+ Brr.Ev.listen Message.Ev.message (demux context) (Worker.as_target worker)
6363+ in
6464+ rpc context
6565+6666+module M = struct
6767+ include Fut
6868+6969+ let fail e = raise e
7070+end
7171+7272+module Rpc_fut = Idl.Make (M)
7373+module Wraw = Toplevel_api_gen.Make (Rpc_fut.GenClient ())
7474+7575+module W = struct
7676+ type init_config = Toplevel_api_gen.init_config
7777+ type err = Toplevel_api_gen.err
7878+ type exec_result = Toplevel_api_gen.exec_result
7979+8080+ let init rpc a = Wraw.init rpc a |> Rpc_fut.T.get
8181+ let create_env rpc env_id = Wraw.create_env rpc env_id |> Rpc_fut.T.get
8282+ let destroy_env rpc env_id = Wraw.destroy_env rpc env_id |> Rpc_fut.T.get
8383+ let list_envs rpc = Wraw.list_envs rpc () |> Rpc_fut.T.get
8484+ let setup rpc env_id = Wraw.setup rpc env_id |> Rpc_fut.T.get
8585+ let exec rpc env_id phrase = Wraw.exec rpc env_id phrase |> Rpc_fut.T.get
8686+8787+ let query_errors rpc env_id id deps is_toplevel doc =
8888+ Wraw.query_errors rpc env_id id deps is_toplevel doc |> Rpc_fut.T.get
8989+9090+ let exec_toplevel rpc env_id doc = Wraw.exec_toplevel rpc env_id doc |> Rpc_fut.T.get
9191+9292+ let complete_prefix rpc env_id id deps is_toplevel doc pos =
9393+ Wraw.complete_prefix rpc env_id id deps is_toplevel doc pos |> Rpc_fut.T.get
9494+9595+ let type_enclosing rpc env_id id deps is_toplevel doc pos =
9696+ Wraw.type_enclosing rpc env_id id deps is_toplevel doc pos |> Rpc_fut.T.get
9797+end
+404
js_top_worker/idl/js_top_worker_client_msg.ml
···11+(** Worker client using the message protocol.
22+33+ This client communicates with the OCaml toplevel worker using a simple
44+ JSON message protocol instead of RPC. *)
55+66+module Brr_worker = Brr_webworkers.Worker
77+module Brr_message = Brr_io.Message
88+module Msg = Js_top_worker_message.Message
99+1010+(** Incremental output from a single phrase *)
1111+type output_at = {
1212+ cell_id : int;
1313+ loc : int; (** Character position after phrase (pos_cnum) *)
1414+ caml_ppf : string;
1515+ mime_vals : Msg.mime_val list;
1616+}
1717+1818+(** Output result type *)
1919+type output = {
2020+ cell_id : int;
2121+ stdout : string;
2222+ stderr : string;
2323+ caml_ppf : string;
2424+ mime_vals : Msg.mime_val list;
2525+}
2626+2727+(** Eval stream event *)
2828+type eval_event =
2929+ | Phrase of output_at (** Incremental output after each phrase *)
3030+ | Done of output (** Final result *)
3131+ | Error of string (** Error occurred *)
3232+3333+(** Client state *)
3434+type t = {
3535+ worker : Brr_worker.t;
3636+ timeout : int;
3737+ mutable cell_id : int;
3838+ mutable ready : bool;
3939+ ready_waiters : (unit -> unit) Queue.t;
4040+ pending : (int, Msg.worker_msg Lwt.u) Hashtbl.t;
4141+ pending_env : (string, Msg.worker_msg Lwt.u) Hashtbl.t;
4242+ pending_stream : (int, eval_event option -> unit) Hashtbl.t;
4343+}
4444+4545+exception Timeout
4646+exception InitError of string
4747+exception EvalError of string
4848+4949+(** Parse a worker message from JSON string *)
5050+let parse_worker_msg s =
5151+ let open Js_of_ocaml in
5252+ let obj = Json.unsafe_input (Js.string s) in
5353+ let typ = Js.to_string (Js.Unsafe.get obj (Js.string "type")) in
5454+ let get_int key = Js.Unsafe.get obj (Js.string key) in
5555+ let get_string key = Js.to_string (Js.Unsafe.get obj (Js.string key)) in
5656+ let parse_position p =
5757+ { Msg.pos_cnum = Js.Unsafe.get p (Js.string "pos_cnum");
5858+ pos_lnum = Js.Unsafe.get p (Js.string "pos_lnum");
5959+ pos_bol = Js.Unsafe.get p (Js.string "pos_bol") }
6060+ in
6161+ let parse_location loc =
6262+ { Msg.loc_start = parse_position (Js.Unsafe.get loc (Js.string "loc_start"));
6363+ loc_end = parse_position (Js.Unsafe.get loc (Js.string "loc_end")) }
6464+ in
6565+ match typ with
6666+ | "ready" -> Msg.Ready
6767+ | "init_error" -> Msg.InitError { message = get_string "message" }
6868+ | "output" ->
6969+ let mime_vals_arr = Js.to_array (Js.Unsafe.get obj (Js.string "mime_vals")) in
7070+ let mime_vals = Array.to_list (Array.map (fun mv ->
7171+ { Msg.mime_type = Js.to_string (Js.Unsafe.get mv (Js.string "mime_type"));
7272+ data = Js.to_string (Js.Unsafe.get mv (Js.string "data")) }
7373+ ) mime_vals_arr) in
7474+ Msg.Output {
7575+ cell_id = get_int "cell_id";
7676+ stdout = get_string "stdout";
7777+ stderr = get_string "stderr";
7878+ caml_ppf = get_string "caml_ppf";
7979+ mime_vals;
8080+ }
8181+ | "completions" ->
8282+ let c = Js.Unsafe.get obj (Js.string "completions") in
8383+ let entries_arr = Js.to_array (Js.Unsafe.get c (Js.string "entries")) in
8484+ let entries = Array.to_list (Array.map (fun e ->
8585+ { Msg.name = Js.to_string (Js.Unsafe.get e (Js.string "name"));
8686+ kind = Js.to_string (Js.Unsafe.get e (Js.string "kind"));
8787+ desc = Js.to_string (Js.Unsafe.get e (Js.string "desc"));
8888+ info = Js.to_string (Js.Unsafe.get e (Js.string "info"));
8989+ deprecated = Js.to_bool (Js.Unsafe.get e (Js.string "deprecated")) }
9090+ ) entries_arr) in
9191+ Msg.Completions {
9292+ cell_id = get_int "cell_id";
9393+ completions = {
9494+ from = Js.Unsafe.get c (Js.string "from");
9595+ to_ = Js.Unsafe.get c (Js.string "to");
9696+ entries;
9797+ };
9898+ }
9999+ | "types" ->
100100+ let types_arr = Js.to_array (Js.Unsafe.get obj (Js.string "types")) in
101101+ let types = Array.to_list (Array.map (fun t ->
102102+ { Msg.loc = parse_location (Js.Unsafe.get t (Js.string "loc"));
103103+ type_str = Js.to_string (Js.Unsafe.get t (Js.string "type_str"));
104104+ tail = Js.to_string (Js.Unsafe.get t (Js.string "tail")) }
105105+ ) types_arr) in
106106+ Msg.Types { cell_id = get_int "cell_id"; types }
107107+ | "errors" ->
108108+ let errors_arr = Js.to_array (Js.Unsafe.get obj (Js.string "errors")) in
109109+ let errors = Array.to_list (Array.map (fun e ->
110110+ let sub_arr = Js.to_array (Js.Unsafe.get e (Js.string "sub")) in
111111+ let sub = Array.to_list (Array.map Js.to_string sub_arr) in
112112+ { Msg.kind = Js.to_string (Js.Unsafe.get e (Js.string "kind"));
113113+ loc = parse_location (Js.Unsafe.get e (Js.string "loc"));
114114+ main = Js.to_string (Js.Unsafe.get e (Js.string "main"));
115115+ sub;
116116+ source = Js.to_string (Js.Unsafe.get e (Js.string "source")) }
117117+ ) errors_arr) in
118118+ Msg.ErrorList { cell_id = get_int "cell_id"; errors }
119119+ | "eval_error" ->
120120+ Msg.EvalError { cell_id = get_int "cell_id"; message = get_string "message" }
121121+ | "env_created" ->
122122+ Msg.EnvCreated { env_id = get_string "env_id" }
123123+ | "env_destroyed" ->
124124+ Msg.EnvDestroyed { env_id = get_string "env_id" }
125125+ | "output_at" ->
126126+ let mime_vals_arr = Js.to_array (Js.Unsafe.get obj (Js.string "mime_vals")) in
127127+ let mime_vals = Array.to_list (Array.map (fun mv ->
128128+ { Msg.mime_type = Js.to_string (Js.Unsafe.get mv (Js.string "mime_type"));
129129+ data = Js.to_string (Js.Unsafe.get mv (Js.string "data")) }
130130+ ) mime_vals_arr) in
131131+ Msg.OutputAt {
132132+ cell_id = get_int "cell_id";
133133+ loc = get_int "loc";
134134+ caml_ppf = get_string "caml_ppf";
135135+ mime_vals;
136136+ }
137137+ | _ -> failwith ("Unknown message type: " ^ typ)
138138+139139+(** Handle incoming message from worker *)
140140+let handle_message t msg =
141141+ let data = Brr_message.Ev.data (Brr.Ev.as_type msg) in
142142+ let parsed = parse_worker_msg (Js_of_ocaml.Js.to_string data) in
143143+ match parsed with
144144+ | Msg.Ready ->
145145+ t.ready <- true;
146146+ Queue.iter (fun f -> f ()) t.ready_waiters;
147147+ Queue.clear t.ready_waiters
148148+ | Msg.InitError _ ->
149149+ t.ready <- true;
150150+ Queue.iter (fun f -> f ()) t.ready_waiters;
151151+ Queue.clear t.ready_waiters
152152+ | Msg.OutputAt { cell_id; loc; caml_ppf; mime_vals } ->
153153+ (match Hashtbl.find_opt t.pending_stream cell_id with
154154+ | Some push -> push (Some (Phrase { cell_id; loc; caml_ppf; mime_vals }))
155155+ | None -> ())
156156+ | Msg.Output { cell_id; stdout; stderr; caml_ppf; mime_vals } ->
157157+ (* Handle streaming eval *)
158158+ (match Hashtbl.find_opt t.pending_stream cell_id with
159159+ | Some push ->
160160+ Hashtbl.remove t.pending_stream cell_id;
161161+ push (Some (Done { cell_id; stdout; stderr; caml_ppf; mime_vals }));
162162+ push None (* Close the stream *)
163163+ | None -> ());
164164+ (* Handle regular eval *)
165165+ (match Hashtbl.find_opt t.pending cell_id with
166166+ | Some resolver ->
167167+ Hashtbl.remove t.pending cell_id;
168168+ Lwt.wakeup resolver parsed
169169+ | None -> ())
170170+ | Msg.EvalError { cell_id; message } ->
171171+ (* Handle streaming eval *)
172172+ (match Hashtbl.find_opt t.pending_stream cell_id with
173173+ | Some push ->
174174+ Hashtbl.remove t.pending_stream cell_id;
175175+ push (Some (Error message));
176176+ push None (* Close the stream *)
177177+ | None -> ());
178178+ (* Handle regular eval *)
179179+ (match Hashtbl.find_opt t.pending cell_id with
180180+ | Some resolver ->
181181+ Hashtbl.remove t.pending cell_id;
182182+ Lwt.wakeup resolver parsed
183183+ | None -> ())
184184+ | Msg.Completions { cell_id; _ }
185185+ | Msg.Types { cell_id; _ } | Msg.ErrorList { cell_id; _ } ->
186186+ (match Hashtbl.find_opt t.pending cell_id with
187187+ | Some resolver ->
188188+ Hashtbl.remove t.pending cell_id;
189189+ Lwt.wakeup resolver parsed
190190+ | None -> ())
191191+ | Msg.EnvCreated { env_id } | Msg.EnvDestroyed { env_id } ->
192192+ (match Hashtbl.find_opt t.pending_env env_id with
193193+ | Some resolver ->
194194+ Hashtbl.remove t.pending_env env_id;
195195+ Lwt.wakeup resolver parsed
196196+ | None -> ())
197197+198198+(** Create a new worker client.
199199+ @param timeout Timeout in milliseconds (default: 30000) *)
200200+let create ?(timeout = 30000) url =
201201+ let worker = Brr_worker.create (Jstr.v url) in
202202+ let t = {
203203+ worker;
204204+ timeout;
205205+ cell_id = 0;
206206+ ready = false;
207207+ ready_waiters = Queue.create ();
208208+ pending = Hashtbl.create 16;
209209+ pending_env = Hashtbl.create 16;
210210+ pending_stream = Hashtbl.create 16;
211211+ } in
212212+ let _listener =
213213+ Brr.Ev.listen Brr_message.Ev.message (handle_message t) (Brr_worker.as_target worker)
214214+ in
215215+ t
216216+217217+(** Get next cell ID *)
218218+let next_cell_id t =
219219+ t.cell_id <- t.cell_id + 1;
220220+ t.cell_id
221221+222222+(** Send a message to the worker *)
223223+let send t msg =
224224+ let open Js_of_ocaml in
225225+ let json = match msg with
226226+ | `Init config ->
227227+ let obj = Js.Unsafe.obj [|
228228+ ("type", Js.Unsafe.inject (Js.string "init"));
229229+ ("findlib_requires", Js.Unsafe.inject (Js.array (Array.of_list (List.map Js.string config.Msg.findlib_requires))));
230230+ ("stdlib_dcs", Js.Unsafe.inject (match config.Msg.stdlib_dcs with Some s -> Js.some (Js.string s) | None -> Js.null));
231231+ ("findlib_index", Js.Unsafe.inject (match config.Msg.findlib_index with Some s -> Js.some (Js.string s) | None -> Js.null));
232232+ |] in
233233+ Js.to_string (Json.output obj)
234234+ | `Eval (cell_id, env_id, code) ->
235235+ let obj = Js.Unsafe.obj [|
236236+ ("type", Js.Unsafe.inject (Js.string "eval"));
237237+ ("cell_id", Js.Unsafe.inject cell_id);
238238+ ("env_id", Js.Unsafe.inject (Js.string env_id));
239239+ ("code", Js.Unsafe.inject (Js.string code));
240240+ |] in
241241+ Js.to_string (Json.output obj)
242242+ | `Complete (cell_id, env_id, source, position) ->
243243+ let obj = Js.Unsafe.obj [|
244244+ ("type", Js.Unsafe.inject (Js.string "complete"));
245245+ ("cell_id", Js.Unsafe.inject cell_id);
246246+ ("env_id", Js.Unsafe.inject (Js.string env_id));
247247+ ("source", Js.Unsafe.inject (Js.string source));
248248+ ("position", Js.Unsafe.inject position);
249249+ |] in
250250+ Js.to_string (Json.output obj)
251251+ | `TypeAt (cell_id, env_id, source, position) ->
252252+ let obj = Js.Unsafe.obj [|
253253+ ("type", Js.Unsafe.inject (Js.string "type_at"));
254254+ ("cell_id", Js.Unsafe.inject cell_id);
255255+ ("env_id", Js.Unsafe.inject (Js.string env_id));
256256+ ("source", Js.Unsafe.inject (Js.string source));
257257+ ("position", Js.Unsafe.inject position);
258258+ |] in
259259+ Js.to_string (Json.output obj)
260260+ | `Errors (cell_id, env_id, source) ->
261261+ let obj = Js.Unsafe.obj [|
262262+ ("type", Js.Unsafe.inject (Js.string "errors"));
263263+ ("cell_id", Js.Unsafe.inject cell_id);
264264+ ("env_id", Js.Unsafe.inject (Js.string env_id));
265265+ ("source", Js.Unsafe.inject (Js.string source));
266266+ |] in
267267+ Js.to_string (Json.output obj)
268268+ | `CreateEnv env_id ->
269269+ let obj = Js.Unsafe.obj [|
270270+ ("type", Js.Unsafe.inject (Js.string "create_env"));
271271+ ("env_id", Js.Unsafe.inject (Js.string env_id));
272272+ |] in
273273+ Js.to_string (Json.output obj)
274274+ | `DestroyEnv env_id ->
275275+ let obj = Js.Unsafe.obj [|
276276+ ("type", Js.Unsafe.inject (Js.string "destroy_env"));
277277+ ("env_id", Js.Unsafe.inject (Js.string env_id));
278278+ |] in
279279+ Js.to_string (Json.output obj)
280280+ in
281281+ Brr_worker.post t.worker (Js.string json)
282282+283283+(** Wait for the worker to be ready *)
284284+let wait_ready t =
285285+ if t.ready then Lwt.return_unit
286286+ else
287287+ let promise, resolver = Lwt.wait () in
288288+ Queue.push (fun () -> Lwt.wakeup resolver ()) t.ready_waiters;
289289+ promise
290290+291291+(** Initialize the worker *)
292292+let init t config =
293293+ let open Lwt.Infix in
294294+ send t (`Init config);
295295+ wait_ready t >>= fun () ->
296296+ Lwt.return_unit
297297+298298+(** Evaluate OCaml code *)
299299+let eval t ?(env_id = "default") code =
300300+ let open Lwt.Infix in
301301+ wait_ready t >>= fun () ->
302302+ let cell_id = next_cell_id t in
303303+ let promise, resolver = Lwt.wait () in
304304+ Hashtbl.add t.pending cell_id resolver;
305305+ send t (`Eval (cell_id, env_id, code));
306306+ promise >>= fun msg ->
307307+ match msg with
308308+ | Msg.Output { cell_id; stdout; stderr; caml_ppf; mime_vals } ->
309309+ Lwt.return { cell_id; stdout; stderr; caml_ppf; mime_vals }
310310+ | Msg.EvalError { message; _ } ->
311311+ Lwt.fail (EvalError message)
312312+ | _ -> Lwt.fail (Failure "Unexpected response")
313313+314314+(** Evaluate OCaml code with streaming output.
315315+ Returns a stream of events: [Phrase] for each phrase as it executes,
316316+ then [Done] with the final result, or [Error] if evaluation fails. *)
317317+let eval_stream t ?(env_id = "default") code =
318318+ let stream, push = Lwt_stream.create () in
319319+ (* Wait for ready before sending, but return stream immediately *)
320320+ Lwt.async (fun () ->
321321+ let open Lwt.Infix in
322322+ wait_ready t >|= fun () ->
323323+ let cell_id = next_cell_id t in
324324+ Hashtbl.add t.pending_stream cell_id push;
325325+ send t (`Eval (cell_id, env_id, code)));
326326+ stream
327327+328328+(** Get completions *)
329329+let complete t ?(env_id = "default") source position =
330330+ let open Lwt.Infix in
331331+ wait_ready t >>= fun () ->
332332+ let cell_id = next_cell_id t in
333333+ let promise, resolver = Lwt.wait () in
334334+ Hashtbl.add t.pending cell_id resolver;
335335+ send t (`Complete (cell_id, env_id, source, position));
336336+ promise >>= fun msg ->
337337+ match msg with
338338+ | Msg.Completions { completions; _ } ->
339339+ Lwt.return completions
340340+ | Msg.EvalError { message; _ } ->
341341+ Lwt.fail (EvalError message)
342342+ | _ -> Lwt.fail (Failure "Unexpected response")
343343+344344+(** Get type at position *)
345345+let type_at t ?(env_id = "default") source position =
346346+ let open Lwt.Infix in
347347+ wait_ready t >>= fun () ->
348348+ let cell_id = next_cell_id t in
349349+ let promise, resolver = Lwt.wait () in
350350+ Hashtbl.add t.pending cell_id resolver;
351351+ send t (`TypeAt (cell_id, env_id, source, position));
352352+ promise >>= fun msg ->
353353+ match msg with
354354+ | Msg.Types { types; _ } ->
355355+ Lwt.return types
356356+ | Msg.EvalError { message; _ } ->
357357+ Lwt.fail (EvalError message)
358358+ | _ -> Lwt.fail (Failure "Unexpected response")
359359+360360+(** Get errors *)
361361+let errors t ?(env_id = "default") source =
362362+ let open Lwt.Infix in
363363+ wait_ready t >>= fun () ->
364364+ let cell_id = next_cell_id t in
365365+ let promise, resolver = Lwt.wait () in
366366+ Hashtbl.add t.pending cell_id resolver;
367367+ send t (`Errors (cell_id, env_id, source));
368368+ promise >>= fun msg ->
369369+ match msg with
370370+ | Msg.ErrorList { errors; _ } ->
371371+ Lwt.return errors
372372+ | Msg.EvalError { message; _ } ->
373373+ Lwt.fail (EvalError message)
374374+ | _ -> Lwt.fail (Failure "Unexpected response")
375375+376376+(** Create environment *)
377377+let create_env t env_id =
378378+ let open Lwt.Infix in
379379+ wait_ready t >>= fun () ->
380380+ let promise, resolver = Lwt.wait () in
381381+ Hashtbl.add t.pending_env env_id resolver;
382382+ send t (`CreateEnv env_id);
383383+ promise >>= fun msg ->
384384+ match msg with
385385+ | Msg.EnvCreated _ -> Lwt.return_unit
386386+ | Msg.InitError { message } -> Lwt.fail (InitError message)
387387+ | _ -> Lwt.fail (Failure "Unexpected response")
388388+389389+(** Destroy environment *)
390390+let destroy_env t env_id =
391391+ let open Lwt.Infix in
392392+ wait_ready t >>= fun () ->
393393+ let promise, resolver = Lwt.wait () in
394394+ Hashtbl.add t.pending_env env_id resolver;
395395+ send t (`DestroyEnv env_id);
396396+ promise >>= fun msg ->
397397+ match msg with
398398+ | Msg.EnvDestroyed _ -> Lwt.return_unit
399399+ | Msg.InitError { message } -> Lwt.fail (InitError message)
400400+ | _ -> Lwt.fail (Failure "Unexpected response")
401401+402402+(** Terminate the worker *)
403403+let terminate t =
404404+ Brr_worker.terminate t.worker
···11+(** IDL for talking to the toplevel webworker *)
22+33+open Rpc
44+open Idl
55+66+let sockpath =
77+ match Sys.getenv_opt "JS_TOP_WORKER_SOCK" with
88+ | Some path -> path
99+ | None -> "/tmp/js_top_worker.sock"
1010+1111+open Merlin_kernel
1212+module Location = Ocaml_parsing.Location
1313+1414+type lexing_position = Lexing.position = {
1515+ pos_fname: string;
1616+ pos_lnum: int;
1717+ pos_bol: int;
1818+ pos_cnum: int;
1919+} [@@deriving rpcty]
2020+2121+type location = Location.t = {
2222+ loc_start: lexing_position;
2323+ loc_end: lexing_position;
2424+ loc_ghost: bool;
2525+} [@@deriving rpcty]
2626+2727+type location_error_source = Location.error_source =
2828+ | Lexer
2929+ | Parser
3030+ | Typer
3131+ | Warning
3232+ | Unknown
3333+ | Env
3434+ | Config [@@deriving rpcty]
3535+3636+type location_report_kind = Location.report_kind =
3737+ | Report_error
3838+ | Report_warning of string
3939+ | Report_warning_as_error of string
4040+ | Report_alert of string
4141+ | Report_alert_as_error of string [@@deriving rpcty]
4242+4343+type source = string [@@deriving rpcty]
4444+4545+(** CMIs are provided either statically or as URLs to be downloaded on demand *)
4646+4747+(** Dynamic cmis are loaded from beneath the given url. In addition the
4848+ top-level modules are specified, and prefixes for other modules. For
4949+ example, for the OCaml standard library, a user might pass:
5050+5151+ {[
5252+ { dcs_url="/static/stdlib";
5353+ dcs_toplevel_modules=["Stdlib"];
5454+ dcs_file_prefixes=["stdlib__"]; }
5555+ ]}
5656+5757+ In which case, merlin will expect to be able to download a valid file
5858+ from the url ["/static/stdlib/stdlib.cmi"] corresponding to the
5959+ specified toplevel module, and it will also attempt to download any
6060+ module with the prefix ["Stdlib__"] from the same base url, so for
6161+ example if an attempt is made to look up the module ["Stdlib__Foo"]
6262+ then merlin-js will attempt to download a file from the url
6363+ ["/static/stdlib/stdlib__Foo.cmi"].
6464+ *)
6565+6666+type dynamic_cmis = {
6767+ dcs_url : string;
6868+ dcs_toplevel_modules : string list;
6969+ dcs_file_prefixes : string list;
7070+}
7171+7272+and static_cmi = {
7373+ sc_name : string; (* capitalised, e.g. 'Stdlib' *)
7474+ sc_content : string;
7575+}
7676+7777+and cmis = {
7878+ static_cmis : static_cmi list;
7979+ dynamic_cmis : dynamic_cmis list;
8080+} [@@deriving rpcty]
8181+8282+type action =
8383+ | Complete_prefix of source * Msource.position
8484+ | Type_enclosing of source * Msource.position
8585+ | All_errors of source
8686+ | Add_cmis of cmis
8787+8888+type error = {
8989+ kind : location_report_kind;
9090+ loc: location;
9191+ main : string;
9292+ sub : string list;
9393+ source : location_error_source;
9494+} [@@deriving rpcty]
9595+9696+type error_list = error list [@@deriving rpcty]
9797+9898+type kind_ty =
9999+ Constructor
100100+ | Keyword
101101+ | Label
102102+ | MethodCall
103103+ | Modtype
104104+ | Module
105105+ | Type
106106+ | Value
107107+ | Variant [@@deriving rpcty]
108108+109109+ type query_protocol_compl_entry = {
110110+ name: string;
111111+ kind: kind_ty;
112112+ desc: string;
113113+ info: string;
114114+ deprecated: bool;
115115+ } [@@deriving rpcty]
116116+117117+118118+type completions = {
119119+ from: int;
120120+ to_: int;
121121+ entries : query_protocol_compl_entry list
122122+} [@@deriving rpcty]
123123+124124+type msource_position =
125125+ | Start
126126+ | Offset of int
127127+ | Logical of int * int
128128+ | End [@@deriving rpcty]
129129+130130+type is_tail_position =
131131+ | No | Tail_position | Tail_call [@@deriving rpcty]
132132+133133+type index_or_string =
134134+ | Index of int
135135+ | String of string [@@deriving rpcty]
136136+137137+138138+type typed_enclosings = location * index_or_string * is_tail_position [@@deriving rpcty]
139139+type typed_enclosings_list = typed_enclosings list [@@deriving rpcty]
140140+let report_source_to_string = function
141141+ | Location.Lexer -> "lexer"
142142+ | Location.Parser -> "parser"
143143+ | Location.Typer -> "typer"
144144+ | Location.Warning -> "warning" (* todo incorrect ?*)
145145+ | Location.Unknown -> "unknown"
146146+ | Location.Env -> "env"
147147+ | Location.Config -> "config"
148148+149149+type highlight = { line1 : int; line2 : int; col1 : int; col2 : int }
150150+[@@deriving rpcty]
151151+(** An area to be highlighted *)
152152+type encoding = Mime_printer.encoding = | Noencoding | Base64 [@@deriving rpcty]
153153+154154+type mime_val = Mime_printer.t = {
155155+ mime_type : string;
156156+ encoding : encoding;
157157+ data : string;
158158+}
159159+[@@deriving rpcty]
160160+161161+type exec_result = {
162162+ stdout : string option;
163163+ stderr : string option;
164164+ sharp_ppf : string option;
165165+ caml_ppf : string option;
166166+ highlight : highlight option;
167167+ mime_vals : mime_val list;
168168+}
169169+[@@deriving rpcty]
170170+(** Represents the result of executing a toplevel phrase *)
171171+172172+type script_parts = (int * int) list (* Input length and output length *)
173173+[@@deriving rpcty]
174174+175175+type exec_toplevel_result = {
176176+ script : string;
177177+ parts : script_parts;
178178+ mime_vals : mime_val list;
179179+}
180180+[@@deriving rpcty]
181181+(** Represents the result of executing a toplevel script *)
182182+183183+type cma = {
184184+ url : string; (** URL where the cma is available *)
185185+ fn : string; (** Name of the 'wrapping' function *)
186186+}
187187+[@@deriving rpcty]
188188+189189+type init_config = {
190190+ findlib_requires : string list; (** Findlib packages to require *)
191191+ stdlib_dcs : string option; (** URL to the dynamic cmis for the OCaml standard library *)
192192+ findlib_index : string option; (** URL to the findlib_index file. Defaults to "findlib_index" *)
193193+ execute : bool (** Whether this session should support execution or not. *)
194194+} [@@deriving rpcty]
195195+type err = InternalError of string [@@deriving rpcty]
196196+197197+type opt_id = string option [@@deriving rpcty]
198198+199199+type env_id = string [@@deriving rpcty]
200200+(** Environment identifier. If empty string, uses the default environment. *)
201201+202202+type env_id_list = string list [@@deriving rpcty]
203203+(** List of environment identifiers *)
204204+205205+type dependencies = string list [@@deriving rpcty]
206206+(** The ids of the cells that are dependencies *)
207207+208208+module E = Idl.Error.Make (struct
209209+ type t = err
210210+211211+ let t = err
212212+ let internal_error_of e = Some (InternalError (Printexc.to_string e))
213213+end)
214214+215215+let err = E.error
216216+217217+module Make (R : RPC) = struct
218218+ open R
219219+220220+ let description =
221221+ Interface.
222222+ {
223223+ name = "Toplevel";
224224+ namespace = None;
225225+ description =
226226+ [ "Functions for manipulating the toplevel worker thread" ];
227227+ version = (1, 0, 0);
228228+ }
229229+230230+ let implementation = implement description
231231+ let unit_p = Param.mk Types.unit
232232+ let phrase_p = Param.mk ~name:"string" ~description:["The OCaml phrase to execute"] Types.string
233233+ let id_p = Param.mk opt_id
234234+ let env_id_p = Param.mk ~name:"env_id" ~description:["Environment ID (empty string for default)"] env_id
235235+ let env_id_list_p = Param.mk env_id_list
236236+ let dependencies_p = Param.mk dependencies
237237+ let exec_result_p = Param.mk exec_result
238238+239239+ let source_p = Param.mk source
240240+ let position_p = Param.mk msource_position
241241+242242+ let completions_p = Param.mk completions
243243+ let error_list_p = Param.mk error_list
244244+ let typed_enclosings_p = Param.mk typed_enclosings_list
245245+ let is_toplevel_p = Param.mk ~name:"is_toplevel" Types.bool
246246+247247+ let toplevel_script_p = Param.mk ~description:[
248248+ "A toplevel script is a sequence of toplevel phrases interspersed with";
249249+ "The output from the toplevel. Each phase must be preceded by '# ', and";
250250+ "the output from the toplevel is indented by 2 spaces."
251251+ ] Types.string
252252+253253+ let exec_toplevel_result_p = Param.mk exec_toplevel_result
254254+255255+ let init_libs =
256256+ Param.mk ~name:"init_libs"
257257+ ~description:
258258+ [
259259+ "Configuration for the toplevel.";
260260+ ]
261261+ init_config
262262+263263+ let init =
264264+ declare "init"
265265+ [ "Initialise the toplevel. This must be called before any other API." ]
266266+ (init_libs @-> returning unit_p err)
267267+268268+ (** {2 Environment Management} *)
269269+270270+ let create_env =
271271+ declare "create_env"
272272+ [
273273+ "Create a new isolated execution environment with the given ID.";
274274+ "Returns unit on success. The environment must be set up with";
275275+ "setup_env before use.";
276276+ ]
277277+ (env_id_p @-> returning unit_p err)
278278+279279+ let destroy_env =
280280+ declare "destroy_env"
281281+ [
282282+ "Destroy an execution environment, freeing its resources.";
283283+ "The environment ID must exist.";
284284+ ]
285285+ (env_id_p @-> returning unit_p err)
286286+287287+ let list_envs =
288288+ declare "list_envs"
289289+ [ "List all existing environment IDs." ]
290290+ (unit_p @-> returning env_id_list_p err)
291291+292292+ let setup =
293293+ declare "setup"
294294+ [
295295+ "Start the toplevel for the given environment. Return value is the";
296296+ "initial blurb printed when starting a toplevel. Note that the";
297297+ "toplevel must be initialised first. If env_id is None, uses the";
298298+ "default environment.";
299299+ ]
300300+ (env_id_p @-> returning exec_result_p err)
301301+302302+ let exec =
303303+ declare "exec"
304304+ [
305305+ "Execute a phrase using the toplevel. The toplevel must have been";
306306+ "initialised first. If env_id is None, uses the default environment.";
307307+ ]
308308+ (env_id_p @-> phrase_p @-> returning exec_result_p err)
309309+310310+ let exec_toplevel =
311311+ declare "exec_toplevel"
312312+ [
313313+ "Execute a toplevel script. The toplevel must have been";
314314+ "initialised first. Returns the updated toplevel script.";
315315+ "If env_id is None, uses the default environment.";
316316+ ]
317317+ (env_id_p @-> toplevel_script_p @-> returning exec_toplevel_result_p err)
318318+319319+ let complete_prefix =
320320+ declare "complete_prefix"
321321+ [
322322+ "Complete a prefix. If env_id is None, uses the default environment.";
323323+ ]
324324+ (env_id_p @-> id_p @-> dependencies_p @-> is_toplevel_p @-> source_p @-> position_p @-> returning completions_p err)
325325+326326+ let query_errors =
327327+ declare "query_errors"
328328+ [
329329+ "Query the errors in the given source.";
330330+ "If env_id is None, uses the default environment.";
331331+ ]
332332+ (env_id_p @-> id_p @-> dependencies_p @-> is_toplevel_p @-> source_p @-> returning error_list_p err)
333333+334334+ let type_enclosing =
335335+ declare "type_enclosing"
336336+ [
337337+ "Get the type of the enclosing expression.";
338338+ "If env_id is None, uses the default environment.";
339339+ ]
340340+ (env_id_p @-> id_p @-> dependencies_p @-> is_toplevel_p @-> source_p @-> position_p @-> returning typed_enclosings_p err)
341341+end
+2310
js_top_worker/idl/toplevel_api_gen.ml
···11+[@@@ocaml.ppx.context
22+ {
33+ tool_name = "ppx_driver";
44+ include_dirs = [];
55+ hidden_include_dirs = [];
66+ load_path = ([], []);
77+ open_modules = [];
88+ for_package = None;
99+ debug = false;
1010+ use_threads = false;
1111+ use_vmthreads = false;
1212+ recursive_types = false;
1313+ principal = false;
1414+ no_alias_deps = false;
1515+ unboxed_types = false;
1616+ unsafe_string = false;
1717+ cookies = [("library-name", "js_top_worker_rpc_def")]
1818+ }]
1919+[@@@ocaml.text " IDL for talking to the toplevel webworker "]
2020+open Rpc
2121+open Idl
2222+let sockpath =
2323+ match Sys.getenv_opt "JS_TOP_WORKER_SOCK" with
2424+ | Some path -> path
2525+ | None -> "/tmp/js_top_worker.sock"
2626+open Merlin_kernel
2727+module Location = Ocaml_parsing.Location
2828+type lexing_position = Lexing.position =
2929+ {
3030+ pos_fname: string ;
3131+ pos_lnum: int ;
3232+ pos_bol: int ;
3333+ pos_cnum: int }[@@deriving rpcty]
3434+include
3535+ struct
3636+ let _ = fun (_ : lexing_position) -> ()
3737+ let rec lexing_position_pos_fname : (_, lexing_position) Rpc.Types.field
3838+ =
3939+ {
4040+ Rpc.Types.fname = "pos_fname";
4141+ Rpc.Types.field = (let open Rpc.Types in Basic String);
4242+ Rpc.Types.fdefault = None;
4343+ Rpc.Types.fdescription = [];
4444+ Rpc.Types.fversion = None;
4545+ Rpc.Types.fget = (fun _r -> _r.pos_fname);
4646+ Rpc.Types.fset = (fun v _s -> { _s with pos_fname = v })
4747+ }
4848+ and lexing_position_pos_lnum : (_, lexing_position) Rpc.Types.field =
4949+ {
5050+ Rpc.Types.fname = "pos_lnum";
5151+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
5252+ Rpc.Types.fdefault = None;
5353+ Rpc.Types.fdescription = [];
5454+ Rpc.Types.fversion = None;
5555+ Rpc.Types.fget = (fun _r -> _r.pos_lnum);
5656+ Rpc.Types.fset = (fun v _s -> { _s with pos_lnum = v })
5757+ }
5858+ and lexing_position_pos_bol : (_, lexing_position) Rpc.Types.field =
5959+ {
6060+ Rpc.Types.fname = "pos_bol";
6161+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
6262+ Rpc.Types.fdefault = None;
6363+ Rpc.Types.fdescription = [];
6464+ Rpc.Types.fversion = None;
6565+ Rpc.Types.fget = (fun _r -> _r.pos_bol);
6666+ Rpc.Types.fset = (fun v _s -> { _s with pos_bol = v })
6767+ }
6868+ and lexing_position_pos_cnum : (_, lexing_position) Rpc.Types.field =
6969+ {
7070+ Rpc.Types.fname = "pos_cnum";
7171+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
7272+ Rpc.Types.fdefault = None;
7373+ Rpc.Types.fdescription = [];
7474+ Rpc.Types.fversion = None;
7575+ Rpc.Types.fget = (fun _r -> _r.pos_cnum);
7676+ Rpc.Types.fset = (fun v _s -> { _s with pos_cnum = v })
7777+ }
7878+ and typ_of_lexing_position =
7979+ Rpc.Types.Struct
8080+ ({
8181+ Rpc.Types.fields =
8282+ [Rpc.Types.BoxedField lexing_position_pos_fname;
8383+ Rpc.Types.BoxedField lexing_position_pos_lnum;
8484+ Rpc.Types.BoxedField lexing_position_pos_bol;
8585+ Rpc.Types.BoxedField lexing_position_pos_cnum];
8686+ Rpc.Types.sname = "lexing_position";
8787+ Rpc.Types.version = None;
8888+ Rpc.Types.constructor =
8989+ (fun getter ->
9090+ let open Rresult.R in
9191+ (getter.Rpc.Types.field_get "pos_cnum"
9292+ (let open Rpc.Types in Basic Int))
9393+ >>=
9494+ (fun lexing_position_pos_cnum ->
9595+ (getter.Rpc.Types.field_get "pos_bol"
9696+ (let open Rpc.Types in Basic Int))
9797+ >>=
9898+ (fun lexing_position_pos_bol ->
9999+ (getter.Rpc.Types.field_get "pos_lnum"
100100+ (let open Rpc.Types in Basic Int))
101101+ >>=
102102+ (fun lexing_position_pos_lnum ->
103103+ (getter.Rpc.Types.field_get "pos_fname"
104104+ (let open Rpc.Types in Basic String))
105105+ >>=
106106+ (fun lexing_position_pos_fname ->
107107+ return
108108+ {
109109+ pos_fname =
110110+ lexing_position_pos_fname;
111111+ pos_lnum = lexing_position_pos_lnum;
112112+ pos_bol = lexing_position_pos_bol;
113113+ pos_cnum = lexing_position_pos_cnum
114114+ })))))
115115+ } : lexing_position Rpc.Types.structure)
116116+ and lexing_position =
117117+ {
118118+ Rpc.Types.name = "lexing_position";
119119+ Rpc.Types.description = [];
120120+ Rpc.Types.ty = typ_of_lexing_position
121121+ }
122122+ let _ = lexing_position_pos_fname
123123+ and _ = lexing_position_pos_lnum
124124+ and _ = lexing_position_pos_bol
125125+ and _ = lexing_position_pos_cnum
126126+ and _ = typ_of_lexing_position
127127+ and _ = lexing_position
128128+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
129129+type location = Location.t =
130130+ {
131131+ loc_start: lexing_position ;
132132+ loc_end: lexing_position ;
133133+ loc_ghost: bool }[@@deriving rpcty]
134134+include
135135+ struct
136136+ let _ = fun (_ : location) -> ()
137137+ let rec location_loc_start : (_, location) Rpc.Types.field =
138138+ {
139139+ Rpc.Types.fname = "loc_start";
140140+ Rpc.Types.field = typ_of_lexing_position;
141141+ Rpc.Types.fdefault = None;
142142+ Rpc.Types.fdescription = [];
143143+ Rpc.Types.fversion = None;
144144+ Rpc.Types.fget = (fun _r -> _r.loc_start);
145145+ Rpc.Types.fset = (fun v _s -> { _s with loc_start = v })
146146+ }
147147+ and location_loc_end : (_, location) Rpc.Types.field =
148148+ {
149149+ Rpc.Types.fname = "loc_end";
150150+ Rpc.Types.field = typ_of_lexing_position;
151151+ Rpc.Types.fdefault = None;
152152+ Rpc.Types.fdescription = [];
153153+ Rpc.Types.fversion = None;
154154+ Rpc.Types.fget = (fun _r -> _r.loc_end);
155155+ Rpc.Types.fset = (fun v _s -> { _s with loc_end = v })
156156+ }
157157+ and location_loc_ghost : (_, location) Rpc.Types.field =
158158+ {
159159+ Rpc.Types.fname = "loc_ghost";
160160+ Rpc.Types.field = (let open Rpc.Types in Basic Bool);
161161+ Rpc.Types.fdefault = None;
162162+ Rpc.Types.fdescription = [];
163163+ Rpc.Types.fversion = None;
164164+ Rpc.Types.fget = (fun _r -> _r.loc_ghost);
165165+ Rpc.Types.fset = (fun v _s -> { _s with loc_ghost = v })
166166+ }
167167+ and typ_of_location =
168168+ Rpc.Types.Struct
169169+ ({
170170+ Rpc.Types.fields =
171171+ [Rpc.Types.BoxedField location_loc_start;
172172+ Rpc.Types.BoxedField location_loc_end;
173173+ Rpc.Types.BoxedField location_loc_ghost];
174174+ Rpc.Types.sname = "location";
175175+ Rpc.Types.version = None;
176176+ Rpc.Types.constructor =
177177+ (fun getter ->
178178+ let open Rresult.R in
179179+ (getter.Rpc.Types.field_get "loc_ghost"
180180+ (let open Rpc.Types in Basic Bool))
181181+ >>=
182182+ (fun location_loc_ghost ->
183183+ (getter.Rpc.Types.field_get "loc_end"
184184+ typ_of_lexing_position)
185185+ >>=
186186+ (fun location_loc_end ->
187187+ (getter.Rpc.Types.field_get "loc_start"
188188+ typ_of_lexing_position)
189189+ >>=
190190+ (fun location_loc_start ->
191191+ return
192192+ {
193193+ loc_start = location_loc_start;
194194+ loc_end = location_loc_end;
195195+ loc_ghost = location_loc_ghost
196196+ }))))
197197+ } : location Rpc.Types.structure)
198198+ and location =
199199+ {
200200+ Rpc.Types.name = "location";
201201+ Rpc.Types.description = [];
202202+ Rpc.Types.ty = typ_of_location
203203+ }
204204+ let _ = location_loc_start
205205+ and _ = location_loc_end
206206+ and _ = location_loc_ghost
207207+ and _ = typ_of_location
208208+ and _ = location
209209+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
210210+type location_error_source = Location.error_source =
211211+ | Lexer
212212+ | Parser
213213+ | Typer
214214+ | Warning
215215+ | Unknown
216216+ | Env
217217+ | Config [@@deriving rpcty]
218218+include
219219+ struct
220220+ let _ = fun (_ : location_error_source) -> ()
221221+ let rec typ_of_location_error_source =
222222+ Rpc.Types.Variant
223223+ ({
224224+ Rpc.Types.vname = "location_error_source";
225225+ Rpc.Types.variants =
226226+ [BoxedTag
227227+ {
228228+ Rpc.Types.tname = "Lexer";
229229+ Rpc.Types.tcontents = Unit;
230230+ Rpc.Types.tversion = None;
231231+ Rpc.Types.tdescription = [];
232232+ Rpc.Types.tpreview =
233233+ ((function | Lexer -> Some () | _ -> None));
234234+ Rpc.Types.treview = ((function | () -> Lexer))
235235+ };
236236+ BoxedTag
237237+ {
238238+ Rpc.Types.tname = "Parser";
239239+ Rpc.Types.tcontents = Unit;
240240+ Rpc.Types.tversion = None;
241241+ Rpc.Types.tdescription = [];
242242+ Rpc.Types.tpreview =
243243+ ((function | Parser -> Some () | _ -> None));
244244+ Rpc.Types.treview = ((function | () -> Parser))
245245+ };
246246+ BoxedTag
247247+ {
248248+ Rpc.Types.tname = "Typer";
249249+ Rpc.Types.tcontents = Unit;
250250+ Rpc.Types.tversion = None;
251251+ Rpc.Types.tdescription = [];
252252+ Rpc.Types.tpreview =
253253+ ((function | Typer -> Some () | _ -> None));
254254+ Rpc.Types.treview = ((function | () -> Typer))
255255+ };
256256+ BoxedTag
257257+ {
258258+ Rpc.Types.tname = "Warning";
259259+ Rpc.Types.tcontents = Unit;
260260+ Rpc.Types.tversion = None;
261261+ Rpc.Types.tdescription = [];
262262+ Rpc.Types.tpreview =
263263+ ((function | Warning -> Some () | _ -> None));
264264+ Rpc.Types.treview = ((function | () -> Warning))
265265+ };
266266+ BoxedTag
267267+ {
268268+ Rpc.Types.tname = "Unknown";
269269+ Rpc.Types.tcontents = Unit;
270270+ Rpc.Types.tversion = None;
271271+ Rpc.Types.tdescription = [];
272272+ Rpc.Types.tpreview =
273273+ ((function | Unknown -> Some () | _ -> None));
274274+ Rpc.Types.treview = ((function | () -> Unknown))
275275+ };
276276+ BoxedTag
277277+ {
278278+ Rpc.Types.tname = "Env";
279279+ Rpc.Types.tcontents = Unit;
280280+ Rpc.Types.tversion = None;
281281+ Rpc.Types.tdescription = [];
282282+ Rpc.Types.tpreview =
283283+ ((function | Env -> Some () | _ -> None));
284284+ Rpc.Types.treview = ((function | () -> Env))
285285+ };
286286+ BoxedTag
287287+ {
288288+ Rpc.Types.tname = "Config";
289289+ Rpc.Types.tcontents = Unit;
290290+ Rpc.Types.tversion = None;
291291+ Rpc.Types.tdescription = [];
292292+ Rpc.Types.tpreview =
293293+ ((function | Config -> Some () | _ -> None));
294294+ Rpc.Types.treview = ((function | () -> Config))
295295+ }];
296296+ Rpc.Types.vdefault = None;
297297+ Rpc.Types.vversion = None;
298298+ Rpc.Types.vconstructor =
299299+ (fun s' t ->
300300+ let s = String.lowercase_ascii s' in
301301+ match s with
302302+ | "lexer" ->
303303+ Rresult.R.bind (t.tget Unit)
304304+ (function | () -> Rresult.R.ok Lexer)
305305+ | "parser" ->
306306+ Rresult.R.bind (t.tget Unit)
307307+ (function | () -> Rresult.R.ok Parser)
308308+ | "typer" ->
309309+ Rresult.R.bind (t.tget Unit)
310310+ (function | () -> Rresult.R.ok Typer)
311311+ | "warning" ->
312312+ Rresult.R.bind (t.tget Unit)
313313+ (function | () -> Rresult.R.ok Warning)
314314+ | "unknown" ->
315315+ Rresult.R.bind (t.tget Unit)
316316+ (function | () -> Rresult.R.ok Unknown)
317317+ | "env" ->
318318+ Rresult.R.bind (t.tget Unit)
319319+ (function | () -> Rresult.R.ok Env)
320320+ | "config" ->
321321+ Rresult.R.bind (t.tget Unit)
322322+ (function | () -> Rresult.R.ok Config)
323323+ | _ ->
324324+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
325325+ } : location_error_source Rpc.Types.variant)
326326+ and location_error_source =
327327+ {
328328+ Rpc.Types.name = "location_error_source";
329329+ Rpc.Types.description = [];
330330+ Rpc.Types.ty = typ_of_location_error_source
331331+ }
332332+ let _ = typ_of_location_error_source
333333+ and _ = location_error_source
334334+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
335335+type location_report_kind = Location.report_kind =
336336+ | Report_error
337337+ | Report_warning of string
338338+ | Report_warning_as_error of string
339339+ | Report_alert of string
340340+ | Report_alert_as_error of string [@@deriving rpcty]
341341+include
342342+ struct
343343+ let _ = fun (_ : location_report_kind) -> ()
344344+ let rec typ_of_location_report_kind =
345345+ Rpc.Types.Variant
346346+ ({
347347+ Rpc.Types.vname = "location_report_kind";
348348+ Rpc.Types.variants =
349349+ [BoxedTag
350350+ {
351351+ Rpc.Types.tname = "Report_error";
352352+ Rpc.Types.tcontents = Unit;
353353+ Rpc.Types.tversion = None;
354354+ Rpc.Types.tdescription = [];
355355+ Rpc.Types.tpreview =
356356+ ((function | Report_error -> Some () | _ -> None));
357357+ Rpc.Types.treview = ((function | () -> Report_error))
358358+ };
359359+ BoxedTag
360360+ {
361361+ Rpc.Types.tname = "Report_warning";
362362+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic String));
363363+ Rpc.Types.tversion = None;
364364+ Rpc.Types.tdescription = [];
365365+ Rpc.Types.tpreview =
366366+ ((function | Report_warning a0 -> Some a0 | _ -> None));
367367+ Rpc.Types.treview = ((function | a0 -> Report_warning a0))
368368+ };
369369+ BoxedTag
370370+ {
371371+ Rpc.Types.tname = "Report_warning_as_error";
372372+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic String));
373373+ Rpc.Types.tversion = None;
374374+ Rpc.Types.tdescription = [];
375375+ Rpc.Types.tpreview =
376376+ ((function
377377+ | Report_warning_as_error a0 -> Some a0
378378+ | _ -> None));
379379+ Rpc.Types.treview =
380380+ ((function | a0 -> Report_warning_as_error a0))
381381+ };
382382+ BoxedTag
383383+ {
384384+ Rpc.Types.tname = "Report_alert";
385385+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic String));
386386+ Rpc.Types.tversion = None;
387387+ Rpc.Types.tdescription = [];
388388+ Rpc.Types.tpreview =
389389+ ((function | Report_alert a0 -> Some a0 | _ -> None));
390390+ Rpc.Types.treview = ((function | a0 -> Report_alert a0))
391391+ };
392392+ BoxedTag
393393+ {
394394+ Rpc.Types.tname = "Report_alert_as_error";
395395+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic String));
396396+ Rpc.Types.tversion = None;
397397+ Rpc.Types.tdescription = [];
398398+ Rpc.Types.tpreview =
399399+ ((function
400400+ | Report_alert_as_error a0 -> Some a0
401401+ | _ -> None));
402402+ Rpc.Types.treview =
403403+ ((function | a0 -> Report_alert_as_error a0))
404404+ }];
405405+ Rpc.Types.vdefault = None;
406406+ Rpc.Types.vversion = None;
407407+ Rpc.Types.vconstructor =
408408+ (fun s' t ->
409409+ let s = String.lowercase_ascii s' in
410410+ match s with
411411+ | "report_error" ->
412412+ Rresult.R.bind (t.tget Unit)
413413+ (function | () -> Rresult.R.ok Report_error)
414414+ | "report_warning" ->
415415+ Rresult.R.bind
416416+ (t.tget (let open Rpc.Types in Basic String))
417417+ (function | a0 -> Rresult.R.ok (Report_warning a0))
418418+ | "report_warning_as_error" ->
419419+ Rresult.R.bind
420420+ (t.tget (let open Rpc.Types in Basic String))
421421+ (function
422422+ | a0 -> Rresult.R.ok (Report_warning_as_error a0))
423423+ | "report_alert" ->
424424+ Rresult.R.bind
425425+ (t.tget (let open Rpc.Types in Basic String))
426426+ (function | a0 -> Rresult.R.ok (Report_alert a0))
427427+ | "report_alert_as_error" ->
428428+ Rresult.R.bind
429429+ (t.tget (let open Rpc.Types in Basic String))
430430+ (function
431431+ | a0 -> Rresult.R.ok (Report_alert_as_error a0))
432432+ | _ ->
433433+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
434434+ } : location_report_kind Rpc.Types.variant)
435435+ and location_report_kind =
436436+ {
437437+ Rpc.Types.name = "location_report_kind";
438438+ Rpc.Types.description = [];
439439+ Rpc.Types.ty = typ_of_location_report_kind
440440+ }
441441+ let _ = typ_of_location_report_kind
442442+ and _ = location_report_kind
443443+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
444444+type source = string[@@deriving rpcty]
445445+include
446446+ struct
447447+ let _ = fun (_ : source) -> ()
448448+ let rec typ_of_source = let open Rpc.Types in Basic String
449449+ and source =
450450+ {
451451+ Rpc.Types.name = "source";
452452+ Rpc.Types.description = [];
453453+ Rpc.Types.ty = typ_of_source
454454+ }
455455+ let _ = typ_of_source
456456+ and _ = source
457457+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
458458+[@@@ocaml.text
459459+ " CMIs are provided either statically or as URLs to be downloaded on demand "]
460460+[@@@ocaml.text
461461+ " Dynamic cmis are loaded from beneath the given url. In addition the\n top-level modules are specified, and prefixes for other modules. For\n example, for the OCaml standard library, a user might pass:\n\n {[\n { dcs_url=\"/static/stdlib\";\n dcs_toplevel_modules=[\"Stdlib\"];\n dcs_file_prefixes=[\"stdlib__\"]; }\n ]}\n\n In which case, merlin will expect to be able to download a valid file\n from the url [\"/static/stdlib/stdlib.cmi\"] corresponding to the\n specified toplevel module, and it will also attempt to download any\n module with the prefix [\"Stdlib__\"] from the same base url, so for\n example if an attempt is made to look up the module [\"Stdlib__Foo\"]\n then merlin-js will attempt to download a file from the url\n [\"/static/stdlib/stdlib__Foo.cmi\"].\n "]
462462+type dynamic_cmis =
463463+ {
464464+ dcs_url: string ;
465465+ dcs_toplevel_modules: string list ;
466466+ dcs_file_prefixes: string list }
467467+and static_cmi = {
468468+ sc_name: string ;
469469+ sc_content: string }
470470+and cmis = {
471471+ static_cmis: static_cmi list ;
472472+ dynamic_cmis: dynamic_cmis list }[@@deriving rpcty]
473473+include
474474+ struct
475475+ let _ = fun (_ : dynamic_cmis) -> ()
476476+ let _ = fun (_ : static_cmi) -> ()
477477+ let _ = fun (_ : cmis) -> ()
478478+ let rec dynamic_cmis_dcs_url : (_, dynamic_cmis) Rpc.Types.field =
479479+ {
480480+ Rpc.Types.fname = "dcs_url";
481481+ Rpc.Types.field = (let open Rpc.Types in Basic String);
482482+ Rpc.Types.fdefault = None;
483483+ Rpc.Types.fdescription = [];
484484+ Rpc.Types.fversion = None;
485485+ Rpc.Types.fget = (fun _r -> _r.dcs_url);
486486+ Rpc.Types.fset = (fun v _s -> { _s with dcs_url = v })
487487+ }
488488+ and dynamic_cmis_dcs_toplevel_modules : (_, dynamic_cmis) Rpc.Types.field
489489+ =
490490+ {
491491+ Rpc.Types.fname = "dcs_toplevel_modules";
492492+ Rpc.Types.field =
493493+ (Rpc.Types.List (let open Rpc.Types in Basic String));
494494+ Rpc.Types.fdefault = None;
495495+ Rpc.Types.fdescription = [];
496496+ Rpc.Types.fversion = None;
497497+ Rpc.Types.fget = (fun _r -> _r.dcs_toplevel_modules);
498498+ Rpc.Types.fset = (fun v _s -> { _s with dcs_toplevel_modules = v })
499499+ }
500500+ and dynamic_cmis_dcs_file_prefixes : (_, dynamic_cmis) Rpc.Types.field =
501501+ {
502502+ Rpc.Types.fname = "dcs_file_prefixes";
503503+ Rpc.Types.field =
504504+ (Rpc.Types.List (let open Rpc.Types in Basic String));
505505+ Rpc.Types.fdefault = None;
506506+ Rpc.Types.fdescription = [];
507507+ Rpc.Types.fversion = None;
508508+ Rpc.Types.fget = (fun _r -> _r.dcs_file_prefixes);
509509+ Rpc.Types.fset = (fun v _s -> { _s with dcs_file_prefixes = v })
510510+ }
511511+ and typ_of_dynamic_cmis =
512512+ Rpc.Types.Struct
513513+ ({
514514+ Rpc.Types.fields =
515515+ [Rpc.Types.BoxedField dynamic_cmis_dcs_url;
516516+ Rpc.Types.BoxedField dynamic_cmis_dcs_toplevel_modules;
517517+ Rpc.Types.BoxedField dynamic_cmis_dcs_file_prefixes];
518518+ Rpc.Types.sname = "dynamic_cmis";
519519+ Rpc.Types.version = None;
520520+ Rpc.Types.constructor =
521521+ (fun getter ->
522522+ let open Rresult.R in
523523+ (getter.Rpc.Types.field_get "dcs_file_prefixes"
524524+ (Rpc.Types.List (let open Rpc.Types in Basic String)))
525525+ >>=
526526+ (fun dynamic_cmis_dcs_file_prefixes ->
527527+ (getter.Rpc.Types.field_get "dcs_toplevel_modules"
528528+ (Rpc.Types.List
529529+ (let open Rpc.Types in Basic String)))
530530+ >>=
531531+ (fun dynamic_cmis_dcs_toplevel_modules ->
532532+ (getter.Rpc.Types.field_get "dcs_url"
533533+ (let open Rpc.Types in Basic String))
534534+ >>=
535535+ (fun dynamic_cmis_dcs_url ->
536536+ return
537537+ {
538538+ dcs_url = dynamic_cmis_dcs_url;
539539+ dcs_toplevel_modules =
540540+ dynamic_cmis_dcs_toplevel_modules;
541541+ dcs_file_prefixes =
542542+ dynamic_cmis_dcs_file_prefixes
543543+ }))))
544544+ } : dynamic_cmis Rpc.Types.structure)
545545+ and dynamic_cmis =
546546+ {
547547+ Rpc.Types.name = "dynamic_cmis";
548548+ Rpc.Types.description = [];
549549+ Rpc.Types.ty = typ_of_dynamic_cmis
550550+ }
551551+ and static_cmi_sc_name : (_, static_cmi) Rpc.Types.field =
552552+ {
553553+ Rpc.Types.fname = "sc_name";
554554+ Rpc.Types.field = (let open Rpc.Types in Basic String);
555555+ Rpc.Types.fdefault = None;
556556+ Rpc.Types.fdescription = [];
557557+ Rpc.Types.fversion = None;
558558+ Rpc.Types.fget = (fun _r -> _r.sc_name);
559559+ Rpc.Types.fset = (fun v _s -> { _s with sc_name = v })
560560+ }
561561+ and static_cmi_sc_content : (_, static_cmi) Rpc.Types.field =
562562+ {
563563+ Rpc.Types.fname = "sc_content";
564564+ Rpc.Types.field = (let open Rpc.Types in Basic String);
565565+ Rpc.Types.fdefault = None;
566566+ Rpc.Types.fdescription = [];
567567+ Rpc.Types.fversion = None;
568568+ Rpc.Types.fget = (fun _r -> _r.sc_content);
569569+ Rpc.Types.fset = (fun v _s -> { _s with sc_content = v })
570570+ }
571571+ and typ_of_static_cmi =
572572+ Rpc.Types.Struct
573573+ ({
574574+ Rpc.Types.fields =
575575+ [Rpc.Types.BoxedField static_cmi_sc_name;
576576+ Rpc.Types.BoxedField static_cmi_sc_content];
577577+ Rpc.Types.sname = "static_cmi";
578578+ Rpc.Types.version = None;
579579+ Rpc.Types.constructor =
580580+ (fun getter ->
581581+ let open Rresult.R in
582582+ (getter.Rpc.Types.field_get "sc_content"
583583+ (let open Rpc.Types in Basic String))
584584+ >>=
585585+ (fun static_cmi_sc_content ->
586586+ (getter.Rpc.Types.field_get "sc_name"
587587+ (let open Rpc.Types in Basic String))
588588+ >>=
589589+ (fun static_cmi_sc_name ->
590590+ return
591591+ {
592592+ sc_name = static_cmi_sc_name;
593593+ sc_content = static_cmi_sc_content
594594+ })))
595595+ } : static_cmi Rpc.Types.structure)
596596+ and static_cmi =
597597+ {
598598+ Rpc.Types.name = "static_cmi";
599599+ Rpc.Types.description = [];
600600+ Rpc.Types.ty = typ_of_static_cmi
601601+ }
602602+ and cmis_static_cmis : (_, cmis) Rpc.Types.field =
603603+ {
604604+ Rpc.Types.fname = "static_cmis";
605605+ Rpc.Types.field = (Rpc.Types.List typ_of_static_cmi);
606606+ Rpc.Types.fdefault = None;
607607+ Rpc.Types.fdescription = [];
608608+ Rpc.Types.fversion = None;
609609+ Rpc.Types.fget = (fun _r -> _r.static_cmis);
610610+ Rpc.Types.fset = (fun v _s -> { _s with static_cmis = v })
611611+ }
612612+ and cmis_dynamic_cmis : (_, cmis) Rpc.Types.field =
613613+ {
614614+ Rpc.Types.fname = "dynamic_cmis";
615615+ Rpc.Types.field = (Rpc.Types.List typ_of_dynamic_cmis);
616616+ Rpc.Types.fdefault = None;
617617+ Rpc.Types.fdescription = [];
618618+ Rpc.Types.fversion = None;
619619+ Rpc.Types.fget = (fun _r -> _r.dynamic_cmis);
620620+ Rpc.Types.fset = (fun v _s -> { _s with dynamic_cmis = v })
621621+ }
622622+ and typ_of_cmis =
623623+ Rpc.Types.Struct
624624+ ({
625625+ Rpc.Types.fields =
626626+ [Rpc.Types.BoxedField cmis_static_cmis;
627627+ Rpc.Types.BoxedField cmis_dynamic_cmis];
628628+ Rpc.Types.sname = "cmis";
629629+ Rpc.Types.version = None;
630630+ Rpc.Types.constructor =
631631+ (fun getter ->
632632+ let open Rresult.R in
633633+ (getter.Rpc.Types.field_get "dynamic_cmis"
634634+ (Rpc.Types.List typ_of_dynamic_cmis))
635635+ >>=
636636+ (fun cmis_dynamic_cmis ->
637637+ (getter.Rpc.Types.field_get "static_cmis"
638638+ (Rpc.Types.List typ_of_static_cmi))
639639+ >>=
640640+ (fun cmis_static_cmis ->
641641+ return
642642+ {
643643+ static_cmis = cmis_static_cmis;
644644+ dynamic_cmis = cmis_dynamic_cmis
645645+ })))
646646+ } : cmis Rpc.Types.structure)
647647+ and cmis =
648648+ {
649649+ Rpc.Types.name = "cmis";
650650+ Rpc.Types.description = [];
651651+ Rpc.Types.ty = typ_of_cmis
652652+ }
653653+ let _ = dynamic_cmis_dcs_url
654654+ and _ = dynamic_cmis_dcs_toplevel_modules
655655+ and _ = dynamic_cmis_dcs_file_prefixes
656656+ and _ = typ_of_dynamic_cmis
657657+ and _ = dynamic_cmis
658658+ and _ = static_cmi_sc_name
659659+ and _ = static_cmi_sc_content
660660+ and _ = typ_of_static_cmi
661661+ and _ = static_cmi
662662+ and _ = cmis_static_cmis
663663+ and _ = cmis_dynamic_cmis
664664+ and _ = typ_of_cmis
665665+ and _ = cmis
666666+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
667667+type action =
668668+ | Complete_prefix of source * Msource.position
669669+ | Type_enclosing of source * Msource.position
670670+ | All_errors of source
671671+ | Add_cmis of cmis
672672+type error =
673673+ {
674674+ kind: location_report_kind ;
675675+ loc: location ;
676676+ main: string ;
677677+ sub: string list ;
678678+ source: location_error_source }[@@deriving rpcty]
679679+include
680680+ struct
681681+ let _ = fun (_ : error) -> ()
682682+ let rec error_kind : (_, error) Rpc.Types.field =
683683+ {
684684+ Rpc.Types.fname = "kind";
685685+ Rpc.Types.field = typ_of_location_report_kind;
686686+ Rpc.Types.fdefault = None;
687687+ Rpc.Types.fdescription = [];
688688+ Rpc.Types.fversion = None;
689689+ Rpc.Types.fget = (fun _r -> _r.kind);
690690+ Rpc.Types.fset = (fun v _s -> { _s with kind = v })
691691+ }
692692+ and error_loc : (_, error) Rpc.Types.field =
693693+ {
694694+ Rpc.Types.fname = "loc";
695695+ Rpc.Types.field = typ_of_location;
696696+ Rpc.Types.fdefault = None;
697697+ Rpc.Types.fdescription = [];
698698+ Rpc.Types.fversion = None;
699699+ Rpc.Types.fget = (fun _r -> _r.loc);
700700+ Rpc.Types.fset = (fun v _s -> { _s with loc = v })
701701+ }
702702+ and error_main : (_, error) Rpc.Types.field =
703703+ {
704704+ Rpc.Types.fname = "main";
705705+ Rpc.Types.field = (let open Rpc.Types in Basic String);
706706+ Rpc.Types.fdefault = None;
707707+ Rpc.Types.fdescription = [];
708708+ Rpc.Types.fversion = None;
709709+ Rpc.Types.fget = (fun _r -> _r.main);
710710+ Rpc.Types.fset = (fun v _s -> { _s with main = v })
711711+ }
712712+ and error_sub : (_, error) Rpc.Types.field =
713713+ {
714714+ Rpc.Types.fname = "sub";
715715+ Rpc.Types.field =
716716+ (Rpc.Types.List (let open Rpc.Types in Basic String));
717717+ Rpc.Types.fdefault = None;
718718+ Rpc.Types.fdescription = [];
719719+ Rpc.Types.fversion = None;
720720+ Rpc.Types.fget = (fun _r -> _r.sub);
721721+ Rpc.Types.fset = (fun v _s -> { _s with sub = v })
722722+ }
723723+ and error_source : (_, error) Rpc.Types.field =
724724+ {
725725+ Rpc.Types.fname = "source";
726726+ Rpc.Types.field = typ_of_location_error_source;
727727+ Rpc.Types.fdefault = None;
728728+ Rpc.Types.fdescription = [];
729729+ Rpc.Types.fversion = None;
730730+ Rpc.Types.fget = (fun _r -> _r.source);
731731+ Rpc.Types.fset = (fun v _s -> { _s with source = v })
732732+ }
733733+ and typ_of_error =
734734+ Rpc.Types.Struct
735735+ ({
736736+ Rpc.Types.fields =
737737+ [Rpc.Types.BoxedField error_kind;
738738+ Rpc.Types.BoxedField error_loc;
739739+ Rpc.Types.BoxedField error_main;
740740+ Rpc.Types.BoxedField error_sub;
741741+ Rpc.Types.BoxedField error_source];
742742+ Rpc.Types.sname = "error";
743743+ Rpc.Types.version = None;
744744+ Rpc.Types.constructor =
745745+ (fun getter ->
746746+ let open Rresult.R in
747747+ (getter.Rpc.Types.field_get "source"
748748+ typ_of_location_error_source)
749749+ >>=
750750+ (fun error_source ->
751751+ (getter.Rpc.Types.field_get "sub"
752752+ (Rpc.Types.List
753753+ (let open Rpc.Types in Basic String)))
754754+ >>=
755755+ (fun error_sub ->
756756+ (getter.Rpc.Types.field_get "main"
757757+ (let open Rpc.Types in Basic String))
758758+ >>=
759759+ (fun error_main ->
760760+ (getter.Rpc.Types.field_get "loc"
761761+ typ_of_location)
762762+ >>=
763763+ (fun error_loc ->
764764+ (getter.Rpc.Types.field_get "kind"
765765+ typ_of_location_report_kind)
766766+ >>=
767767+ (fun error_kind ->
768768+ return
769769+ {
770770+ kind = error_kind;
771771+ loc = error_loc;
772772+ main = error_main;
773773+ sub = error_sub;
774774+ source = error_source
775775+ }))))))
776776+ } : error Rpc.Types.structure)
777777+ and error =
778778+ {
779779+ Rpc.Types.name = "error";
780780+ Rpc.Types.description = [];
781781+ Rpc.Types.ty = typ_of_error
782782+ }
783783+ let _ = error_kind
784784+ and _ = error_loc
785785+ and _ = error_main
786786+ and _ = error_sub
787787+ and _ = error_source
788788+ and _ = typ_of_error
789789+ and _ = error
790790+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
791791+type error_list = error list[@@deriving rpcty]
792792+include
793793+ struct
794794+ let _ = fun (_ : error_list) -> ()
795795+ let rec typ_of_error_list = Rpc.Types.List typ_of_error
796796+ and error_list =
797797+ {
798798+ Rpc.Types.name = "error_list";
799799+ Rpc.Types.description = [];
800800+ Rpc.Types.ty = typ_of_error_list
801801+ }
802802+ let _ = typ_of_error_list
803803+ and _ = error_list
804804+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
805805+type kind_ty =
806806+ | Constructor
807807+ | Keyword
808808+ | Label
809809+ | MethodCall
810810+ | Modtype
811811+ | Module
812812+ | Type
813813+ | Value
814814+ | Variant [@@deriving rpcty]
815815+include
816816+ struct
817817+ let _ = fun (_ : kind_ty) -> ()
818818+ let rec typ_of_kind_ty =
819819+ Rpc.Types.Variant
820820+ ({
821821+ Rpc.Types.vname = "kind_ty";
822822+ Rpc.Types.variants =
823823+ [BoxedTag
824824+ {
825825+ Rpc.Types.tname = "Constructor";
826826+ Rpc.Types.tcontents = Unit;
827827+ Rpc.Types.tversion = None;
828828+ Rpc.Types.tdescription = [];
829829+ Rpc.Types.tpreview =
830830+ ((function | Constructor -> Some () | _ -> None));
831831+ Rpc.Types.treview = ((function | () -> Constructor))
832832+ };
833833+ BoxedTag
834834+ {
835835+ Rpc.Types.tname = "Keyword";
836836+ Rpc.Types.tcontents = Unit;
837837+ Rpc.Types.tversion = None;
838838+ Rpc.Types.tdescription = [];
839839+ Rpc.Types.tpreview =
840840+ ((function | Keyword -> Some () | _ -> None));
841841+ Rpc.Types.treview = ((function | () -> Keyword))
842842+ };
843843+ BoxedTag
844844+ {
845845+ Rpc.Types.tname = "Label";
846846+ Rpc.Types.tcontents = Unit;
847847+ Rpc.Types.tversion = None;
848848+ Rpc.Types.tdescription = [];
849849+ Rpc.Types.tpreview =
850850+ ((function | Label -> Some () | _ -> None));
851851+ Rpc.Types.treview = ((function | () -> Label))
852852+ };
853853+ BoxedTag
854854+ {
855855+ Rpc.Types.tname = "MethodCall";
856856+ Rpc.Types.tcontents = Unit;
857857+ Rpc.Types.tversion = None;
858858+ Rpc.Types.tdescription = [];
859859+ Rpc.Types.tpreview =
860860+ ((function | MethodCall -> Some () | _ -> None));
861861+ Rpc.Types.treview = ((function | () -> MethodCall))
862862+ };
863863+ BoxedTag
864864+ {
865865+ Rpc.Types.tname = "Modtype";
866866+ Rpc.Types.tcontents = Unit;
867867+ Rpc.Types.tversion = None;
868868+ Rpc.Types.tdescription = [];
869869+ Rpc.Types.tpreview =
870870+ ((function | Modtype -> Some () | _ -> None));
871871+ Rpc.Types.treview = ((function | () -> Modtype))
872872+ };
873873+ BoxedTag
874874+ {
875875+ Rpc.Types.tname = "Module";
876876+ Rpc.Types.tcontents = Unit;
877877+ Rpc.Types.tversion = None;
878878+ Rpc.Types.tdescription = [];
879879+ Rpc.Types.tpreview =
880880+ ((function | Module -> Some () | _ -> None));
881881+ Rpc.Types.treview = ((function | () -> Module))
882882+ };
883883+ BoxedTag
884884+ {
885885+ Rpc.Types.tname = "Type";
886886+ Rpc.Types.tcontents = Unit;
887887+ Rpc.Types.tversion = None;
888888+ Rpc.Types.tdescription = [];
889889+ Rpc.Types.tpreview =
890890+ ((function | Type -> Some () | _ -> None));
891891+ Rpc.Types.treview = ((function | () -> Type))
892892+ };
893893+ BoxedTag
894894+ {
895895+ Rpc.Types.tname = "Value";
896896+ Rpc.Types.tcontents = Unit;
897897+ Rpc.Types.tversion = None;
898898+ Rpc.Types.tdescription = [];
899899+ Rpc.Types.tpreview =
900900+ ((function | Value -> Some () | _ -> None));
901901+ Rpc.Types.treview = ((function | () -> Value))
902902+ };
903903+ BoxedTag
904904+ {
905905+ Rpc.Types.tname = "Variant";
906906+ Rpc.Types.tcontents = Unit;
907907+ Rpc.Types.tversion = None;
908908+ Rpc.Types.tdescription = [];
909909+ Rpc.Types.tpreview =
910910+ ((function | Variant -> Some () | _ -> None));
911911+ Rpc.Types.treview = ((function | () -> Variant))
912912+ }];
913913+ Rpc.Types.vdefault = None;
914914+ Rpc.Types.vversion = None;
915915+ Rpc.Types.vconstructor =
916916+ (fun s' t ->
917917+ let s = String.lowercase_ascii s' in
918918+ match s with
919919+ | "constructor" ->
920920+ Rresult.R.bind (t.tget Unit)
921921+ (function | () -> Rresult.R.ok Constructor)
922922+ | "keyword" ->
923923+ Rresult.R.bind (t.tget Unit)
924924+ (function | () -> Rresult.R.ok Keyword)
925925+ | "label" ->
926926+ Rresult.R.bind (t.tget Unit)
927927+ (function | () -> Rresult.R.ok Label)
928928+ | "methodcall" ->
929929+ Rresult.R.bind (t.tget Unit)
930930+ (function | () -> Rresult.R.ok MethodCall)
931931+ | "modtype" ->
932932+ Rresult.R.bind (t.tget Unit)
933933+ (function | () -> Rresult.R.ok Modtype)
934934+ | "module" ->
935935+ Rresult.R.bind (t.tget Unit)
936936+ (function | () -> Rresult.R.ok Module)
937937+ | "type" ->
938938+ Rresult.R.bind (t.tget Unit)
939939+ (function | () -> Rresult.R.ok Type)
940940+ | "value" ->
941941+ Rresult.R.bind (t.tget Unit)
942942+ (function | () -> Rresult.R.ok Value)
943943+ | "variant" ->
944944+ Rresult.R.bind (t.tget Unit)
945945+ (function | () -> Rresult.R.ok Variant)
946946+ | _ ->
947947+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
948948+ } : kind_ty Rpc.Types.variant)
949949+ and kind_ty =
950950+ {
951951+ Rpc.Types.name = "kind_ty";
952952+ Rpc.Types.description = [];
953953+ Rpc.Types.ty = typ_of_kind_ty
954954+ }
955955+ let _ = typ_of_kind_ty
956956+ and _ = kind_ty
957957+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
958958+type query_protocol_compl_entry =
959959+ {
960960+ name: string ;
961961+ kind: kind_ty ;
962962+ desc: string ;
963963+ info: string ;
964964+ deprecated: bool }[@@deriving rpcty]
965965+include
966966+ struct
967967+ let _ = fun (_ : query_protocol_compl_entry) -> ()
968968+ let rec query_protocol_compl_entry_name :
969969+ (_, query_protocol_compl_entry) Rpc.Types.field =
970970+ {
971971+ Rpc.Types.fname = "name";
972972+ Rpc.Types.field = (let open Rpc.Types in Basic String);
973973+ Rpc.Types.fdefault = None;
974974+ Rpc.Types.fdescription = [];
975975+ Rpc.Types.fversion = None;
976976+ Rpc.Types.fget = (fun _r -> _r.name);
977977+ Rpc.Types.fset = (fun v _s -> { _s with name = v })
978978+ }
979979+ and query_protocol_compl_entry_kind :
980980+ (_, query_protocol_compl_entry) Rpc.Types.field =
981981+ {
982982+ Rpc.Types.fname = "kind";
983983+ Rpc.Types.field = typ_of_kind_ty;
984984+ Rpc.Types.fdefault = None;
985985+ Rpc.Types.fdescription = [];
986986+ Rpc.Types.fversion = None;
987987+ Rpc.Types.fget = (fun _r -> _r.kind);
988988+ Rpc.Types.fset = (fun v _s -> { _s with kind = v })
989989+ }
990990+ and query_protocol_compl_entry_desc :
991991+ (_, query_protocol_compl_entry) Rpc.Types.field =
992992+ {
993993+ Rpc.Types.fname = "desc";
994994+ Rpc.Types.field = (let open Rpc.Types in Basic String);
995995+ Rpc.Types.fdefault = None;
996996+ Rpc.Types.fdescription = [];
997997+ Rpc.Types.fversion = None;
998998+ Rpc.Types.fget = (fun _r -> _r.desc);
999999+ Rpc.Types.fset = (fun v _s -> { _s with desc = v })
10001000+ }
10011001+ and query_protocol_compl_entry_info :
10021002+ (_, query_protocol_compl_entry) Rpc.Types.field =
10031003+ {
10041004+ Rpc.Types.fname = "info";
10051005+ Rpc.Types.field = (let open Rpc.Types in Basic String);
10061006+ Rpc.Types.fdefault = None;
10071007+ Rpc.Types.fdescription = [];
10081008+ Rpc.Types.fversion = None;
10091009+ Rpc.Types.fget = (fun _r -> _r.info);
10101010+ Rpc.Types.fset = (fun v _s -> { _s with info = v })
10111011+ }
10121012+ and query_protocol_compl_entry_deprecated :
10131013+ (_, query_protocol_compl_entry) Rpc.Types.field =
10141014+ {
10151015+ Rpc.Types.fname = "deprecated";
10161016+ Rpc.Types.field = (let open Rpc.Types in Basic Bool);
10171017+ Rpc.Types.fdefault = None;
10181018+ Rpc.Types.fdescription = [];
10191019+ Rpc.Types.fversion = None;
10201020+ Rpc.Types.fget = (fun _r -> _r.deprecated);
10211021+ Rpc.Types.fset = (fun v _s -> { _s with deprecated = v })
10221022+ }
10231023+ and typ_of_query_protocol_compl_entry =
10241024+ Rpc.Types.Struct
10251025+ ({
10261026+ Rpc.Types.fields =
10271027+ [Rpc.Types.BoxedField query_protocol_compl_entry_name;
10281028+ Rpc.Types.BoxedField query_protocol_compl_entry_kind;
10291029+ Rpc.Types.BoxedField query_protocol_compl_entry_desc;
10301030+ Rpc.Types.BoxedField query_protocol_compl_entry_info;
10311031+ Rpc.Types.BoxedField query_protocol_compl_entry_deprecated];
10321032+ Rpc.Types.sname = "query_protocol_compl_entry";
10331033+ Rpc.Types.version = None;
10341034+ Rpc.Types.constructor =
10351035+ (fun getter ->
10361036+ let open Rresult.R in
10371037+ (getter.Rpc.Types.field_get "deprecated"
10381038+ (let open Rpc.Types in Basic Bool))
10391039+ >>=
10401040+ (fun query_protocol_compl_entry_deprecated ->
10411041+ (getter.Rpc.Types.field_get "info"
10421042+ (let open Rpc.Types in Basic String))
10431043+ >>=
10441044+ (fun query_protocol_compl_entry_info ->
10451045+ (getter.Rpc.Types.field_get "desc"
10461046+ (let open Rpc.Types in Basic String))
10471047+ >>=
10481048+ (fun query_protocol_compl_entry_desc ->
10491049+ (getter.Rpc.Types.field_get "kind"
10501050+ typ_of_kind_ty)
10511051+ >>=
10521052+ (fun query_protocol_compl_entry_kind ->
10531053+ (getter.Rpc.Types.field_get "name"
10541054+ (let open Rpc.Types in Basic String))
10551055+ >>=
10561056+ (fun query_protocol_compl_entry_name
10571057+ ->
10581058+ return
10591059+ {
10601060+ name =
10611061+ query_protocol_compl_entry_name;
10621062+ kind =
10631063+ query_protocol_compl_entry_kind;
10641064+ desc =
10651065+ query_protocol_compl_entry_desc;
10661066+ info =
10671067+ query_protocol_compl_entry_info;
10681068+ deprecated =
10691069+ query_protocol_compl_entry_deprecated
10701070+ }))))))
10711071+ } : query_protocol_compl_entry Rpc.Types.structure)
10721072+ and query_protocol_compl_entry =
10731073+ {
10741074+ Rpc.Types.name = "query_protocol_compl_entry";
10751075+ Rpc.Types.description = [];
10761076+ Rpc.Types.ty = typ_of_query_protocol_compl_entry
10771077+ }
10781078+ let _ = query_protocol_compl_entry_name
10791079+ and _ = query_protocol_compl_entry_kind
10801080+ and _ = query_protocol_compl_entry_desc
10811081+ and _ = query_protocol_compl_entry_info
10821082+ and _ = query_protocol_compl_entry_deprecated
10831083+ and _ = typ_of_query_protocol_compl_entry
10841084+ and _ = query_protocol_compl_entry
10851085+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
10861086+type completions =
10871087+ {
10881088+ from: int ;
10891089+ to_: int ;
10901090+ entries: query_protocol_compl_entry list }[@@deriving rpcty]
10911091+include
10921092+ struct
10931093+ let _ = fun (_ : completions) -> ()
10941094+ let rec completions_from : (_, completions) Rpc.Types.field =
10951095+ {
10961096+ Rpc.Types.fname = "from";
10971097+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
10981098+ Rpc.Types.fdefault = None;
10991099+ Rpc.Types.fdescription = [];
11001100+ Rpc.Types.fversion = None;
11011101+ Rpc.Types.fget = (fun _r -> _r.from);
11021102+ Rpc.Types.fset = (fun v _s -> { _s with from = v })
11031103+ }
11041104+ and completions_to_ : (_, completions) Rpc.Types.field =
11051105+ {
11061106+ Rpc.Types.fname = "to_";
11071107+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
11081108+ Rpc.Types.fdefault = None;
11091109+ Rpc.Types.fdescription = [];
11101110+ Rpc.Types.fversion = None;
11111111+ Rpc.Types.fget = (fun _r -> _r.to_);
11121112+ Rpc.Types.fset = (fun v _s -> { _s with to_ = v })
11131113+ }
11141114+ and completions_entries : (_, completions) Rpc.Types.field =
11151115+ {
11161116+ Rpc.Types.fname = "entries";
11171117+ Rpc.Types.field = (Rpc.Types.List typ_of_query_protocol_compl_entry);
11181118+ Rpc.Types.fdefault = None;
11191119+ Rpc.Types.fdescription = [];
11201120+ Rpc.Types.fversion = None;
11211121+ Rpc.Types.fget = (fun _r -> _r.entries);
11221122+ Rpc.Types.fset = (fun v _s -> { _s with entries = v })
11231123+ }
11241124+ and typ_of_completions =
11251125+ Rpc.Types.Struct
11261126+ ({
11271127+ Rpc.Types.fields =
11281128+ [Rpc.Types.BoxedField completions_from;
11291129+ Rpc.Types.BoxedField completions_to_;
11301130+ Rpc.Types.BoxedField completions_entries];
11311131+ Rpc.Types.sname = "completions";
11321132+ Rpc.Types.version = None;
11331133+ Rpc.Types.constructor =
11341134+ (fun getter ->
11351135+ let open Rresult.R in
11361136+ (getter.Rpc.Types.field_get "entries"
11371137+ (Rpc.Types.List typ_of_query_protocol_compl_entry))
11381138+ >>=
11391139+ (fun completions_entries ->
11401140+ (getter.Rpc.Types.field_get "to_"
11411141+ (let open Rpc.Types in Basic Int))
11421142+ >>=
11431143+ (fun completions_to_ ->
11441144+ (getter.Rpc.Types.field_get "from"
11451145+ (let open Rpc.Types in Basic Int))
11461146+ >>=
11471147+ (fun completions_from ->
11481148+ return
11491149+ {
11501150+ from = completions_from;
11511151+ to_ = completions_to_;
11521152+ entries = completions_entries
11531153+ }))))
11541154+ } : completions Rpc.Types.structure)
11551155+ and completions =
11561156+ {
11571157+ Rpc.Types.name = "completions";
11581158+ Rpc.Types.description = [];
11591159+ Rpc.Types.ty = typ_of_completions
11601160+ }
11611161+ let _ = completions_from
11621162+ and _ = completions_to_
11631163+ and _ = completions_entries
11641164+ and _ = typ_of_completions
11651165+ and _ = completions
11661166+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
11671167+type msource_position =
11681168+ | Start
11691169+ | Offset of int
11701170+ | Logical of int * int
11711171+ | End [@@deriving rpcty]
11721172+include
11731173+ struct
11741174+ let _ = fun (_ : msource_position) -> ()
11751175+ let rec typ_of_msource_position =
11761176+ Rpc.Types.Variant
11771177+ ({
11781178+ Rpc.Types.vname = "msource_position";
11791179+ Rpc.Types.variants =
11801180+ [BoxedTag
11811181+ {
11821182+ Rpc.Types.tname = "Start";
11831183+ Rpc.Types.tcontents = Unit;
11841184+ Rpc.Types.tversion = None;
11851185+ Rpc.Types.tdescription = [];
11861186+ Rpc.Types.tpreview =
11871187+ ((function | Start -> Some () | _ -> None));
11881188+ Rpc.Types.treview = ((function | () -> Start))
11891189+ };
11901190+ BoxedTag
11911191+ {
11921192+ Rpc.Types.tname = "Offset";
11931193+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic Int));
11941194+ Rpc.Types.tversion = None;
11951195+ Rpc.Types.tdescription = [];
11961196+ Rpc.Types.tpreview =
11971197+ ((function | Offset a0 -> Some a0 | _ -> None));
11981198+ Rpc.Types.treview = ((function | a0 -> Offset a0))
11991199+ };
12001200+ BoxedTag
12011201+ {
12021202+ Rpc.Types.tname = "Logical";
12031203+ Rpc.Types.tcontents =
12041204+ (Tuple
12051205+ (((let open Rpc.Types in Basic Int)),
12061206+ ((let open Rpc.Types in Basic Int))));
12071207+ Rpc.Types.tversion = None;
12081208+ Rpc.Types.tdescription = [];
12091209+ Rpc.Types.tpreview =
12101210+ ((function | Logical (a0, a1) -> Some (a0, a1) | _ -> None));
12111211+ Rpc.Types.treview =
12121212+ ((function | (a0, a1) -> Logical (a0, a1)))
12131213+ };
12141214+ BoxedTag
12151215+ {
12161216+ Rpc.Types.tname = "End";
12171217+ Rpc.Types.tcontents = Unit;
12181218+ Rpc.Types.tversion = None;
12191219+ Rpc.Types.tdescription = [];
12201220+ Rpc.Types.tpreview =
12211221+ ((function | End -> Some () | _ -> None));
12221222+ Rpc.Types.treview = ((function | () -> End))
12231223+ }];
12241224+ Rpc.Types.vdefault = None;
12251225+ Rpc.Types.vversion = None;
12261226+ Rpc.Types.vconstructor =
12271227+ (fun s' t ->
12281228+ let s = String.lowercase_ascii s' in
12291229+ match s with
12301230+ | "start" ->
12311231+ Rresult.R.bind (t.tget Unit)
12321232+ (function | () -> Rresult.R.ok Start)
12331233+ | "offset" ->
12341234+ Rresult.R.bind (t.tget (let open Rpc.Types in Basic Int))
12351235+ (function | a0 -> Rresult.R.ok (Offset a0))
12361236+ | "logical" ->
12371237+ Rresult.R.bind
12381238+ (t.tget
12391239+ (Tuple
12401240+ ((let open Rpc.Types in Basic Int),
12411241+ (let open Rpc.Types in Basic Int))))
12421242+ (function | (a0, a1) -> Rresult.R.ok (Logical (a0, a1)))
12431243+ | "end" ->
12441244+ Rresult.R.bind (t.tget Unit)
12451245+ (function | () -> Rresult.R.ok End)
12461246+ | _ ->
12471247+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
12481248+ } : msource_position Rpc.Types.variant)
12491249+ and msource_position =
12501250+ {
12511251+ Rpc.Types.name = "msource_position";
12521252+ Rpc.Types.description = [];
12531253+ Rpc.Types.ty = typ_of_msource_position
12541254+ }
12551255+ let _ = typ_of_msource_position
12561256+ and _ = msource_position
12571257+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
12581258+type is_tail_position =
12591259+ | No
12601260+ | Tail_position
12611261+ | Tail_call [@@deriving rpcty]
12621262+include
12631263+ struct
12641264+ let _ = fun (_ : is_tail_position) -> ()
12651265+ let rec typ_of_is_tail_position =
12661266+ Rpc.Types.Variant
12671267+ ({
12681268+ Rpc.Types.vname = "is_tail_position";
12691269+ Rpc.Types.variants =
12701270+ [BoxedTag
12711271+ {
12721272+ Rpc.Types.tname = "No";
12731273+ Rpc.Types.tcontents = Unit;
12741274+ Rpc.Types.tversion = None;
12751275+ Rpc.Types.tdescription = [];
12761276+ Rpc.Types.tpreview =
12771277+ ((function | No -> Some () | _ -> None));
12781278+ Rpc.Types.treview = ((function | () -> No))
12791279+ };
12801280+ BoxedTag
12811281+ {
12821282+ Rpc.Types.tname = "Tail_position";
12831283+ Rpc.Types.tcontents = Unit;
12841284+ Rpc.Types.tversion = None;
12851285+ Rpc.Types.tdescription = [];
12861286+ Rpc.Types.tpreview =
12871287+ ((function | Tail_position -> Some () | _ -> None));
12881288+ Rpc.Types.treview = ((function | () -> Tail_position))
12891289+ };
12901290+ BoxedTag
12911291+ {
12921292+ Rpc.Types.tname = "Tail_call";
12931293+ Rpc.Types.tcontents = Unit;
12941294+ Rpc.Types.tversion = None;
12951295+ Rpc.Types.tdescription = [];
12961296+ Rpc.Types.tpreview =
12971297+ ((function | Tail_call -> Some () | _ -> None));
12981298+ Rpc.Types.treview = ((function | () -> Tail_call))
12991299+ }];
13001300+ Rpc.Types.vdefault = None;
13011301+ Rpc.Types.vversion = None;
13021302+ Rpc.Types.vconstructor =
13031303+ (fun s' t ->
13041304+ let s = String.lowercase_ascii s' in
13051305+ match s with
13061306+ | "no" ->
13071307+ Rresult.R.bind (t.tget Unit)
13081308+ (function | () -> Rresult.R.ok No)
13091309+ | "tail_position" ->
13101310+ Rresult.R.bind (t.tget Unit)
13111311+ (function | () -> Rresult.R.ok Tail_position)
13121312+ | "tail_call" ->
13131313+ Rresult.R.bind (t.tget Unit)
13141314+ (function | () -> Rresult.R.ok Tail_call)
13151315+ | _ ->
13161316+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
13171317+ } : is_tail_position Rpc.Types.variant)
13181318+ and is_tail_position =
13191319+ {
13201320+ Rpc.Types.name = "is_tail_position";
13211321+ Rpc.Types.description = [];
13221322+ Rpc.Types.ty = typ_of_is_tail_position
13231323+ }
13241324+ let _ = typ_of_is_tail_position
13251325+ and _ = is_tail_position
13261326+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
13271327+type index_or_string =
13281328+ | Index of int
13291329+ | String of string [@@deriving rpcty]
13301330+include
13311331+ struct
13321332+ let _ = fun (_ : index_or_string) -> ()
13331333+ let rec typ_of_index_or_string =
13341334+ Rpc.Types.Variant
13351335+ ({
13361336+ Rpc.Types.vname = "index_or_string";
13371337+ Rpc.Types.variants =
13381338+ [BoxedTag
13391339+ {
13401340+ Rpc.Types.tname = "Index";
13411341+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic Int));
13421342+ Rpc.Types.tversion = None;
13431343+ Rpc.Types.tdescription = [];
13441344+ Rpc.Types.tpreview =
13451345+ ((function | Index a0 -> Some a0 | _ -> None));
13461346+ Rpc.Types.treview = ((function | a0 -> Index a0))
13471347+ };
13481348+ BoxedTag
13491349+ {
13501350+ Rpc.Types.tname = "String";
13511351+ Rpc.Types.tcontents = ((let open Rpc.Types in Basic String));
13521352+ Rpc.Types.tversion = None;
13531353+ Rpc.Types.tdescription = [];
13541354+ Rpc.Types.tpreview =
13551355+ ((function | String a0 -> Some a0 | _ -> None));
13561356+ Rpc.Types.treview = ((function | a0 -> String a0))
13571357+ }];
13581358+ Rpc.Types.vdefault = None;
13591359+ Rpc.Types.vversion = None;
13601360+ Rpc.Types.vconstructor =
13611361+ (fun s' t ->
13621362+ let s = String.lowercase_ascii s' in
13631363+ match s with
13641364+ | "index" ->
13651365+ Rresult.R.bind (t.tget (let open Rpc.Types in Basic Int))
13661366+ (function | a0 -> Rresult.R.ok (Index a0))
13671367+ | "string" ->
13681368+ Rresult.R.bind
13691369+ (t.tget (let open Rpc.Types in Basic String))
13701370+ (function | a0 -> Rresult.R.ok (String a0))
13711371+ | _ ->
13721372+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
13731373+ } : index_or_string Rpc.Types.variant)
13741374+ and index_or_string =
13751375+ {
13761376+ Rpc.Types.name = "index_or_string";
13771377+ Rpc.Types.description = [];
13781378+ Rpc.Types.ty = typ_of_index_or_string
13791379+ }
13801380+ let _ = typ_of_index_or_string
13811381+ and _ = index_or_string
13821382+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
13831383+type typed_enclosings = (location * index_or_string * is_tail_position)
13841384+[@@deriving rpcty]
13851385+include
13861386+ struct
13871387+ let _ = fun (_ : typed_enclosings) -> ()
13881388+ let rec typ_of_typed_enclosings =
13891389+ Rpc.Types.Tuple3
13901390+ (typ_of_location, typ_of_index_or_string, typ_of_is_tail_position)
13911391+ and typed_enclosings =
13921392+ {
13931393+ Rpc.Types.name = "typed_enclosings";
13941394+ Rpc.Types.description = [];
13951395+ Rpc.Types.ty = typ_of_typed_enclosings
13961396+ }
13971397+ let _ = typ_of_typed_enclosings
13981398+ and _ = typed_enclosings
13991399+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
14001400+type typed_enclosings_list = typed_enclosings list[@@deriving rpcty]
14011401+include
14021402+ struct
14031403+ let _ = fun (_ : typed_enclosings_list) -> ()
14041404+ let rec typ_of_typed_enclosings_list =
14051405+ Rpc.Types.List typ_of_typed_enclosings
14061406+ and typed_enclosings_list =
14071407+ {
14081408+ Rpc.Types.name = "typed_enclosings_list";
14091409+ Rpc.Types.description = [];
14101410+ Rpc.Types.ty = typ_of_typed_enclosings_list
14111411+ }
14121412+ let _ = typ_of_typed_enclosings_list
14131413+ and _ = typed_enclosings_list
14141414+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
14151415+let report_source_to_string =
14161416+ function
14171417+ | Location.Lexer -> "lexer"
14181418+ | Location.Parser -> "parser"
14191419+ | Location.Typer -> "typer"
14201420+ | Location.Warning -> "warning"
14211421+ | Location.Unknown -> "unknown"
14221422+ | Location.Env -> "env"
14231423+ | Location.Config -> "config"
14241424+type highlight = {
14251425+ line1: int ;
14261426+ line2: int ;
14271427+ col1: int ;
14281428+ col2: int }[@@deriving rpcty][@@ocaml.doc " An area to be highlighted "]
14291429+include
14301430+ struct
14311431+ let _ = fun (_ : highlight) -> ()
14321432+ let rec highlight_line1 : (_, highlight) Rpc.Types.field =
14331433+ {
14341434+ Rpc.Types.fname = "line1";
14351435+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
14361436+ Rpc.Types.fdefault = None;
14371437+ Rpc.Types.fdescription = [];
14381438+ Rpc.Types.fversion = None;
14391439+ Rpc.Types.fget = (fun _r -> _r.line1);
14401440+ Rpc.Types.fset = (fun v _s -> { _s with line1 = v })
14411441+ }
14421442+ and highlight_line2 : (_, highlight) Rpc.Types.field =
14431443+ {
14441444+ Rpc.Types.fname = "line2";
14451445+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
14461446+ Rpc.Types.fdefault = None;
14471447+ Rpc.Types.fdescription = [];
14481448+ Rpc.Types.fversion = None;
14491449+ Rpc.Types.fget = (fun _r -> _r.line2);
14501450+ Rpc.Types.fset = (fun v _s -> { _s with line2 = v })
14511451+ }
14521452+ and highlight_col1 : (_, highlight) Rpc.Types.field =
14531453+ {
14541454+ Rpc.Types.fname = "col1";
14551455+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
14561456+ Rpc.Types.fdefault = None;
14571457+ Rpc.Types.fdescription = [];
14581458+ Rpc.Types.fversion = None;
14591459+ Rpc.Types.fget = (fun _r -> _r.col1);
14601460+ Rpc.Types.fset = (fun v _s -> { _s with col1 = v })
14611461+ }
14621462+ and highlight_col2 : (_, highlight) Rpc.Types.field =
14631463+ {
14641464+ Rpc.Types.fname = "col2";
14651465+ Rpc.Types.field = (let open Rpc.Types in Basic Int);
14661466+ Rpc.Types.fdefault = None;
14671467+ Rpc.Types.fdescription = [];
14681468+ Rpc.Types.fversion = None;
14691469+ Rpc.Types.fget = (fun _r -> _r.col2);
14701470+ Rpc.Types.fset = (fun v _s -> { _s with col2 = v })
14711471+ }
14721472+ and typ_of_highlight =
14731473+ Rpc.Types.Struct
14741474+ ({
14751475+ Rpc.Types.fields =
14761476+ [Rpc.Types.BoxedField highlight_line1;
14771477+ Rpc.Types.BoxedField highlight_line2;
14781478+ Rpc.Types.BoxedField highlight_col1;
14791479+ Rpc.Types.BoxedField highlight_col2];
14801480+ Rpc.Types.sname = "highlight";
14811481+ Rpc.Types.version = None;
14821482+ Rpc.Types.constructor =
14831483+ (fun getter ->
14841484+ let open Rresult.R in
14851485+ (getter.Rpc.Types.field_get "col2"
14861486+ (let open Rpc.Types in Basic Int))
14871487+ >>=
14881488+ (fun highlight_col2 ->
14891489+ (getter.Rpc.Types.field_get "col1"
14901490+ (let open Rpc.Types in Basic Int))
14911491+ >>=
14921492+ (fun highlight_col1 ->
14931493+ (getter.Rpc.Types.field_get "line2"
14941494+ (let open Rpc.Types in Basic Int))
14951495+ >>=
14961496+ (fun highlight_line2 ->
14971497+ (getter.Rpc.Types.field_get "line1"
14981498+ (let open Rpc.Types in Basic Int))
14991499+ >>=
15001500+ (fun highlight_line1 ->
15011501+ return
15021502+ {
15031503+ line1 = highlight_line1;
15041504+ line2 = highlight_line2;
15051505+ col1 = highlight_col1;
15061506+ col2 = highlight_col2
15071507+ })))))
15081508+ } : highlight Rpc.Types.structure)
15091509+ and highlight =
15101510+ {
15111511+ Rpc.Types.name = "highlight";
15121512+ Rpc.Types.description = ["An area to be highlighted"];
15131513+ Rpc.Types.ty = typ_of_highlight
15141514+ }
15151515+ let _ = highlight_line1
15161516+ and _ = highlight_line2
15171517+ and _ = highlight_col1
15181518+ and _ = highlight_col2
15191519+ and _ = typ_of_highlight
15201520+ and _ = highlight
15211521+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
15221522+type encoding = Mime_printer.encoding =
15231523+ | Noencoding
15241524+ | Base64 [@@ocaml.doc " An area to be highlighted "][@@deriving rpcty]
15251525+include
15261526+ struct
15271527+ let _ = fun (_ : encoding) -> ()
15281528+ let rec typ_of_encoding =
15291529+ Rpc.Types.Variant
15301530+ ({
15311531+ Rpc.Types.vname = "encoding";
15321532+ Rpc.Types.variants =
15331533+ [BoxedTag
15341534+ {
15351535+ Rpc.Types.tname = "Noencoding";
15361536+ Rpc.Types.tcontents = Unit;
15371537+ Rpc.Types.tversion = None;
15381538+ Rpc.Types.tdescription = [];
15391539+ Rpc.Types.tpreview =
15401540+ ((function | Noencoding -> Some () | _ -> None));
15411541+ Rpc.Types.treview = ((function | () -> Noencoding))
15421542+ };
15431543+ BoxedTag
15441544+ {
15451545+ Rpc.Types.tname = "Base64";
15461546+ Rpc.Types.tcontents = Unit;
15471547+ Rpc.Types.tversion = None;
15481548+ Rpc.Types.tdescription = [];
15491549+ Rpc.Types.tpreview =
15501550+ ((function | Base64 -> Some () | _ -> None));
15511551+ Rpc.Types.treview = ((function | () -> Base64))
15521552+ }];
15531553+ Rpc.Types.vdefault = None;
15541554+ Rpc.Types.vversion = None;
15551555+ Rpc.Types.vconstructor =
15561556+ (fun s' t ->
15571557+ let s = String.lowercase_ascii s' in
15581558+ match s with
15591559+ | "noencoding" ->
15601560+ Rresult.R.bind (t.tget Unit)
15611561+ (function | () -> Rresult.R.ok Noencoding)
15621562+ | "base64" ->
15631563+ Rresult.R.bind (t.tget Unit)
15641564+ (function | () -> Rresult.R.ok Base64)
15651565+ | _ ->
15661566+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
15671567+ } : encoding Rpc.Types.variant)
15681568+ and encoding =
15691569+ {
15701570+ Rpc.Types.name = "encoding";
15711571+ Rpc.Types.description = ["An area to be highlighted"];
15721572+ Rpc.Types.ty = typ_of_encoding
15731573+ }
15741574+ let _ = typ_of_encoding
15751575+ and _ = encoding
15761576+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
15771577+type mime_val = Mime_printer.t =
15781578+ {
15791579+ mime_type: string ;
15801580+ encoding: encoding ;
15811581+ data: string }[@@deriving rpcty]
15821582+include
15831583+ struct
15841584+ let _ = fun (_ : mime_val) -> ()
15851585+ let rec mime_val_mime_type : (_, mime_val) Rpc.Types.field =
15861586+ {
15871587+ Rpc.Types.fname = "mime_type";
15881588+ Rpc.Types.field = (let open Rpc.Types in Basic String);
15891589+ Rpc.Types.fdefault = None;
15901590+ Rpc.Types.fdescription = [];
15911591+ Rpc.Types.fversion = None;
15921592+ Rpc.Types.fget = (fun _r -> _r.mime_type);
15931593+ Rpc.Types.fset = (fun v _s -> { _s with mime_type = v })
15941594+ }
15951595+ and mime_val_encoding : (_, mime_val) Rpc.Types.field =
15961596+ {
15971597+ Rpc.Types.fname = "encoding";
15981598+ Rpc.Types.field = typ_of_encoding;
15991599+ Rpc.Types.fdefault = None;
16001600+ Rpc.Types.fdescription = [];
16011601+ Rpc.Types.fversion = None;
16021602+ Rpc.Types.fget = (fun _r -> _r.encoding);
16031603+ Rpc.Types.fset = (fun v _s -> { _s with encoding = v })
16041604+ }
16051605+ and mime_val_data : (_, mime_val) Rpc.Types.field =
16061606+ {
16071607+ Rpc.Types.fname = "data";
16081608+ Rpc.Types.field = (let open Rpc.Types in Basic String);
16091609+ Rpc.Types.fdefault = None;
16101610+ Rpc.Types.fdescription = [];
16111611+ Rpc.Types.fversion = None;
16121612+ Rpc.Types.fget = (fun _r -> _r.data);
16131613+ Rpc.Types.fset = (fun v _s -> { _s with data = v })
16141614+ }
16151615+ and typ_of_mime_val =
16161616+ Rpc.Types.Struct
16171617+ ({
16181618+ Rpc.Types.fields =
16191619+ [Rpc.Types.BoxedField mime_val_mime_type;
16201620+ Rpc.Types.BoxedField mime_val_encoding;
16211621+ Rpc.Types.BoxedField mime_val_data];
16221622+ Rpc.Types.sname = "mime_val";
16231623+ Rpc.Types.version = None;
16241624+ Rpc.Types.constructor =
16251625+ (fun getter ->
16261626+ let open Rresult.R in
16271627+ (getter.Rpc.Types.field_get "data"
16281628+ (let open Rpc.Types in Basic String))
16291629+ >>=
16301630+ (fun mime_val_data ->
16311631+ (getter.Rpc.Types.field_get "encoding" typ_of_encoding)
16321632+ >>=
16331633+ (fun mime_val_encoding ->
16341634+ (getter.Rpc.Types.field_get "mime_type"
16351635+ (let open Rpc.Types in Basic String))
16361636+ >>=
16371637+ (fun mime_val_mime_type ->
16381638+ return
16391639+ {
16401640+ mime_type = mime_val_mime_type;
16411641+ encoding = mime_val_encoding;
16421642+ data = mime_val_data
16431643+ }))))
16441644+ } : mime_val Rpc.Types.structure)
16451645+ and mime_val =
16461646+ {
16471647+ Rpc.Types.name = "mime_val";
16481648+ Rpc.Types.description = [];
16491649+ Rpc.Types.ty = typ_of_mime_val
16501650+ }
16511651+ let _ = mime_val_mime_type
16521652+ and _ = mime_val_encoding
16531653+ and _ = mime_val_data
16541654+ and _ = typ_of_mime_val
16551655+ and _ = mime_val
16561656+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
16571657+type exec_result =
16581658+ {
16591659+ stdout: string option ;
16601660+ stderr: string option ;
16611661+ sharp_ppf: string option ;
16621662+ caml_ppf: string option ;
16631663+ highlight: highlight option ;
16641664+ mime_vals: mime_val list }[@@deriving rpcty][@@ocaml.doc
16651665+ " Represents the result of executing a toplevel phrase "]
16661666+include
16671667+ struct
16681668+ let _ = fun (_ : exec_result) -> ()
16691669+ let rec exec_result_stdout : (_, exec_result) Rpc.Types.field =
16701670+ {
16711671+ Rpc.Types.fname = "stdout";
16721672+ Rpc.Types.field =
16731673+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
16741674+ Rpc.Types.fdefault = None;
16751675+ Rpc.Types.fdescription = [];
16761676+ Rpc.Types.fversion = None;
16771677+ Rpc.Types.fget = (fun _r -> _r.stdout);
16781678+ Rpc.Types.fset = (fun v _s -> { _s with stdout = v })
16791679+ }
16801680+ and exec_result_stderr : (_, exec_result) Rpc.Types.field =
16811681+ {
16821682+ Rpc.Types.fname = "stderr";
16831683+ Rpc.Types.field =
16841684+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
16851685+ Rpc.Types.fdefault = None;
16861686+ Rpc.Types.fdescription = [];
16871687+ Rpc.Types.fversion = None;
16881688+ Rpc.Types.fget = (fun _r -> _r.stderr);
16891689+ Rpc.Types.fset = (fun v _s -> { _s with stderr = v })
16901690+ }
16911691+ and exec_result_sharp_ppf : (_, exec_result) Rpc.Types.field =
16921692+ {
16931693+ Rpc.Types.fname = "sharp_ppf";
16941694+ Rpc.Types.field =
16951695+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
16961696+ Rpc.Types.fdefault = None;
16971697+ Rpc.Types.fdescription = [];
16981698+ Rpc.Types.fversion = None;
16991699+ Rpc.Types.fget = (fun _r -> _r.sharp_ppf);
17001700+ Rpc.Types.fset = (fun v _s -> { _s with sharp_ppf = v })
17011701+ }
17021702+ and exec_result_caml_ppf : (_, exec_result) Rpc.Types.field =
17031703+ {
17041704+ Rpc.Types.fname = "caml_ppf";
17051705+ Rpc.Types.field =
17061706+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
17071707+ Rpc.Types.fdefault = None;
17081708+ Rpc.Types.fdescription = [];
17091709+ Rpc.Types.fversion = None;
17101710+ Rpc.Types.fget = (fun _r -> _r.caml_ppf);
17111711+ Rpc.Types.fset = (fun v _s -> { _s with caml_ppf = v })
17121712+ }
17131713+ and exec_result_highlight : (_, exec_result) Rpc.Types.field =
17141714+ {
17151715+ Rpc.Types.fname = "highlight";
17161716+ Rpc.Types.field = (Rpc.Types.Option typ_of_highlight);
17171717+ Rpc.Types.fdefault = None;
17181718+ Rpc.Types.fdescription = [];
17191719+ Rpc.Types.fversion = None;
17201720+ Rpc.Types.fget = (fun _r -> _r.highlight);
17211721+ Rpc.Types.fset = (fun v _s -> { _s with highlight = v })
17221722+ }
17231723+ and exec_result_mime_vals : (_, exec_result) Rpc.Types.field =
17241724+ {
17251725+ Rpc.Types.fname = "mime_vals";
17261726+ Rpc.Types.field = (Rpc.Types.List typ_of_mime_val);
17271727+ Rpc.Types.fdefault = None;
17281728+ Rpc.Types.fdescription = [];
17291729+ Rpc.Types.fversion = None;
17301730+ Rpc.Types.fget = (fun _r -> _r.mime_vals);
17311731+ Rpc.Types.fset = (fun v _s -> { _s with mime_vals = v })
17321732+ }
17331733+ and typ_of_exec_result =
17341734+ Rpc.Types.Struct
17351735+ ({
17361736+ Rpc.Types.fields =
17371737+ [Rpc.Types.BoxedField exec_result_stdout;
17381738+ Rpc.Types.BoxedField exec_result_stderr;
17391739+ Rpc.Types.BoxedField exec_result_sharp_ppf;
17401740+ Rpc.Types.BoxedField exec_result_caml_ppf;
17411741+ Rpc.Types.BoxedField exec_result_highlight;
17421742+ Rpc.Types.BoxedField exec_result_mime_vals];
17431743+ Rpc.Types.sname = "exec_result";
17441744+ Rpc.Types.version = None;
17451745+ Rpc.Types.constructor =
17461746+ (fun getter ->
17471747+ let open Rresult.R in
17481748+ (getter.Rpc.Types.field_get "mime_vals"
17491749+ (Rpc.Types.List typ_of_mime_val))
17501750+ >>=
17511751+ (fun exec_result_mime_vals ->
17521752+ (getter.Rpc.Types.field_get "highlight"
17531753+ (Rpc.Types.Option typ_of_highlight))
17541754+ >>=
17551755+ (fun exec_result_highlight ->
17561756+ (getter.Rpc.Types.field_get "caml_ppf"
17571757+ (Rpc.Types.Option
17581758+ (let open Rpc.Types in Basic String)))
17591759+ >>=
17601760+ (fun exec_result_caml_ppf ->
17611761+ (getter.Rpc.Types.field_get "sharp_ppf"
17621762+ (Rpc.Types.Option
17631763+ (let open Rpc.Types in Basic String)))
17641764+ >>=
17651765+ (fun exec_result_sharp_ppf ->
17661766+ (getter.Rpc.Types.field_get "stderr"
17671767+ (Rpc.Types.Option
17681768+ (let open Rpc.Types in
17691769+ Basic String)))
17701770+ >>=
17711771+ (fun exec_result_stderr ->
17721772+ (getter.Rpc.Types.field_get
17731773+ "stdout"
17741774+ (Rpc.Types.Option
17751775+ (let open Rpc.Types in
17761776+ Basic String)))
17771777+ >>=
17781778+ (fun exec_result_stdout ->
17791779+ return
17801780+ {
17811781+ stdout =
17821782+ exec_result_stdout;
17831783+ stderr =
17841784+ exec_result_stderr;
17851785+ sharp_ppf =
17861786+ exec_result_sharp_ppf;
17871787+ caml_ppf =
17881788+ exec_result_caml_ppf;
17891789+ highlight =
17901790+ exec_result_highlight;
17911791+ mime_vals =
17921792+ exec_result_mime_vals
17931793+ })))))))
17941794+ } : exec_result Rpc.Types.structure)
17951795+ and exec_result =
17961796+ {
17971797+ Rpc.Types.name = "exec_result";
17981798+ Rpc.Types.description =
17991799+ ["Represents the result of executing a toplevel phrase"];
18001800+ Rpc.Types.ty = typ_of_exec_result
18011801+ }
18021802+ let _ = exec_result_stdout
18031803+ and _ = exec_result_stderr
18041804+ and _ = exec_result_sharp_ppf
18051805+ and _ = exec_result_caml_ppf
18061806+ and _ = exec_result_highlight
18071807+ and _ = exec_result_mime_vals
18081808+ and _ = typ_of_exec_result
18091809+ and _ = exec_result
18101810+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
18111811+type script_parts = (int * int) list[@@deriving rpcty]
18121812+include
18131813+ struct
18141814+ let _ = fun (_ : script_parts) -> ()
18151815+ let rec typ_of_script_parts =
18161816+ Rpc.Types.List
18171817+ (Rpc.Types.Tuple
18181818+ ((let open Rpc.Types in Basic Int),
18191819+ (let open Rpc.Types in Basic Int)))
18201820+ and script_parts =
18211821+ {
18221822+ Rpc.Types.name = "script_parts";
18231823+ Rpc.Types.description = [];
18241824+ Rpc.Types.ty = typ_of_script_parts
18251825+ }
18261826+ let _ = typ_of_script_parts
18271827+ and _ = script_parts
18281828+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
18291829+type exec_toplevel_result =
18301830+ {
18311831+ script: string ;
18321832+ parts: script_parts ;
18331833+ mime_vals: mime_val list }[@@deriving rpcty][@@ocaml.doc
18341834+ " Represents the result of executing a toplevel script "]
18351835+include
18361836+ struct
18371837+ let _ = fun (_ : exec_toplevel_result) -> ()
18381838+ let rec exec_toplevel_result_script :
18391839+ (_, exec_toplevel_result) Rpc.Types.field =
18401840+ {
18411841+ Rpc.Types.fname = "script";
18421842+ Rpc.Types.field = (let open Rpc.Types in Basic String);
18431843+ Rpc.Types.fdefault = None;
18441844+ Rpc.Types.fdescription = [];
18451845+ Rpc.Types.fversion = None;
18461846+ Rpc.Types.fget = (fun _r -> _r.script);
18471847+ Rpc.Types.fset = (fun v _s -> { _s with script = v })
18481848+ }
18491849+ and exec_toplevel_result_parts :
18501850+ (_, exec_toplevel_result) Rpc.Types.field =
18511851+ {
18521852+ Rpc.Types.fname = "parts";
18531853+ Rpc.Types.field = typ_of_script_parts;
18541854+ Rpc.Types.fdefault = None;
18551855+ Rpc.Types.fdescription = [];
18561856+ Rpc.Types.fversion = None;
18571857+ Rpc.Types.fget = (fun _r -> _r.parts);
18581858+ Rpc.Types.fset = (fun v _s -> { _s with parts = v })
18591859+ }
18601860+ and exec_toplevel_result_mime_vals :
18611861+ (_, exec_toplevel_result) Rpc.Types.field =
18621862+ {
18631863+ Rpc.Types.fname = "mime_vals";
18641864+ Rpc.Types.field = (Rpc.Types.List typ_of_mime_val);
18651865+ Rpc.Types.fdefault = None;
18661866+ Rpc.Types.fdescription = [];
18671867+ Rpc.Types.fversion = None;
18681868+ Rpc.Types.fget = (fun _r -> _r.mime_vals);
18691869+ Rpc.Types.fset = (fun v _s -> { _s with mime_vals = v })
18701870+ }
18711871+ and typ_of_exec_toplevel_result =
18721872+ Rpc.Types.Struct
18731873+ ({
18741874+ Rpc.Types.fields =
18751875+ [Rpc.Types.BoxedField exec_toplevel_result_script;
18761876+ Rpc.Types.BoxedField exec_toplevel_result_parts;
18771877+ Rpc.Types.BoxedField exec_toplevel_result_mime_vals];
18781878+ Rpc.Types.sname = "exec_toplevel_result";
18791879+ Rpc.Types.version = None;
18801880+ Rpc.Types.constructor =
18811881+ (fun getter ->
18821882+ let open Rresult.R in
18831883+ (getter.Rpc.Types.field_get "mime_vals"
18841884+ (Rpc.Types.List typ_of_mime_val))
18851885+ >>=
18861886+ (fun exec_toplevel_result_mime_vals ->
18871887+ (getter.Rpc.Types.field_get "parts"
18881888+ typ_of_script_parts)
18891889+ >>=
18901890+ (fun exec_toplevel_result_parts ->
18911891+ (getter.Rpc.Types.field_get "script"
18921892+ (let open Rpc.Types in Basic String))
18931893+ >>=
18941894+ (fun exec_toplevel_result_script ->
18951895+ return
18961896+ {
18971897+ script = exec_toplevel_result_script;
18981898+ parts = exec_toplevel_result_parts;
18991899+ mime_vals =
19001900+ exec_toplevel_result_mime_vals
19011901+ }))))
19021902+ } : exec_toplevel_result Rpc.Types.structure)
19031903+ and exec_toplevel_result =
19041904+ {
19051905+ Rpc.Types.name = "exec_toplevel_result";
19061906+ Rpc.Types.description =
19071907+ ["Represents the result of executing a toplevel script"];
19081908+ Rpc.Types.ty = typ_of_exec_toplevel_result
19091909+ }
19101910+ let _ = exec_toplevel_result_script
19111911+ and _ = exec_toplevel_result_parts
19121912+ and _ = exec_toplevel_result_mime_vals
19131913+ and _ = typ_of_exec_toplevel_result
19141914+ and _ = exec_toplevel_result
19151915+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
19161916+type cma =
19171917+ {
19181918+ url: string [@ocaml.doc " URL where the cma is available "];
19191919+ fn: string [@ocaml.doc " Name of the 'wrapping' function "]}[@@deriving
19201920+ rpcty]
19211921+include
19221922+ struct
19231923+ let _ = fun (_ : cma) -> ()
19241924+ let rec cma_url : (_, cma) Rpc.Types.field =
19251925+ {
19261926+ Rpc.Types.fname = "url";
19271927+ Rpc.Types.field = (let open Rpc.Types in Basic String);
19281928+ Rpc.Types.fdefault = None;
19291929+ Rpc.Types.fdescription = ["URL where the cma is available"];
19301930+ Rpc.Types.fversion = None;
19311931+ Rpc.Types.fget = (fun _r -> _r.url);
19321932+ Rpc.Types.fset = (fun v _s -> { _s with url = v })
19331933+ }
19341934+ and cma_fn : (_, cma) Rpc.Types.field =
19351935+ {
19361936+ Rpc.Types.fname = "fn";
19371937+ Rpc.Types.field = (let open Rpc.Types in Basic String);
19381938+ Rpc.Types.fdefault = None;
19391939+ Rpc.Types.fdescription = ["Name of the 'wrapping' function"];
19401940+ Rpc.Types.fversion = None;
19411941+ Rpc.Types.fget = (fun _r -> _r.fn);
19421942+ Rpc.Types.fset = (fun v _s -> { _s with fn = v })
19431943+ }
19441944+ and typ_of_cma =
19451945+ Rpc.Types.Struct
19461946+ ({
19471947+ Rpc.Types.fields =
19481948+ [Rpc.Types.BoxedField cma_url; Rpc.Types.BoxedField cma_fn];
19491949+ Rpc.Types.sname = "cma";
19501950+ Rpc.Types.version = None;
19511951+ Rpc.Types.constructor =
19521952+ (fun getter ->
19531953+ let open Rresult.R in
19541954+ (getter.Rpc.Types.field_get "fn"
19551955+ (let open Rpc.Types in Basic String))
19561956+ >>=
19571957+ (fun cma_fn ->
19581958+ (getter.Rpc.Types.field_get "url"
19591959+ (let open Rpc.Types in Basic String))
19601960+ >>=
19611961+ (fun cma_url ->
19621962+ return { url = cma_url; fn = cma_fn })))
19631963+ } : cma Rpc.Types.structure)
19641964+ and cma =
19651965+ {
19661966+ Rpc.Types.name = "cma";
19671967+ Rpc.Types.description = [];
19681968+ Rpc.Types.ty = typ_of_cma
19691969+ }
19701970+ let _ = cma_url
19711971+ and _ = cma_fn
19721972+ and _ = typ_of_cma
19731973+ and _ = cma
19741974+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
19751975+type init_config =
19761976+ {
19771977+ findlib_requires: string list [@ocaml.doc " Findlib packages to require "];
19781978+ stdlib_dcs: string option
19791979+ [@ocaml.doc " URL to the dynamic cmis for the OCaml standard library "];
19801980+ findlib_index: string option
19811981+ [@ocaml.doc
19821982+ " URL to the findlib_index file. Defaults to \"findlib_index\" "];
19831983+ execute: bool
19841984+ [@ocaml.doc " Whether this session should support execution or not. "]}
19851985+[@@deriving rpcty]
19861986+include
19871987+ struct
19881988+ let _ = fun (_ : init_config) -> ()
19891989+ let rec init_config_findlib_requires : (_, init_config) Rpc.Types.field =
19901990+ {
19911991+ Rpc.Types.fname = "findlib_requires";
19921992+ Rpc.Types.field =
19931993+ (Rpc.Types.List (let open Rpc.Types in Basic String));
19941994+ Rpc.Types.fdefault = None;
19951995+ Rpc.Types.fdescription = ["Findlib packages to require"];
19961996+ Rpc.Types.fversion = None;
19971997+ Rpc.Types.fget = (fun _r -> _r.findlib_requires);
19981998+ Rpc.Types.fset = (fun v _s -> { _s with findlib_requires = v })
19991999+ }
20002000+ and init_config_stdlib_dcs : (_, init_config) Rpc.Types.field =
20012001+ {
20022002+ Rpc.Types.fname = "stdlib_dcs";
20032003+ Rpc.Types.field =
20042004+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
20052005+ Rpc.Types.fdefault = None;
20062006+ Rpc.Types.fdescription =
20072007+ ["URL to the dynamic cmis for the OCaml standard library"];
20082008+ Rpc.Types.fversion = None;
20092009+ Rpc.Types.fget = (fun _r -> _r.stdlib_dcs);
20102010+ Rpc.Types.fset = (fun v _s -> { _s with stdlib_dcs = v })
20112011+ }
20122012+ and init_config_findlib_index : (_, init_config) Rpc.Types.field =
20132013+ {
20142014+ Rpc.Types.fname = "findlib_index";
20152015+ Rpc.Types.field =
20162016+ (Rpc.Types.Option (let open Rpc.Types in Basic String));
20172017+ Rpc.Types.fdefault = None;
20182018+ Rpc.Types.fdescription =
20192019+ ["URL to the findlib_index file. Defaults to \"findlib_index\""];
20202020+ Rpc.Types.fversion = None;
20212021+ Rpc.Types.fget = (fun _r -> _r.findlib_index);
20222022+ Rpc.Types.fset = (fun v _s -> { _s with findlib_index = v })
20232023+ }
20242024+ and init_config_execute : (_, init_config) Rpc.Types.field =
20252025+ {
20262026+ Rpc.Types.fname = "execute";
20272027+ Rpc.Types.field = (let open Rpc.Types in Basic Bool);
20282028+ Rpc.Types.fdefault = None;
20292029+ Rpc.Types.fdescription =
20302030+ ["Whether this session should support execution or not."];
20312031+ Rpc.Types.fversion = None;
20322032+ Rpc.Types.fget = (fun _r -> _r.execute);
20332033+ Rpc.Types.fset = (fun v _s -> { _s with execute = v })
20342034+ }
20352035+ and typ_of_init_config =
20362036+ Rpc.Types.Struct
20372037+ ({
20382038+ Rpc.Types.fields =
20392039+ [Rpc.Types.BoxedField init_config_findlib_requires;
20402040+ Rpc.Types.BoxedField init_config_stdlib_dcs;
20412041+ Rpc.Types.BoxedField init_config_findlib_index;
20422042+ Rpc.Types.BoxedField init_config_execute];
20432043+ Rpc.Types.sname = "init_config";
20442044+ Rpc.Types.version = None;
20452045+ Rpc.Types.constructor =
20462046+ (fun getter ->
20472047+ let open Rresult.R in
20482048+ (getter.Rpc.Types.field_get "execute"
20492049+ (let open Rpc.Types in Basic Bool))
20502050+ >>=
20512051+ (fun init_config_execute ->
20522052+ (getter.Rpc.Types.field_get "findlib_index"
20532053+ (Rpc.Types.Option
20542054+ (let open Rpc.Types in Basic String)))
20552055+ >>=
20562056+ (fun init_config_findlib_index ->
20572057+ (getter.Rpc.Types.field_get "stdlib_dcs"
20582058+ (Rpc.Types.Option
20592059+ (let open Rpc.Types in Basic String)))
20602060+ >>=
20612061+ (fun init_config_stdlib_dcs ->
20622062+ (getter.Rpc.Types.field_get
20632063+ "findlib_requires"
20642064+ (Rpc.Types.List
20652065+ (let open Rpc.Types in Basic String)))
20662066+ >>=
20672067+ (fun init_config_findlib_requires ->
20682068+ return
20692069+ {
20702070+ findlib_requires =
20712071+ init_config_findlib_requires;
20722072+ stdlib_dcs = init_config_stdlib_dcs;
20732073+ findlib_index =
20742074+ init_config_findlib_index;
20752075+ execute = init_config_execute
20762076+ })))))
20772077+ } : init_config Rpc.Types.structure)
20782078+ and init_config =
20792079+ {
20802080+ Rpc.Types.name = "init_config";
20812081+ Rpc.Types.description = [];
20822082+ Rpc.Types.ty = typ_of_init_config
20832083+ }
20842084+ let _ = init_config_findlib_requires
20852085+ and _ = init_config_stdlib_dcs
20862086+ and _ = init_config_findlib_index
20872087+ and _ = init_config_execute
20882088+ and _ = typ_of_init_config
20892089+ and _ = init_config
20902090+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
20912091+type err =
20922092+ | InternalError of string [@@deriving rpcty]
20932093+include
20942094+ struct
20952095+ let _ = fun (_ : err) -> ()
20962096+ let rec typ_of_err =
20972097+ Rpc.Types.Variant
20982098+ ({
20992099+ Rpc.Types.vname = "err";
21002100+ Rpc.Types.variants =
21012101+ [BoxedTag
21022102+ {
21032103+ Rpc.Types.tname = "InternalError";
21042104+ Rpc.Types.tcontents =
21052105+ ((let open Rpc.Types in Basic String));
21062106+ Rpc.Types.tversion = None;
21072107+ Rpc.Types.tdescription = [];
21082108+ Rpc.Types.tpreview =
21092109+ ((function | InternalError a0 -> Some a0));
21102110+ Rpc.Types.treview = ((function | a0 -> InternalError a0))
21112111+ }];
21122112+ Rpc.Types.vdefault = None;
21132113+ Rpc.Types.vversion = None;
21142114+ Rpc.Types.vconstructor =
21152115+ (fun s' t ->
21162116+ let s = String.lowercase_ascii s' in
21172117+ match s with
21182118+ | "internalerror" ->
21192119+ Rresult.R.bind
21202120+ (t.tget (let open Rpc.Types in Basic String))
21212121+ (function | a0 -> Rresult.R.ok (InternalError a0))
21222122+ | _ ->
21232123+ Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s))
21242124+ } : err Rpc.Types.variant)
21252125+ and err =
21262126+ {
21272127+ Rpc.Types.name = "err";
21282128+ Rpc.Types.description = [];
21292129+ Rpc.Types.ty = typ_of_err
21302130+ }
21312131+ let _ = typ_of_err
21322132+ and _ = err
21332133+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
21342134+type opt_id = string option[@@deriving rpcty]
21352135+include
21362136+ struct
21372137+ let _ = fun (_ : opt_id) -> ()
21382138+ let rec typ_of_opt_id =
21392139+ Rpc.Types.Option (let open Rpc.Types in Basic String)
21402140+ and opt_id =
21412141+ {
21422142+ Rpc.Types.name = "opt_id";
21432143+ Rpc.Types.description = [];
21442144+ Rpc.Types.ty = typ_of_opt_id
21452145+ }
21462146+ let _ = typ_of_opt_id
21472147+ and _ = opt_id
21482148+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
21492149+type env_id = string[@@deriving rpcty][@@ocaml.doc
21502150+ " Environment identifier. If empty string, uses the default environment. "]
21512151+include
21522152+ struct
21532153+ let _ = fun (_ : env_id) -> ()
21542154+ let rec typ_of_env_id = let open Rpc.Types in Basic String
21552155+ and env_id =
21562156+ {
21572157+ Rpc.Types.name = "env_id";
21582158+ Rpc.Types.description =
21592159+ ["Environment identifier. If empty string, uses the default environment."];
21602160+ Rpc.Types.ty = typ_of_env_id
21612161+ }
21622162+ let _ = typ_of_env_id
21632163+ and _ = env_id
21642164+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
21652165+type env_id_list = string list[@@deriving rpcty][@@ocaml.doc
21662166+ " List of environment identifiers "]
21672167+include
21682168+ struct
21692169+ let _ = fun (_ : env_id_list) -> ()
21702170+ let rec typ_of_env_id_list =
21712171+ Rpc.Types.List (let open Rpc.Types in Basic String)
21722172+ and env_id_list =
21732173+ {
21742174+ Rpc.Types.name = "env_id_list";
21752175+ Rpc.Types.description = ["List of environment identifiers"];
21762176+ Rpc.Types.ty = typ_of_env_id_list
21772177+ }
21782178+ let _ = typ_of_env_id_list
21792179+ and _ = env_id_list
21802180+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
21812181+type dependencies = string list[@@deriving rpcty][@@ocaml.doc
21822182+ " The ids of the cells that are dependencies "]
21832183+include
21842184+ struct
21852185+ let _ = fun (_ : dependencies) -> ()
21862186+ let rec typ_of_dependencies =
21872187+ Rpc.Types.List (let open Rpc.Types in Basic String)
21882188+ and dependencies =
21892189+ {
21902190+ Rpc.Types.name = "dependencies";
21912191+ Rpc.Types.description =
21922192+ ["The ids of the cells that are dependencies"];
21932193+ Rpc.Types.ty = typ_of_dependencies
21942194+ }
21952195+ let _ = typ_of_dependencies
21962196+ and _ = dependencies
21972197+ end[@@ocaml.doc "@inline"][@@merlin.hide ]
21982198+module E =
21992199+ (Idl.Error.Make)(struct
22002200+ type t = err
22012201+ let t = err
22022202+ let internal_error_of e =
22032203+ Some (InternalError (Printexc.to_string e))
22042204+ end)
22052205+let err = E.error
22062206+module Make(R:RPC) =
22072207+ struct
22082208+ open R
22092209+ let description =
22102210+ let open Interface in
22112211+ {
22122212+ name = "Toplevel";
22132213+ namespace = None;
22142214+ description =
22152215+ ["Functions for manipulating the toplevel worker thread"];
22162216+ version = (1, 0, 0)
22172217+ }
22182218+ let implementation = implement description
22192219+ let unit_p = Param.mk Types.unit
22202220+ let phrase_p =
22212221+ Param.mk ~name:"string" ~description:["The OCaml phrase to execute"]
22222222+ Types.string
22232223+ let id_p = Param.mk opt_id
22242224+ let env_id_p =
22252225+ Param.mk ~name:"env_id"
22262226+ ~description:["Environment ID (empty string for default)"] env_id
22272227+ let env_id_list_p = Param.mk env_id_list
22282228+ let dependencies_p = Param.mk dependencies
22292229+ let exec_result_p = Param.mk exec_result
22302230+ let source_p = Param.mk source
22312231+ let position_p = Param.mk msource_position
22322232+ let completions_p = Param.mk completions
22332233+ let error_list_p = Param.mk error_list
22342234+ let typed_enclosings_p = Param.mk typed_enclosings_list
22352235+ let is_toplevel_p = Param.mk ~name:"is_toplevel" Types.bool
22362236+ let toplevel_script_p =
22372237+ Param.mk
22382238+ ~description:["A toplevel script is a sequence of toplevel phrases interspersed with";
22392239+ "The output from the toplevel. Each phase must be preceded by '# ', and";
22402240+ "the output from the toplevel is indented by 2 spaces."]
22412241+ Types.string
22422242+ let exec_toplevel_result_p = Param.mk exec_toplevel_result
22432243+ let init_libs =
22442244+ Param.mk ~name:"init_libs"
22452245+ ~description:["Configuration for the toplevel."] init_config
22462246+ let init =
22472247+ declare "init"
22482248+ ["Initialise the toplevel. This must be called before any other API."]
22492249+ (init_libs @-> (returning unit_p err))
22502250+ [@@@ocaml.text " {2 Environment Management} "]
22512251+ let create_env =
22522252+ declare "create_env"
22532253+ ["Create a new isolated execution environment with the given ID.";
22542254+ "Returns unit on success. The environment must be set up with";
22552255+ "setup_env before use."] (env_id_p @-> (returning unit_p err))
22562256+ let destroy_env =
22572257+ declare "destroy_env"
22582258+ ["Destroy an execution environment, freeing its resources.";
22592259+ "The environment ID must exist."]
22602260+ (env_id_p @-> (returning unit_p err))
22612261+ let list_envs =
22622262+ declare "list_envs" ["List all existing environment IDs."]
22632263+ (unit_p @-> (returning env_id_list_p err))
22642264+ let setup =
22652265+ declare "setup"
22662266+ ["Start the toplevel for the given environment. Return value is the";
22672267+ "initial blurb printed when starting a toplevel. Note that the";
22682268+ "toplevel must be initialised first. If env_id is None, uses the";
22692269+ "default environment."] (env_id_p @-> (returning exec_result_p err))
22702270+ let exec =
22712271+ declare "exec"
22722272+ ["Execute a phrase using the toplevel. The toplevel must have been";
22732273+ "initialised first. If env_id is None, uses the default environment."]
22742274+ (env_id_p @-> (phrase_p @-> (returning exec_result_p err)))
22752275+ let exec_toplevel =
22762276+ declare "exec_toplevel"
22772277+ ["Execute a toplevel script. The toplevel must have been";
22782278+ "initialised first. Returns the updated toplevel script.";
22792279+ "If env_id is None, uses the default environment."]
22802280+ (env_id_p @->
22812281+ (toplevel_script_p @-> (returning exec_toplevel_result_p err)))
22822282+ let complete_prefix =
22832283+ declare "complete_prefix"
22842284+ ["Complete a prefix. If env_id is None, uses the default environment."]
22852285+ (env_id_p @->
22862286+ (id_p @->
22872287+ (dependencies_p @->
22882288+ (is_toplevel_p @->
22892289+ (source_p @->
22902290+ (position_p @-> (returning completions_p err)))))))
22912291+ let query_errors =
22922292+ declare "query_errors"
22932293+ ["Query the errors in the given source.";
22942294+ "If env_id is None, uses the default environment."]
22952295+ (env_id_p @->
22962296+ (id_p @->
22972297+ (dependencies_p @->
22982298+ (is_toplevel_p @->
22992299+ (source_p @-> (returning error_list_p err))))))
23002300+ let type_enclosing =
23012301+ declare "type_enclosing"
23022302+ ["Get the type of the enclosing expression.";
23032303+ "If env_id is None, uses the default environment."]
23042304+ (env_id_p @->
23052305+ (id_p @->
23062306+ (dependencies_p @->
23072307+ (is_toplevel_p @->
23082308+ (source_p @->
23092309+ (position_p @-> (returning typed_enclosings_p err)))))))
23102310+ end
+34
js_top_worker/idl/transport.ml
···11+(** Transport abstraction for RPC encoding.
22+33+ This module provides a common interface for encoding/decoding RPC messages.
44+ Uses JSON-RPC for browser compatibility. *)
55+66+module type S = sig
77+ (** Encode a call (ID is auto-generated) *)
88+ val string_of_call : Rpc.call -> string
99+1010+ (** Decode a message to get the ID and call *)
1111+ val id_and_call_of_string : string -> Rpc.t * Rpc.call
1212+1313+ (** Encode a response with the given ID *)
1414+ val string_of_response : id:Rpc.t -> Rpc.response -> string
1515+1616+ (** Decode a message to get the response *)
1717+ val response_of_string : string -> Rpc.response
1818+end
1919+2020+(** JSON-RPC transport *)
2121+module Json : S = struct
2222+ let string_of_call call =
2323+ Jsonrpc.string_of_call call
2424+2525+ let id_and_call_of_string s =
2626+ let _, id, call = Jsonrpc.version_id_and_call_of_string s in
2727+ (id, call)
2828+2929+ let string_of_response ~id response =
3030+ Jsonrpc.string_of_response ~id response
3131+3232+ let response_of_string s =
3333+ Jsonrpc.response_of_string s
3434+end
+25
js_top_worker/idl/transport.mli
···11+(** Transport abstraction for RPC encoding.
22+33+ This module provides a common interface for encoding/decoding RPC messages.
44+ Uses JSON-RPC for browser compatibility. *)
55+66+(** Transport signature defining the encoding/decoding interface. *)
77+module type S = sig
88+ val string_of_call : Rpc.call -> string
99+ (** Encode a call. A unique request ID is auto-generated. *)
1010+1111+ val id_and_call_of_string : string -> Rpc.t * Rpc.call
1212+ (** Decode a message to get the ID and call.
1313+ @raise Failure if decoding fails. *)
1414+1515+ val string_of_response : id:Rpc.t -> Rpc.response -> string
1616+ (** Encode a response with the given ID. *)
1717+1818+ val response_of_string : string -> Rpc.response
1919+ (** Decode a message to get the response.
2020+ @raise Failure if decoding fails. *)
2121+end
2222+2323+(** JSON-RPC transport.
2424+ Uses the standard JSON-RPC 2.0 encoding from [rpclib.json]. *)
2525+module Json : S
···11+(** Multiple isolated execution environments.
22+33+ This module provides isolated execution environments for the OCaml toplevel.
44+ Each environment maintains both:
55+ - The typing environment (Env.t) which tracks type bindings
66+ - Runtime values (via Toploop.getvalue/setvalue) which store actual values
77+88+ When switching between environments, both are saved and restored to ensure
99+ complete isolation of definitions. *)
1010+1111+module StringSet = Set.Make (String)
1212+module StringMap = Map.Make (String)
1313+1414+(* Debug logging - uses the Logs module which is configured in the worker *)
1515+let log_debug msg = Logs.debug (fun m -> m "%s" msg)
1616+1717+type id = string
1818+1919+(** Runtime values are stored as a map from binding name to Obj.t.
2020+ We use Obj.t because Toploop.getvalue/setvalue work with Obj.t. *)
2121+type runtime_values = Obj.t StringMap.t
2222+2323+type t = {
2424+ id : id;
2525+ mutable toplevel_env : Env.t option;
2626+ mutable runtime_values : runtime_values;
2727+ mutable is_setup : bool;
2828+ failed_cells : StringSet.t ref;
2929+}
3030+3131+let default_id = "default"
3232+3333+(* Global table of environments *)
3434+let environments : (id, t) Hashtbl.t = Hashtbl.create 16
3535+3636+let create id =
3737+ let env = {
3838+ id;
3939+ toplevel_env = None;
4040+ runtime_values = StringMap.empty;
4141+ is_setup = false;
4242+ failed_cells = ref StringSet.empty;
4343+ } in
4444+ Hashtbl.replace environments id env;
4545+ env
4646+4747+let get id = Hashtbl.find_opt environments id
4848+4949+let get_or_create id =
5050+ match get id with
5151+ | Some env -> env
5252+ | None -> create id
5353+5454+let destroy id = Hashtbl.remove environments id
5555+5656+let list () = Hashtbl.fold (fun id _ acc -> id :: acc) environments []
5757+5858+let id env = env.id
5959+6060+(** Get the toplevel name for a binding identifier.
6161+ This is used to look up runtime values via Toploop.getvalue. *)
6262+let toplevel_name ident = Translmod.toplevel_name ident
6363+6464+(** Restore runtime values from the stored map.
6565+ This sets the values in the bytecode global table. *)
6666+let restore_runtime_values env_id values =
6767+ let count = StringMap.cardinal values in
6868+ if count > 0 then
6969+ log_debug (Printf.sprintf "[ENV] Restoring %d runtime values for env %s" count env_id);
7070+ StringMap.iter (fun name value ->
7171+ try
7272+ log_debug (Printf.sprintf "[ENV] setvalue %s" name);
7373+ Toploop.setvalue name value
7474+ with e ->
7575+ log_debug (Printf.sprintf "[ENV] setvalue %s failed: %s" name (Printexc.to_string e))
7676+ ) values
7777+7878+(** Check if an identifier is a value binding in the given environment.
7979+ Returns true for let-bindings, false for exceptions, modules, types, etc. *)
8080+let is_value_binding typing_env ident =
8181+ try
8282+ let path = Path.Pident ident in
8383+ let _ = Env.find_value path typing_env in
8484+ true
8585+ with Not_found -> false
8686+8787+(** Capture runtime values for the given identifiers.
8888+ Only captures value bindings (not exceptions, modules, etc.).
8989+ Returns an updated map with the new values. *)
9090+let capture_runtime_values typing_env env_id base_map idents =
9191+ (* Filter to only value bindings to avoid "Fatal error" from Toploop.getvalue *)
9292+ let value_idents = List.filter (is_value_binding typing_env) idents in
9393+ if value_idents <> [] then
9494+ log_debug (Printf.sprintf "[ENV] Capturing %d value bindings for env %s (filtered from %d total)"
9595+ (List.length value_idents) env_id (List.length idents));
9696+ List.fold_left (fun map ident ->
9797+ let name = toplevel_name ident in
9898+ try
9999+ let value = Toploop.getvalue name in
100100+ log_debug (Printf.sprintf "[ENV] captured %s" name);
101101+ StringMap.add name value map
102102+ with e ->
103103+ log_debug (Printf.sprintf "[ENV] could not capture %s: %s" name (Printexc.to_string e));
104104+ map
105105+ ) base_map value_idents
106106+107107+let with_env env f =
108108+ log_debug (Printf.sprintf "[ENV] with_env called for %s (has_saved_env=%b, runtime_values_count=%d)"
109109+ env.id (Option.is_some env.toplevel_env) (StringMap.cardinal env.runtime_values));
110110+111111+ (* Save current toplevel environment *)
112112+ let saved_typing_env = !Toploop.toplevel_env in
113113+ let saved_typing_env_before =
114114+ match env.toplevel_env with
115115+ | Some e -> e
116116+ | None -> saved_typing_env
117117+ in
118118+119119+ (* Restore this environment's typing environment if we have one *)
120120+ (match env.toplevel_env with
121121+ | Some e -> Toploop.toplevel_env := e
122122+ | None -> ());
123123+124124+ (* Restore this environment's runtime values *)
125125+ restore_runtime_values env.id env.runtime_values;
126126+127127+ (* Run the function *)
128128+ let result =
129129+ try f ()
130130+ with exn ->
131131+ (* Capture new bindings before re-raising *)
132132+ let current_typing_env = !Toploop.toplevel_env in
133133+ let new_idents = Env.diff saved_typing_env_before current_typing_env in
134134+ let updated_values = capture_runtime_values current_typing_env env.id env.runtime_values new_idents in
135135+ env.runtime_values <- updated_values;
136136+ env.toplevel_env <- Some current_typing_env;
137137+ Toploop.toplevel_env := saved_typing_env;
138138+ raise exn
139139+ in
140140+141141+ (* Capture new bindings that were added during execution *)
142142+ let current_typing_env = !Toploop.toplevel_env in
143143+ let new_idents = Env.diff saved_typing_env_before current_typing_env in
144144+ log_debug (Printf.sprintf "[ENV] Env.diff found %d new idents for %s" (List.length new_idents) env.id);
145145+ let updated_values = capture_runtime_values current_typing_env env.id env.runtime_values new_idents in
146146+147147+ (* Save the updated environment state *)
148148+ env.runtime_values <- updated_values;
149149+ env.toplevel_env <- Some !Toploop.toplevel_env;
150150+151151+ (* Restore the previous typing environment *)
152152+ Toploop.toplevel_env := saved_typing_env;
153153+154154+ result
155155+156156+let is_setup env = env.is_setup
157157+158158+let mark_setup env = env.is_setup <- true
159159+160160+let get_failed_cells env = !(env.failed_cells)
161161+162162+let add_failed_cell env cell_id =
163163+ env.failed_cells := StringSet.add cell_id !(env.failed_cells)
164164+165165+let remove_failed_cell env cell_id =
166166+ env.failed_cells := StringSet.remove cell_id !(env.failed_cells)
167167+168168+let is_cell_failed env cell_id =
169169+ StringSet.mem cell_id !(env.failed_cells)
+72
js_top_worker/lib/environment.mli
···11+(** Multiple isolated execution environments.
22+33+ This module provides support for running multiple isolated OCaml
44+ evaluation contexts within a single worker. Each environment has
55+ its own type environment, allowing independent code execution
66+ without interference.
77+88+ Libraries are shared across all environments to save memory - once
99+ a library is loaded, it's available to all environments. *)
1010+1111+(** {1 Types} *)
1212+1313+type t
1414+(** An isolated execution environment. *)
1515+1616+type id = string
1717+(** Environment identifier. *)
1818+1919+(** {1 Environment Management} *)
2020+2121+val create : id -> t
2222+(** [create id] creates a new environment with the given identifier.
2323+ The environment starts uninitialized; call [setup] after creation. *)
2424+2525+val get : id -> t option
2626+(** [get id] returns the environment with the given identifier, if it exists. *)
2727+2828+val get_or_create : id -> t
2929+(** [get_or_create id] returns the existing environment or creates a new one. *)
3030+3131+val destroy : id -> unit
3232+(** [destroy id] removes the environment with the given identifier. *)
3333+3434+val list : unit -> id list
3535+(** [list ()] returns all environment identifiers. *)
3636+3737+val default_id : id
3838+(** The default environment identifier used when none is specified. *)
3939+4040+val id : t -> id
4141+(** [id env] returns the identifier of the environment. *)
4242+4343+(** {1 Environment Switching} *)
4444+4545+val with_env : t -> (unit -> 'a) -> 'a
4646+(** [with_env env f] runs [f ()] in the context of environment [env].
4747+ The toplevel environment is saved before and restored after,
4848+ allowing isolated execution. *)
4949+5050+(** {1 Environment State} *)
5151+5252+val is_setup : t -> bool
5353+(** [is_setup env] returns whether [setup] has been called for this environment. *)
5454+5555+val mark_setup : t -> unit
5656+(** [mark_setup env] marks the environment as having completed setup. *)
5757+5858+(** {1 Failed Cells Tracking} *)
5959+6060+module StringSet : Set.S with type elt = string
6161+6262+val get_failed_cells : t -> StringSet.t
6363+(** [get_failed_cells env] returns the set of cell IDs that failed to compile. *)
6464+6565+val add_failed_cell : t -> string -> unit
6666+(** [add_failed_cell env cell_id] marks a cell as failed. *)
6767+6868+val remove_failed_cell : t -> string -> unit
6969+(** [remove_failed_cell env cell_id] marks a cell as no longer failed. *)
7070+7171+val is_cell_failed : t -> string -> bool
7272+(** [is_cell_failed env cell_id] checks if a cell is marked as failed. *)
+313
js_top_worker/lib/findlibish.ml
···11+(* Kinda findlib, sorta *)
22+33+type library = {
44+ name : string;
55+ meta_uri : Uri.t;
66+ archive_name : string option;
77+ dir : string option;
88+ deps : string list;
99+ children : library list;
1010+ mutable loaded : bool;
1111+}
1212+1313+let rec flatten_libs libs =
1414+ let handle_lib l =
1515+ let children = flatten_libs l.children in
1616+ l :: children
1717+ in
1818+ List.map handle_lib libs |> List.flatten
1919+2020+let preloaded =
2121+ [
2222+ "angstrom";
2323+ "astring";
2424+ "compiler-libs.common";
2525+ "compiler-libs.toplevel";
2626+ "findlib";
2727+ "findlib.top";
2828+ "fpath";
2929+ "js_of_ocaml-compiler";
3030+ "js_of_ocaml-ppx";
3131+ "js_of_ocaml-toplevel";
3232+ "js_top_worker";
3333+ "js_top_worker-rpc";
3434+ "logs";
3535+ "logs.browser";
3636+ "merlin-lib.kernel";
3737+ "merlin-lib.ocaml_parsing";
3838+ "merlin-lib.query_commands";
3939+ "merlin-lib.query_protocol";
4040+ "merlin-lib.utils";
4141+ "mime_printer";
4242+ "uri";
4343+ ]
4444+4545+let rec read_libraries_from_pkg_defs ~library_name ~dir meta_uri pkg_expr =
4646+ try
4747+ Jslib.log "Reading library: %s" library_name;
4848+ let pkg_defs = pkg_expr.Fl_metascanner.pkg_defs in
4949+ (* Try to find archive with various predicates.
5050+ PPX packages often only define archive(ppx_driver,byte), so we need to
5151+ check multiple predicate combinations to find the right archive. *)
5252+ let archive_filename =
5353+ (* First try with ppx_driver,byte - this catches PPX libraries like ppx_deriving.show *)
5454+ try Some (Fl_metascanner.lookup "archive" [ "ppx_driver"; "byte" ] pkg_defs)
5555+ with _ -> (
5656+ (* Then try plain byte *)
5757+ try Some (Fl_metascanner.lookup "archive" [ "byte" ] pkg_defs)
5858+ with _ -> (
5959+ (* Then try native as fallback *)
6060+ try Some (Fl_metascanner.lookup "archive" [ "native" ] pkg_defs)
6161+ with _ -> None))
6262+ in
6363+6464+ (* Use -ppx_driver predicate for toplevel use - this ensures PPX packages
6565+ pull in their runtime dependencies (e.g., ppx_deriving.show requires
6666+ ppx_deriving.runtime when not using ppx_driver) *)
6767+ let predicates = ["-ppx_driver"] in
6868+ let deps_str =
6969+ try Fl_metascanner.lookup "requires" predicates pkg_defs with _ -> "" in
7070+ let deps = Astring.String.fields ~empty:false deps_str in
7171+ let subdir =
7272+ List.find_opt (fun d -> d.Fl_metascanner.def_var = "directory") pkg_defs
7373+ |> Option.map (fun d -> d.Fl_metascanner.def_value)
7474+ in
7575+ let dir =
7676+ match (dir, subdir) with
7777+ | None, None -> None
7878+ | Some d, None -> Some d
7979+ | None, Some d -> Some d
8080+ | Some d1, Some d2 -> Some (Filename.concat d1 d2)
8181+ in
8282+ let archive_name =
8383+ Option.bind archive_filename (fun a ->
8484+ let file_name_len = String.length a in
8585+ if file_name_len > 0 then Some (Filename.chop_extension a) else None)
8686+ in
8787+ Jslib.log "Number of children: %d" (List.length pkg_expr.pkg_children);
8888+ let children =
8989+ List.filter_map
9090+ (fun (n, expr) ->
9191+ Jslib.log "Found child: %s" n;
9292+ let library_name = library_name ^ "." ^ n in
9393+ match
9494+ read_libraries_from_pkg_defs ~library_name ~dir meta_uri expr
9595+ with
9696+ | Ok l -> Some l
9797+ | Error (`Msg m) ->
9898+ Jslib.log "Error reading sub-library: %s" m;
9999+ None)
100100+ pkg_expr.pkg_children
101101+ in
102102+ Ok
103103+ {
104104+ name = library_name;
105105+ archive_name;
106106+ dir;
107107+ deps;
108108+ meta_uri;
109109+ loaded = false;
110110+ children;
111111+ }
112112+ with Not_found -> Error (`Msg "Failed to read libraries from pkg_defs")
113113+114114+type t = library list
115115+116116+let dcs_filename = "dynamic_cmis.json"
117117+118118+let fetch_dynamic_cmis sync_get url =
119119+ match sync_get url with
120120+ | None -> Error (`Msg "Failed to fetch dynamic cmis")
121121+ | Some json ->
122122+ let rpc = Jsonrpc.of_string json in
123123+ Rpcmarshal.unmarshal
124124+ Js_top_worker_rpc.Toplevel_api_gen.typ_of_dynamic_cmis rpc
125125+126126+let (let*) = Lwt.bind
127127+128128+(** Parse a findlib_index file (JSON or legacy text format) and return
129129+ the list of META file paths and universe paths.
130130+131131+ JSON format: {"meta_files": ["path/to/META", ...], "universes": ["universe1", ...]}
132132+133133+ meta_files: direct paths to META files
134134+ universes: paths to other universes (directories containing findlib_index) *)
135135+let parse_findlib_index content =
136136+ (* Try JSON format first *)
137137+ try
138138+ let json = Yojson.Safe.from_string content in
139139+ let open Yojson.Safe.Util in
140140+ (* Support both "meta_files" and "metas" for compatibility *)
141141+ let meta_files =
142142+ try json |> member "meta_files" |> to_list |> List.map to_string
143143+ with _ ->
144144+ try json |> member "metas" |> to_list |> List.map to_string
145145+ with _ -> []
146146+ in
147147+ (* Support both "universes" and "deps" for compatibility *)
148148+ let universes =
149149+ try json |> member "universes" |> to_list |> List.map to_string
150150+ with _ ->
151151+ try json |> member "deps" |> to_list |> List.map to_string
152152+ with _ -> []
153153+ in
154154+ (meta_files, universes)
155155+ with _ ->
156156+ (* Fall back to legacy whitespace-separated format (no universes) *)
157157+ (Astring.String.fields ~empty:false content, [])
158158+159159+(** Load a single META file and parse it into a library *)
160160+let load_meta async_get meta_path =
161161+ let* res = async_get meta_path in
162162+ match res with
163163+ | Error (`Msg m) ->
164164+ Jslib.log "Error fetching findlib meta %s: %s" meta_path m;
165165+ Lwt.return_none
166166+ | Ok meta_content ->
167167+ match Angstrom.parse_string ~consume:All Uri.Parser.uri_reference meta_path with
168168+ | Ok uri -> (
169169+ Jslib.log "Parsed uri: %s" (Uri.path uri);
170170+ let path = Uri.path uri in
171171+ let file = Fpath.v path in
172172+ let base_library_name =
173173+ if Fpath.basename file = "META" then
174174+ Fpath.parent file |> Fpath.basename
175175+ else Fpath.get_ext file
176176+ in
177177+ let lexing = Lexing.from_string meta_content in
178178+ try
179179+ let meta = Fl_metascanner.parse_lexing lexing in
180180+ let libraries =
181181+ read_libraries_from_pkg_defs ~library_name:base_library_name
182182+ ~dir:None uri meta
183183+ in
184184+ Lwt.return (Result.to_option libraries)
185185+ with _ ->
186186+ Jslib.log "Failed to parse meta: %s" (Uri.path uri);
187187+ Lwt.return_none)
188188+ | Error m ->
189189+ Jslib.log "Failed to parse uri: %s" m;
190190+ Lwt.return_none
191191+192192+(** Resolve a path relative to the directory of the base URL.
193193+ Used for meta_files which are relative to their findlib_index.
194194+ e.g. base="http://host/demo1/base/findlib_index", path="lib/base/META"
195195+ => "http://host/demo1/base/lib/base/META" *)
196196+let resolve_relative_to_dir ~base path =
197197+ match Angstrom.parse_string ~consume:All Uri.Parser.uri_reference base with
198198+ | Ok base_uri ->
199199+ let base_path = Uri.path base_uri in
200200+ let parent_dir =
201201+ match Fpath.of_string base_path with
202202+ | Ok p -> Fpath.parent p |> Fpath.to_string
203203+ | Error _ -> "/"
204204+ in
205205+ let resolved = Filename.concat parent_dir path in
206206+ Uri.with_path base_uri resolved |> Uri.to_string
207207+ | Error _ -> path
208208+209209+(** Resolve a path as absolute from root (preserving scheme/host from base).
210210+ Used for universe paths which are already full paths from root.
211211+ e.g. base="http://host/demo1/findlib_index", path="demo1/base/findlib_index"
212212+ => "http://host/demo1/base/findlib_index" *)
213213+let resolve_from_root ~base path =
214214+ match Angstrom.parse_string ~consume:All Uri.Parser.uri_reference base with
215215+ | Ok base_uri ->
216216+ let resolved = "/" ^ path in
217217+ Uri.with_path base_uri resolved |> Uri.to_string
218218+ | Error _ -> "/" ^ path
219219+220220+let init (async_get : string -> (string, [>`Msg of string]) result Lwt.t) findlib_index : t Lwt.t =
221221+ Jslib.log "Initializing findlib";
222222+ (* Track visited universes to avoid infinite loops *)
223223+ let visited = Hashtbl.create 16 in
224224+ let rec load_universe index_url =
225225+ if Hashtbl.mem visited index_url then
226226+ Lwt.return []
227227+ else begin
228228+ Hashtbl.add visited index_url ();
229229+ let* findlib_txt = async_get index_url in
230230+ match findlib_txt with
231231+ | Error (`Msg m) ->
232232+ Jslib.log "Error fetching findlib index %s: %s" index_url m;
233233+ Lwt.return []
234234+ | Ok content ->
235235+ let meta_files, universes = parse_findlib_index content in
236236+ Jslib.log "Loaded findlib_index %s: %d META files, %d universes"
237237+ index_url (List.length meta_files) (List.length universes);
238238+ (* Resolve META paths relative to findlib_index directory *)
239239+ let resolved_metas =
240240+ List.map (fun p -> resolve_relative_to_dir ~base:index_url p) meta_files
241241+ in
242242+ (* Load META files from this universe *)
243243+ let* local_libs = Lwt_list.filter_map_p (load_meta async_get) resolved_metas in
244244+ (* Resolve universe paths from root (they're already full paths) *)
245245+ let universe_index_urls =
246246+ List.map (fun u ->
247247+ resolve_from_root ~base:index_url (Filename.concat u "findlib_index"))
248248+ universes
249249+ in
250250+ let* universe_libs = Lwt_list.map_p load_universe universe_index_urls in
251251+ Lwt.return (local_libs @ List.flatten universe_libs)
252252+ end
253253+ in
254254+ let* all_libs = load_universe findlib_index in
255255+ Lwt.return (flatten_libs all_libs)
256256+257257+let require ~import_scripts sync_get cmi_only v packages =
258258+ let rec require dcss package :
259259+ Js_top_worker_rpc.Toplevel_api_gen.dynamic_cmis list =
260260+ match List.find (fun lib -> lib.name = package) v with
261261+ | exception Not_found ->
262262+ Jslib.log "Package %s not found" package;
263263+ let available =
264264+ v
265265+ |> List.map (fun lib ->
266266+ Printf.sprintf "%s (%d)" lib.name (List.length lib.children))
267267+ |> String.concat ", "
268268+ in
269269+ Jslib.log "Available packages: %s" available;
270270+ dcss
271271+ | lib ->
272272+ if lib.loaded then dcss
273273+ else (
274274+ Jslib.log "Loading package %s" lib.name;
275275+ Jslib.log "lib.dir: %s" (Option.value ~default:"None" lib.dir);
276276+ let dep_dcs = List.fold_left require dcss lib.deps in
277277+ let path = Fpath.(v (Uri.path lib.meta_uri) |> parent) in
278278+ let dir =
279279+ match lib.dir with
280280+ | None -> path
281281+ | Some "+" -> Fpath.parent path (* "+" means parent dir in findlib *)
282282+ | Some d when String.length d > 0 && d.[0] = '^' ->
283283+ (* "^" prefix means relative to stdlib dir - treat as parent *)
284284+ Fpath.parent path
285285+ | Some d -> Fpath.(path // v d)
286286+ in
287287+ let dcs = Fpath.(dir / dcs_filename |> to_string) in
288288+ let uri = Uri.with_path lib.meta_uri dcs in
289289+ Jslib.log "uri: %s" (Uri.to_string uri);
290290+ match fetch_dynamic_cmis sync_get (Uri.to_string uri) with
291291+ | Ok dcs ->
292292+ let should_load =
293293+ (not (List.mem lib.name preloaded)) && not cmi_only
294294+ in
295295+ Option.iter
296296+ (fun archive ->
297297+ if should_load then begin
298298+ let archive_js =
299299+ Fpath.(dir / (archive ^ ".cma.js") |> to_string)
300300+ in
301301+ import_scripts
302302+ [ Uri.with_path uri archive_js |> Uri.to_string ]
303303+ end)
304304+ lib.archive_name;
305305+ lib.loaded <- true;
306306+ Jslib.log "Finished loading package %s" lib.name;
307307+ dcs :: dep_dcs
308308+ | Error (`Msg m) ->
309309+ Jslib.log "Failed to unmarshal dynamic_cms from url %s: %s"
310310+ (Uri.to_string uri) m;
311311+ dcss)
312312+ in
313313+ List.fold_left require [] packages
+1164
js_top_worker/lib/impl.ml
···11+(** {1 OCaml Toplevel Implementation}
22+33+ This module provides the core toplevel functionality for js_top_worker.
44+ It implements phrase execution, type checking, and Merlin integration
55+ (completion, errors, type info).
66+77+ The module is parameterized by a backend signature [S] which provides
88+ platform-specific operations for different environments (WebWorker,
99+ Node.js, Unix). *)
1010+1111+open Js_top_worker_rpc
1212+module M = Rpc_lwt.ErrM (* Server is not synchronous *)
1313+module IdlM = Rpc_lwt
1414+1515+let ( let* ) = Lwt.bind
1616+1717+(** {2 Cell Dependency System}
1818+1919+ Cells are identified by string IDs and can depend on previous cells.
2020+ Each cell is wrapped in a module [Cell__<id>] so that later cells can
2121+ access earlier bindings via [open Cell__<id>]. *)
2222+2323+type captured = { stdout : string; stderr : string }
2424+2525+let modname_of_id id = "Cell__" ^ id
2626+2727+let is_mangled_broken orig src =
2828+ String.length orig <> String.length src
2929+ || Seq.exists2
3030+ (fun c c' -> c <> c' && c' <> ' ')
3131+ (String.to_seq orig) (String.to_seq src)
3232+3333+let mangle_toplevel is_toplevel orig_source deps =
3434+ let src =
3535+ if not is_toplevel then orig_source
3636+ else if
3737+ String.length orig_source < 2
3838+ || orig_source.[0] <> '#'
3939+ || orig_source.[1] <> ' '
4040+ then (
4141+ Logs.err (fun m ->
4242+ m "xx Warning, ignoring toplevel block without a leading '# '.\n%!");
4343+ orig_source)
4444+ else
4545+ try
4646+ let s = String.sub orig_source 2 (String.length orig_source - 2) in
4747+ let list =
4848+ try Ocamltop.parse_toplevel s
4949+ with _ -> Ocamltop.fallback_parse_toplevel s
5050+ in
5151+ let lines =
5252+ List.map
5353+ (fun (phr, junk, output) ->
5454+ let l1 =
5555+ Printf.sprintf " %s%s" phr
5656+ (String.make (String.length junk) ' ')
5757+ in
5858+ match output with
5959+ | [] -> l1
6060+ | _ ->
6161+ let s =
6262+ List.map (fun x -> String.make (String.length x) ' ') output
6363+ in
6464+ String.concat "\n" (l1 :: s))
6565+ list
6666+ in
6767+ String.concat "\n" lines
6868+ with e ->
6969+ Logs.err (fun m ->
7070+ m "Error in mangle_toplevel: %s" (Printexc.to_string e));
7171+ let ppf = Format.err_formatter in
7272+ let _ = Location.report_exception ppf e in
7373+ orig_source
7474+ in
7575+ let line1 =
7676+ List.map (fun id -> Printf.sprintf "open %s" (modname_of_id id)) deps
7777+ |> String.concat " "
7878+ in
7979+ let line1 = if line1 = "" then "" else line1 ^ ";;\n" in
8080+ Logs.debug (fun m -> m "Line 1: '%s'\n%!" line1);
8181+ Logs.debug (fun m -> m "Source: %s\n%!" src);
8282+ if is_mangled_broken orig_source src then (
8383+ Printf.printf "Warning: mangled source is broken\n%!";
8484+ Printf.printf "orig length: %d\n%!" (String.length orig_source);
8585+ Printf.printf "src length: %d\n%!" (String.length src));
8686+ (line1, src)
8787+8888+(** {2 PPX Preprocessing}
8989+9090+ Handles PPX rewriter registration and application. Supports:
9191+ - Old-style [Ast_mapper] PPXs (e.g., [Ppx_js.mapper] for js_of_ocaml)
9292+ - [ppx_deriving]-based PPXs (registered via [Ppx_deriving.register])
9393+ - Modern [ppxlib]-based PPXs (registered via [Ppxlib.Driver])
9494+9595+ The [Ppx_js.mapper] is registered by default to support js_of_ocaml
9696+ syntax extensions. Other PPXs can be dynamically loaded via [#require]. *)
9797+9898+module JsooTopPpx = struct
9999+ open Js_of_ocaml_compiler.Stdlib
100100+101101+ (** Old-style Ast_mapper rewriters *)
102102+ let ppx_rewriters = ref [ (fun _ -> Ppx_js.mapper) ]
103103+104104+ let () =
105105+ Ast_mapper.register_function :=
106106+ fun _ f -> ppx_rewriters := f :: !ppx_rewriters
107107+108108+ (** Apply old-style Ast_mapper rewriters *)
109109+ let apply_ast_mapper_rewriters_structure str =
110110+ let open Ast_mapper in
111111+ List.fold_right !ppx_rewriters ~init:str ~f:(fun ppx_rewriter str ->
112112+ let mapper = ppx_rewriter [] in
113113+ mapper.structure mapper str)
114114+115115+ let apply_ast_mapper_rewriters_signature sg =
116116+ let open Ast_mapper in
117117+ List.fold_right !ppx_rewriters ~init:sg ~f:(fun ppx_rewriter sg ->
118118+ let mapper = ppx_rewriter [] in
119119+ mapper.signature mapper sg)
120120+121121+ (** Apply ppx_deriving transformations using its mapper class.
122122+ This handles [@@deriving] attributes for dynamically loaded derivers. *)
123123+ let apply_ppx_deriving_structure str =
124124+ let mapper = new Ppx_deriving.mapper in
125125+ mapper#structure str
126126+127127+ let apply_ppx_deriving_signature sg =
128128+ let mapper = new Ppx_deriving.mapper in
129129+ mapper#signature sg
130130+131131+ (** Apply all PPX transformations in order:
132132+ 1. Old-style Ast_mapper (e.g., Ppx_js)
133133+ 2. ppx_deriving derivers
134134+ 3. ppxlib-based PPXs
135135+ Handles AST version conversion between compiler's Parsetree and ppxlib's internal AST. *)
136136+ let preprocess_structure str =
137137+ str
138138+ |> apply_ast_mapper_rewriters_structure
139139+ |> Ppxlib_ast.Selected_ast.of_ocaml Structure
140140+ |> apply_ppx_deriving_structure
141141+ |> Ppxlib.Driver.map_structure
142142+ |> Ppxlib_ast.Selected_ast.to_ocaml Structure
143143+144144+ let preprocess_signature sg =
145145+ sg
146146+ |> apply_ast_mapper_rewriters_signature
147147+ |> Ppxlib_ast.Selected_ast.of_ocaml Signature
148148+ |> apply_ppx_deriving_signature
149149+ |> Ppxlib.Driver.map_signature
150150+ |> Ppxlib_ast.Selected_ast.to_ocaml Signature
151151+152152+ let preprocess_phrase phrase =
153153+ let open Parsetree in
154154+ match phrase with
155155+ | Ptop_def str -> Ptop_def (preprocess_structure str)
156156+ | Ptop_dir _ as x -> x
157157+end
158158+159159+(** {2 Backend Signature}
160160+161161+ Platform-specific operations that must be provided by each backend
162162+ (WebWorker, Node.js, Unix). *)
163163+164164+module type S = sig
165165+ type findlib_t
166166+167167+ val capture : (unit -> 'a) -> unit -> captured * 'a
168168+ val create_file : name:string -> content:string -> unit
169169+ val sync_get : string -> string option
170170+ val async_get : string -> (string, [> `Msg of string ]) result Lwt.t
171171+ val import_scripts : string list -> unit
172172+ val init_function : string -> unit -> unit
173173+ val get_stdlib_dcs : string -> Toplevel_api_gen.dynamic_cmis list
174174+ val findlib_init : string -> findlib_t Lwt.t
175175+ val path : string
176176+177177+ val require :
178178+ bool -> findlib_t -> string list -> Toplevel_api_gen.dynamic_cmis list
179179+end
180180+181181+(** {2 Main Functor}
182182+183183+ The toplevel implementation, parameterized by backend operations. *)
184184+185185+module Make (S : S) = struct
186186+ (** {3 Global State}
187187+188188+ These are shared across all environments. *)
189189+190190+ let functions : (unit -> unit) list option ref = ref None
191191+ let requires : string list ref = ref []
192192+ let path : string option ref = ref None
193193+ let findlib_v : S.findlib_t Lwt.t option ref = ref None
194194+ let findlib_resolved : S.findlib_t option ref = ref None
195195+ let execution_allowed = ref true
196196+197197+ (** {3 Environment Management}
198198+199199+ Helper to resolve env_id string to an Environment.t.
200200+ Empty string means the default environment. *)
201201+202202+ let resolve_env env_id =
203203+ let id = if env_id = "" then Environment.default_id else env_id in
204204+ Environment.get_or_create id
205205+206206+ (** {3 Lexer Helpers} *)
207207+208208+ let refill_lexbuf s p ppf buffer len =
209209+ if !p = String.length s then 0
210210+ else
211211+ let len', nl =
212212+ try (String.index_from s !p '\n' - !p + 1, false)
213213+ with _ -> (String.length s - !p, true)
214214+ in
215215+ let len'' = min len len' in
216216+ String.blit s !p buffer 0 len'';
217217+ (match ppf with
218218+ | Some ppf ->
219219+ Format.fprintf ppf "%s" (Bytes.sub_string buffer 0 len'');
220220+ if nl then Format.pp_print_newline ppf ();
221221+ Format.pp_print_flush ppf ()
222222+ | None -> ());
223223+ p := !p + len'';
224224+ len''
225225+226226+ (** {3 Setup and Initialization} *)
227227+228228+ let exec' s =
229229+ S.capture
230230+ (fun () ->
231231+ let res : bool = Toploop.use_silently Format.std_formatter (String s) in
232232+ if not res then Format.eprintf "error while evaluating %s@." s)
233233+ ()
234234+235235+ (** {3 Custom Require Directive}
236236+237237+ Replaces the standard findlib #require with one that loads JavaScript
238238+ archives via importScripts. This is necessary because in js_of_ocaml,
239239+ we can't use Topdirs.dir_load to load .cma files - we need to load
240240+ .cma.js files via importScripts instead. *)
241241+242242+ let add_dynamic_cmis_sync dcs =
243243+ (* Synchronous version for #require directive.
244244+ Fetches and installs toplevel CMIs synchronously. *)
245245+ let furl = "file://" in
246246+ let l = String.length furl in
247247+ if String.length dcs.Toplevel_api_gen.dcs_url > l
248248+ && String.sub dcs.dcs_url 0 l = furl
249249+ then begin
250250+ let path = String.sub dcs.dcs_url l (String.length dcs.dcs_url - l) in
251251+ Topdirs.dir_directory path
252252+ end
253253+ else begin
254254+ (* Web URL - fetch CMIs synchronously *)
255255+ let fetch_sync filename =
256256+ let url = Filename.concat dcs.Toplevel_api_gen.dcs_url filename in
257257+ S.sync_get url
258258+ in
259259+ let path =
260260+ match !path with Some p -> p | None -> failwith "Path not set"
261261+ in
262262+ let to_cmi_filename name =
263263+ Printf.sprintf "%s.cmi" (String.uncapitalize_ascii name)
264264+ in
265265+ Logs.info (fun m -> m "Adding toplevel modules for dynamic cmis from %s" dcs.dcs_url);
266266+ Logs.info (fun m -> m " toplevel modules: %s"
267267+ (String.concat ", " dcs.dcs_toplevel_modules));
268268+ (* Fetch and create toplevel module CMIs *)
269269+ List.iter
270270+ (fun name ->
271271+ let filename = to_cmi_filename name in
272272+ match fetch_sync filename with
273273+ | Some content ->
274274+ let fs_name = Filename.(concat path filename) in
275275+ (try S.create_file ~name:fs_name ~content with _ -> ())
276276+ | None -> ())
277277+ dcs.dcs_toplevel_modules;
278278+ (* Install on-demand loader for prefixed modules *)
279279+ if dcs.dcs_file_prefixes <> [] then begin
280280+ let open Persistent_env.Persistent_signature in
281281+ let old_loader = !load in
282282+ load := fun ~allow_hidden ~unit_name ->
283283+ let filename = to_cmi_filename unit_name in
284284+ let fs_name = Filename.(concat path filename) in
285285+ if (not (Sys.file_exists fs_name))
286286+ && List.exists
287287+ (fun prefix -> String.starts_with ~prefix filename)
288288+ dcs.dcs_file_prefixes
289289+ then begin
290290+ Logs.info (fun m -> m "Fetching %s\n%!" filename);
291291+ match fetch_sync filename with
292292+ | Some content ->
293293+ (try S.create_file ~name:fs_name ~content with _ -> ())
294294+ | None -> ()
295295+ end;
296296+ old_loader ~allow_hidden ~unit_name
297297+ end
298298+ end
299299+300300+ let register_require_directive () =
301301+ let require_handler pkg =
302302+ Logs.info (fun m -> m "Custom #require: loading %s" pkg);
303303+ match !findlib_resolved with
304304+ | None ->
305305+ Format.eprintf "Error: findlib not initialized@."
306306+ | Some v ->
307307+ let cmi_only = not !execution_allowed in
308308+ let dcs_list = S.require cmi_only v [pkg] in
309309+ List.iter add_dynamic_cmis_sync dcs_list;
310310+ Logs.info (fun m -> m "Custom #require: %s loaded" pkg)
311311+ in
312312+ (* Replace the standard findlib #require directive with our custom one.
313313+ We use add_directive which will override the existing one. *)
314314+ let info = { Toploop.section = "Findlib"; doc = "Load a package (js_top_worker)" } in
315315+ Toploop.add_directive "require" (Toploop.Directive_string require_handler) info
316316+317317+ let setup functions () =
318318+ let stdout_buff = Buffer.create 100 in
319319+ let stderr_buff = Buffer.create 100 in
320320+321321+ let combine o =
322322+ Buffer.add_string stdout_buff o.stdout;
323323+ Buffer.add_string stderr_buff o.stderr
324324+ in
325325+326326+ let exec' s =
327327+ let o, () = exec' s in
328328+ combine o
329329+ in
330330+ Sys.interactive := false;
331331+332332+ Toploop.input_name := "//toplevel//";
333333+ let path =
334334+ match !path with Some p -> p | None -> failwith "Path not set"
335335+ in
336336+337337+ Topdirs.dir_directory path;
338338+339339+ Toploop.initialize_toplevel_env ();
340340+341341+ List.iter (fun f -> f ()) functions;
342342+ exec' "open Stdlib";
343343+ let header1 = Printf.sprintf " %s version %%s" "OCaml" in
344344+ exec' (Printf.sprintf "Format.printf \"%s@.\" Sys.ocaml_version;;" header1);
345345+ exec' "#enable \"pretty\";;";
346346+ exec' "#disable \"shortvar\";;";
347347+ Sys.interactive := true;
348348+ Logs.info (fun m -> m "Setup complete");
349349+ {
350350+ stdout = Buffer.contents stdout_buff;
351351+ stderr = Buffer.contents stderr_buff;
352352+ }
353353+354354+ (** {3 Output Helpers} *)
355355+356356+ let stdout_buff = Buffer.create 100
357357+ let stderr_buff = Buffer.create 100
358358+359359+ let buff_opt b =
360360+ match String.trim (Buffer.contents b) with "" -> None | s -> Some s
361361+362362+ let string_opt s = match String.trim s with "" -> None | s -> Some s
363363+364364+ let loc = function
365365+ | Syntaxerr.Error x -> Some (Syntaxerr.location_of_error x)
366366+ | Lexer.Error (_, loc)
367367+ | Typecore.Error (loc, _, _)
368368+ | Typetexp.Error (loc, _, _)
369369+ | Typeclass.Error (loc, _, _)
370370+ | Typemod.Error (loc, _, _)
371371+ | Typedecl.Error (loc, _)
372372+ | Translcore.Error (loc, _)
373373+ | Translclass.Error (loc, _)
374374+ | Translmod.Error (loc, _) ->
375375+ Some loc
376376+ | _ -> None
377377+378378+ (** {3 Phrase Execution}
379379+380380+ Executes OCaml phrases in an environment, capturing all output.
381381+ Handles parsing, PPX preprocessing, and execution with error reporting. *)
382382+383383+ let execute_in_env env phrase =
384384+ let code_buff = Buffer.create 100 in
385385+ let res_buff = Buffer.create 100 in
386386+ let pp_code = Format.formatter_of_buffer code_buff in
387387+ let pp_result = Format.formatter_of_buffer res_buff in
388388+ let highlighted = ref None in
389389+ let set_highlight loc =
390390+ let _file1, line1, col1 = Location.get_pos_info loc.Location.loc_start in
391391+ let _file2, line2, col2 = Location.get_pos_info loc.Location.loc_end in
392392+ highlighted := Some Toplevel_api_gen.{ line1; col1; line2; col2 }
393393+ in
394394+ Buffer.clear code_buff;
395395+ Buffer.clear res_buff;
396396+ Buffer.clear stderr_buff;
397397+ Buffer.clear stdout_buff;
398398+ let phrase =
399399+ let l = String.length phrase in
400400+ if l >= 2 && String.sub phrase (l - 2) 2 = ";;" then phrase
401401+ else phrase ^ ";;"
402402+ in
403403+ let o, () =
404404+ Environment.with_env env (fun () ->
405405+ S.capture
406406+ (fun () ->
407407+ let lb = Lexing.from_function (refill_lexbuf phrase (ref 0) (Some pp_code)) in
408408+ (try
409409+ while true do
410410+ try
411411+ let phr = !Toploop.parse_toplevel_phrase lb in
412412+ let phr = JsooTopPpx.preprocess_phrase phr in
413413+ ignore (Toploop.execute_phrase true pp_result phr : bool)
414414+ with
415415+ | End_of_file -> raise End_of_file
416416+ | x ->
417417+ (match loc x with Some l -> set_highlight l | None -> ());
418418+ Errors.report_error Format.err_formatter x
419419+ done
420420+ with End_of_file -> ());
421421+ flush_all ())
422422+ ())
423423+ in
424424+ let mime_vals = Mime_printer.get () in
425425+ Format.pp_print_flush pp_code ();
426426+ Format.pp_print_flush pp_result ();
427427+ Toplevel_api_gen.
428428+ {
429429+ stdout = string_opt o.stdout;
430430+ stderr = string_opt o.stderr;
431431+ sharp_ppf = buff_opt code_buff;
432432+ caml_ppf = buff_opt res_buff;
433433+ highlight = !highlighted;
434434+ mime_vals;
435435+ }
436436+437437+ (** {3 Incremental Phrase Execution}
438438+439439+ Executes OCaml phrases incrementally, calling a callback after each
440440+ phrase with its output and location. *)
441441+442442+ type phrase_output = {
443443+ loc : int;
444444+ caml_ppf : string option;
445445+ mime_vals : Toplevel_api_gen.mime_val list;
446446+ }
447447+448448+ let execute_in_env_incremental env phrase ~on_phrase_output =
449449+ let code_buff = Buffer.create 100 in
450450+ let res_buff = Buffer.create 100 in
451451+ let pp_code = Format.formatter_of_buffer code_buff in
452452+ let pp_result = Format.formatter_of_buffer res_buff in
453453+ let highlighted = ref None in
454454+ let set_highlight loc =
455455+ let _file1, line1, col1 = Location.get_pos_info loc.Location.loc_start in
456456+ let _file2, line2, col2 = Location.get_pos_info loc.Location.loc_end in
457457+ highlighted := Some Toplevel_api_gen.{ line1; col1; line2; col2 }
458458+ in
459459+ Buffer.clear code_buff;
460460+ Buffer.clear res_buff;
461461+ Buffer.clear stderr_buff;
462462+ Buffer.clear stdout_buff;
463463+ let phrase =
464464+ let l = String.length phrase in
465465+ if l >= 2 && String.sub phrase (l - 2) 2 = ";;" then phrase
466466+ else phrase ^ ";;"
467467+ in
468468+ let o, () =
469469+ Environment.with_env env (fun () ->
470470+ S.capture
471471+ (fun () ->
472472+ let lb = Lexing.from_function (refill_lexbuf phrase (ref 0) (Some pp_code)) in
473473+ (try
474474+ while true do
475475+ try
476476+ let phr = !Toploop.parse_toplevel_phrase lb in
477477+ let phr = JsooTopPpx.preprocess_phrase phr in
478478+ ignore (Toploop.execute_phrase true pp_result phr : bool);
479479+ (* Get location from phrase AST *)
480480+ let loc = match phr with
481481+ | Parsetree.Ptop_def ({ pstr_loc; _ } :: _) ->
482482+ pstr_loc.loc_end.pos_cnum
483483+ | Parsetree.Ptop_dir { pdir_loc; _ } ->
484484+ pdir_loc.loc_end.pos_cnum
485485+ | _ -> lb.lex_curr_p.pos_cnum
486486+ in
487487+ (* Flush and get current output *)
488488+ Format.pp_print_flush pp_result ();
489489+ let caml_ppf = buff_opt res_buff in
490490+ let mime_vals = Mime_printer.get () in
491491+ (* Call callback with phrase output *)
492492+ on_phrase_output { loc; caml_ppf; mime_vals };
493493+ (* Clear for next phrase *)
494494+ Buffer.clear res_buff
495495+ with
496496+ | End_of_file -> raise End_of_file
497497+ | x ->
498498+ (match loc x with Some l -> set_highlight l | None -> ());
499499+ Errors.report_error Format.err_formatter x
500500+ done
501501+ with End_of_file -> ());
502502+ flush_all ())
503503+ ())
504504+ in
505505+ (* Get any remaining mime_vals (shouldn't be any after last callback) *)
506506+ let mime_vals = Mime_printer.get () in
507507+ Format.pp_print_flush pp_code ();
508508+ Format.pp_print_flush pp_result ();
509509+ Toplevel_api_gen.
510510+ {
511511+ stdout = string_opt o.stdout;
512512+ stderr = string_opt o.stderr;
513513+ sharp_ppf = buff_opt code_buff;
514514+ caml_ppf = buff_opt res_buff;
515515+ highlight = !highlighted;
516516+ mime_vals;
517517+ }
518518+519519+ (** {3 Dynamic CMI Loading}
520520+521521+ Handles loading .cmi files on demand for packages that weren't
522522+ compiled into the worker. *)
523523+524524+ let filename_of_module unit_name =
525525+ Printf.sprintf "%s.cmi" (String.uncapitalize_ascii unit_name)
526526+527527+ let get_dirs () =
528528+ let { Load_path.visible; hidden } = Load_path.get_paths () in
529529+ visible @ hidden
530530+531531+ let reset_dirs () =
532532+ Ocaml_utils.Directory_content_cache.clear ();
533533+ let open Ocaml_utils.Load_path in
534534+ let dirs = get_dirs () in
535535+ reset ();
536536+ List.iter (fun p -> prepend_dir (Dir.create ~hidden:false p)) dirs
537537+538538+ let reset_dirs_comp () =
539539+ let open Load_path in
540540+ let dirs = get_dirs () in
541541+ reset ();
542542+ List.iter (fun p -> prepend_dir (Dir.create ~hidden:false p)) dirs
543543+544544+ let add_dynamic_cmis dcs =
545545+ let fetch filename =
546546+ let url = Filename.concat dcs.Toplevel_api_gen.dcs_url filename in
547547+ S.async_get url
548548+ in
549549+ let fetch_sync filename =
550550+ let url = Filename.concat dcs.Toplevel_api_gen.dcs_url filename in
551551+ S.sync_get url
552552+ in
553553+ let path =
554554+ match !path with Some p -> p | None -> failwith "Path not set"
555555+ in
556556+ let ( let* ) = Lwt.bind in
557557+ let* () =
558558+ Logs.info (fun m -> m "Adding toplevel modules for dynamic cmis from %s" dcs.dcs_url);
559559+ Logs.info (fun m -> m " toplevel modules: %s"
560560+ (String.concat ", " dcs.dcs_toplevel_modules));
561561+ Lwt_list.iter_p
562562+ (fun name ->
563563+ let filename = filename_of_module name in
564564+ let* r = fetch (filename_of_module name) in
565565+ let () =
566566+ match r with
567567+ | Ok content -> (
568568+ let name = Filename.(concat path filename) in
569569+ try S.create_file ~name ~content with _ -> ())
570570+ | Error _ -> ()
571571+ in
572572+ Lwt.return ())
573573+ dcs.dcs_toplevel_modules
574574+ in
575575+576576+ let new_load ~s ~old_loader ~allow_hidden ~unit_name =
577577+ (* Logs.info (fun m -> m "%s Loading: %s" s unit_name); *)
578578+ let filename = filename_of_module unit_name in
579579+580580+ let fs_name = Filename.(concat path filename) in
581581+ (* Check if it's already been downloaded. This will be the
582582+ case for all toplevel cmis. Also check whether we're supposed
583583+ to handle this cmi *)
584584+ (* if Sys.file_exists fs_name
585585+ then Logs.info (fun m -> m "Found: %s" fs_name)
586586+ else Logs.info (fun m -> m "No sign of %s locally" fs_name); *)
587587+ if
588588+ (not (Sys.file_exists fs_name))
589589+ && List.exists
590590+ (fun prefix -> String.starts_with ~prefix filename)
591591+ dcs.dcs_file_prefixes
592592+ then (
593593+ Logs.info (fun m -> m "Fetching %s\n%!" filename);
594594+ match fetch_sync filename with
595595+ | Some x ->
596596+ (try S.create_file ~name:fs_name ~content:x with _ -> ());
597597+ (* At this point we need to tell merlin that the dir contents
598598+ have changed *)
599599+ if s = "merl" then reset_dirs () else reset_dirs_comp ()
600600+ | None ->
601601+ Printf.eprintf "Warning: Expected to find cmi at: %s\n%!"
602602+ (Filename.concat dcs.Toplevel_api_gen.dcs_url filename));
603603+ if s = "merl" then reset_dirs () else reset_dirs_comp ();
604604+ old_loader ~allow_hidden ~unit_name
605605+ in
606606+ let furl = "file://" in
607607+ let l = String.length furl in
608608+ let () =
609609+ if String.length dcs.dcs_url > l && String.sub dcs.dcs_url 0 l = furl then
610610+ let path = String.sub dcs.dcs_url l (String.length dcs.dcs_url - l) in
611611+ Topdirs.dir_directory path
612612+ else
613613+ let open Persistent_env.Persistent_signature in
614614+ let old_loader = !load in
615615+ load := new_load ~s:"comp" ~old_loader;
616616+617617+ let open Ocaml_typing.Persistent_env.Persistent_signature in
618618+ let old_loader = !load in
619619+ load := new_load ~s:"merl" ~old_loader
620620+ in
621621+ Lwt.return ()
622622+623623+ (** {3 RPC Handlers}
624624+625625+ Functions that implement the toplevel RPC API. Each function returns
626626+ results in the [IdlM.ErrM] monad. *)
627627+628628+ let init (init_libs : Toplevel_api_gen.init_config) =
629629+ Lwt.catch
630630+ (fun () ->
631631+ Logs.info (fun m -> m "init()");
632632+ path := Some S.path;
633633+634634+ let findlib_path = Option.value ~default:"findlib_index" init_libs.findlib_index in
635635+ findlib_v := Some (S.findlib_init findlib_path);
636636+637637+ let stdlib_dcs =
638638+ match init_libs.stdlib_dcs with
639639+ | Some dcs -> dcs
640640+ | None -> "lib/ocaml/dynamic_cmis.json"
641641+ in
642642+ let* () =
643643+ match S.get_stdlib_dcs stdlib_dcs with
644644+ | [ dcs ] -> add_dynamic_cmis dcs
645645+ | _ -> Lwt.return ()
646646+ in
647647+ Clflags.no_check_prims := true;
648648+649649+ requires := init_libs.findlib_requires;
650650+ functions := Some [];
651651+ execution_allowed := init_libs.execute;
652652+653653+ (* Set up the toplevel environment *)
654654+ Logs.info (fun m -> m "init() finished");
655655+656656+ Lwt.return (Ok ()))
657657+ (fun e ->
658658+ Lwt.return
659659+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
660660+661661+ let setup env_id =
662662+ Lwt.catch
663663+ (fun () ->
664664+ let env = resolve_env env_id in
665665+ Logs.info (fun m -> m "setup() for env %s..." (Environment.id env));
666666+667667+ if Environment.is_setup env then (
668668+ Logs.info (fun m -> m "setup() already done for env %s" (Environment.id env));
669669+ Lwt.return
670670+ (Ok
671671+ Toplevel_api_gen.
672672+ {
673673+ stdout = None;
674674+ stderr = Some "Environment already set up";
675675+ sharp_ppf = None;
676676+ caml_ppf = None;
677677+ highlight = None;
678678+ mime_vals = [];
679679+ }))
680680+ else
681681+ let o =
682682+ Environment.with_env env (fun () ->
683683+ try
684684+ match !functions with
685685+ | Some l -> setup l ()
686686+ | None -> failwith "Error: toplevel has not been initialised"
687687+ with
688688+ | Persistent_env.Error e ->
689689+ Persistent_env.report_error Format.err_formatter e;
690690+ let err = Format.asprintf "%a" Persistent_env.report_error e in
691691+ failwith ("Error: " ^ err)
692692+ | Env.Error _ as exn ->
693693+ Location.report_exception Format.err_formatter exn;
694694+ let err = Format.asprintf "%a" Location.report_exception exn in
695695+ failwith ("Error: " ^ err))
696696+ in
697697+698698+ let* dcs =
699699+ match !findlib_v with
700700+ | Some v ->
701701+ let* v = v in
702702+ (* Store the resolved findlib value for use by #require directive *)
703703+ findlib_resolved := Some v;
704704+ (* Register our custom #require directive that uses findlibish *)
705705+ register_require_directive ();
706706+ Lwt.return (S.require (not !execution_allowed) v !requires)
707707+ | None -> Lwt.return []
708708+ in
709709+710710+ let* () = Lwt_list.iter_p add_dynamic_cmis dcs in
711711+712712+ Environment.mark_setup env;
713713+ Logs.info (fun m -> m "setup() finished for env %s" (Environment.id env));
714714+715715+ Lwt.return
716716+ (Ok
717717+ Toplevel_api_gen.
718718+ {
719719+ stdout = string_opt o.stdout;
720720+ stderr = string_opt o.stderr;
721721+ sharp_ppf = None;
722722+ caml_ppf = None;
723723+ highlight = None;
724724+ mime_vals = [];
725725+ }))
726726+ (fun e ->
727727+ Lwt.return
728728+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
729729+730730+ let handle_toplevel env stripped =
731731+ if String.length stripped < 2 || stripped.[0] <> '#' || stripped.[1] <> ' '
732732+ then (
733733+ Printf.eprintf
734734+ "Warning, ignoring toplevel block without a leading '# '.\n";
735735+ IdlM.ErrM.return
736736+ { Toplevel_api_gen.script = stripped; mime_vals = []; parts = [] })
737737+ else
738738+ let s = String.sub stripped 2 (String.length stripped - 2) in
739739+ let list = Ocamltop.parse_toplevel s in
740740+ let buf = Buffer.create 1024 in
741741+ let mime_vals =
742742+ List.fold_left
743743+ (fun acc (phr, _junk, _output) ->
744744+ let new_output = execute_in_env env phr in
745745+ Printf.bprintf buf "# %s\n" phr;
746746+ let r =
747747+ Option.to_list new_output.stdout
748748+ @ Option.to_list new_output.stderr
749749+ @ Option.to_list new_output.caml_ppf
750750+ in
751751+ let r =
752752+ List.concat_map (fun l -> Astring.String.cuts ~sep:"\n" l) r
753753+ in
754754+ List.iter (fun x -> Printf.bprintf buf " %s\n" x) r;
755755+ let mime_vals = new_output.mime_vals in
756756+ acc @ mime_vals)
757757+ [] list
758758+ in
759759+ let content_txt = Buffer.contents buf in
760760+ let content_txt =
761761+ String.sub content_txt 0 (String.length content_txt - 1)
762762+ in
763763+ let result =
764764+ { Toplevel_api_gen.script = content_txt; mime_vals; parts = [] }
765765+ in
766766+ IdlM.ErrM.return result
767767+768768+ let exec_toplevel env_id (phrase : string) =
769769+ let env = resolve_env env_id in
770770+ try handle_toplevel env phrase
771771+ with e ->
772772+ Logs.info (fun m -> m "Error: %s" (Printexc.to_string e));
773773+ IdlM.ErrM.return_err
774774+ (Toplevel_api_gen.InternalError (Printexc.to_string e))
775775+776776+ let execute env_id (phrase : string) =
777777+ Logs.info (fun m -> m "execute() for env_id=%s" env_id);
778778+ let env = resolve_env env_id in
779779+ let result = execute_in_env env phrase in
780780+ Logs.info (fun m -> m "execute() done for env_id=%s" env_id);
781781+ IdlM.ErrM.return result
782782+783783+ let execute_incremental env_id (phrase : string) ~on_phrase_output =
784784+ Logs.info (fun m -> m "execute_incremental() for env_id=%s" env_id);
785785+ let env = resolve_env env_id in
786786+ let result = execute_in_env_incremental env phrase ~on_phrase_output in
787787+ Logs.info (fun m -> m "execute_incremental() done for env_id=%s" env_id);
788788+ IdlM.ErrM.return result
789789+790790+ (** {3 Merlin Integration}
791791+792792+ Code intelligence features powered by Merlin: completion, type info,
793793+ error diagnostics. *)
794794+795795+ let config () =
796796+ let path =
797797+ match !path with Some p -> p | None -> failwith "Path not set"
798798+ in
799799+ let initial = Merlin_kernel.Mconfig.initial in
800800+ { initial with merlin = { initial.merlin with stdlib = Some path } }
801801+802802+ let make_pipeline source = Merlin_kernel.Mpipeline.make (config ()) source
803803+804804+ let wdispatch source query =
805805+ let pipeline = make_pipeline source in
806806+ Merlin_kernel.Mpipeline.with_pipeline pipeline @@ fun () ->
807807+ Query_commands.dispatch pipeline query
808808+809809+ (** Completion prefix extraction, adapted from ocaml-lsp-server. *)
810810+ module Completion = struct
811811+ open Merlin_utils
812812+ open Std
813813+ open Merlin_kernel
814814+815815+ (* Prefixing code from ocaml-lsp-server *)
816816+ let rfindi =
817817+ let rec loop s ~f i =
818818+ if i < 0 then None
819819+ else if f (String.unsafe_get s i) then Some i
820820+ else loop s ~f (i - 1)
821821+ in
822822+ fun ?from s ~f ->
823823+ let from =
824824+ let len = String.length s in
825825+ match from with
826826+ | None -> len - 1
827827+ | Some i ->
828828+ if i > len - 1 then
829829+ raise @@ Invalid_argument "rfindi: invalid from"
830830+ else i
831831+ in
832832+ loop s ~f from
833833+834834+ let lsplit2 s ~on =
835835+ match String.index_opt s on with
836836+ | None -> None
837837+ | Some i ->
838838+ let open StdLabels.String in
839839+ Some (sub s ~pos:0 ~len:i, sub s ~pos:(i + 1) ~len:(length s - i - 1))
840840+841841+ (** @see <https://ocaml.org/manual/lex.html> reference *)
842842+ let prefix_of_position ?(short_path = false) source position =
843843+ match Msource.text source with
844844+ | "" -> ""
845845+ | text ->
846846+ let from =
847847+ let (`Offset index) = Msource.get_offset source position in
848848+ min (String.length text - 1) (index - 1)
849849+ in
850850+ let pos =
851851+ let should_terminate = ref false in
852852+ let has_seen_dot = ref false in
853853+ let is_prefix_char c =
854854+ if !should_terminate then false
855855+ else
856856+ match c with
857857+ | 'a' .. 'z'
858858+ | 'A' .. 'Z'
859859+ | '0' .. '9'
860860+ | '\'' | '_'
861861+ (* Infix function characters *)
862862+ | '$' | '&' | '*' | '+' | '-' | '/' | '=' | '>' | '@' | '^'
863863+ | '!' | '?' | '%' | '<' | ':' | '~' | '#' ->
864864+ true
865865+ | '`' ->
866866+ if !has_seen_dot then false
867867+ else (
868868+ should_terminate := true;
869869+ true)
870870+ | '.' ->
871871+ has_seen_dot := true;
872872+ not short_path
873873+ | _ -> false
874874+ in
875875+ rfindi text ~from ~f:(fun c -> not (is_prefix_char c))
876876+ in
877877+ let pos = match pos with None -> 0 | Some pos -> pos + 1 in
878878+ let len = from - pos + 1 in
879879+ let reconstructed_prefix = StdLabels.String.sub text ~pos ~len in
880880+ (* if we reconstructed [~f:ignore] or [?f:ignore], we should take only
881881+ [ignore], so: *)
882882+ if
883883+ String.is_prefixed ~by:"~" reconstructed_prefix
884884+ || String.is_prefixed ~by:"?" reconstructed_prefix
885885+ then
886886+ match lsplit2 reconstructed_prefix ~on:':' with
887887+ | Some (_, s) -> s
888888+ | None -> reconstructed_prefix
889889+ else reconstructed_prefix
890890+891891+ let at_pos source position =
892892+ let prefix = prefix_of_position source position in
893893+ let (`Offset to_) = Msource.get_offset source position in
894894+ let from =
895895+ to_
896896+ - String.length (prefix_of_position ~short_path:true source position)
897897+ in
898898+ if prefix = "" then None
899899+ else
900900+ let query =
901901+ Query_protocol.Complete_prefix (prefix, position, [], true, true)
902902+ in
903903+ Some (from, to_, wdispatch source query)
904904+ end
905905+906906+ let complete_prefix env_id id deps is_toplevel source position =
907907+ let _env = resolve_env env_id in (* Reserved for future use *)
908908+ try
909909+ Logs.info (fun m -> m "completing for id: %s" (match id with Some x -> x | None -> "(none)"));
910910+911911+ let line1, src = mangle_toplevel is_toplevel source deps in
912912+ Logs.info (fun m -> m "line1: '%s' (length: %d)" line1 (String.length line1));
913913+ Logs.info (fun m -> m "src: '%s' (length: %d)" src (String.length src));
914914+ let src = line1 ^ src in
915915+ let source = Merlin_kernel.Msource.make src in
916916+ let map_kind :
917917+ [ `Value
918918+ | `Constructor
919919+ | `Variant
920920+ | `Label
921921+ | `Module
922922+ | `Modtype
923923+ | `Type
924924+ | `MethodCall
925925+ | `Keyword ] ->
926926+ Toplevel_api_gen.kind_ty = function
927927+ | `Value -> Value
928928+ | `Constructor -> Constructor
929929+ | `Variant -> Variant
930930+ | `Label -> Label
931931+ | `Module -> Module
932932+ | `Modtype -> Modtype
933933+ | `Type -> Type
934934+ | `MethodCall -> MethodCall
935935+ | `Keyword -> Keyword
936936+ in
937937+ let position =
938938+ match position with
939939+ | Toplevel_api_gen.Start -> `Offset (String.length line1)
940940+ | Offset x -> `Offset (x + String.length line1)
941941+ | Logical (x, y) -> `Logical (x + 1, y)
942942+ | End -> `End
943943+ in
944944+945945+ (match position with
946946+ | `Offset x ->
947947+ let first_char = String.sub src (x-1) 1 in
948948+ Logs.info (fun m -> m "complete after offset: %s" first_char)
949949+ | _ -> ());
950950+951951+ match Completion.at_pos source position with
952952+ | Some (from, to_, compl) ->
953953+ let entries =
954954+ List.map
955955+ (fun (entry : Query_protocol.Compl.entry) ->
956956+ {
957957+ Toplevel_api_gen.name = entry.name;
958958+ kind = map_kind entry.kind;
959959+ desc = entry.desc;
960960+ info = entry.info;
961961+ deprecated = entry.deprecated;
962962+ })
963963+ compl.entries
964964+ in
965965+ let l1l = String.length line1 in
966966+ IdlM.ErrM.return { Toplevel_api_gen.from = from - l1l; to_ = to_ - l1l; entries }
967967+ | None ->
968968+ IdlM.ErrM.return { Toplevel_api_gen.from = 0; to_ = 0; entries = [] }
969969+ with e ->
970970+ Logs.info (fun m -> m "Error: %s" (Printexc.to_string e));
971971+ IdlM.ErrM.return_err
972972+ (Toplevel_api_gen.InternalError (Printexc.to_string e))
973973+974974+ let add_cmi execution_env id deps source =
975975+ Logs.info (fun m -> m "add_cmi");
976976+ let dep_modules = List.map modname_of_id deps in
977977+ let loc = Location.none in
978978+ let path =
979979+ match !path with Some p -> p | None -> failwith "Path not set"
980980+ in
981981+ let filename = modname_of_id id |> String.uncapitalize_ascii in
982982+ let prefix = Printf.sprintf "%s/%s" path filename in
983983+ let filename = Printf.sprintf "%s.ml" prefix in
984984+ Logs.info (fun m -> m "prefix: %s" prefix);
985985+ let oc = open_out filename in
986986+ Printf.fprintf oc "%s" source;
987987+ close_out oc;
988988+ (try Sys.remove (prefix ^ ".cmi") with Sys_error _ -> ());
989989+ let unit_info = Unit_info.make ~source_file:filename Impl prefix in
990990+ try
991991+ let store = Local_store.fresh () in
992992+ Local_store.with_store store (fun () ->
993993+ Local_store.reset ();
994994+ let env =
995995+ Typemod.initial_env ~loc ~initially_opened_module:(Some "Stdlib")
996996+ ~open_implicit_modules:dep_modules
997997+ in
998998+ let lexbuf = Lexing.from_string source in
999999+ let ast = Parse.implementation lexbuf in
10001000+ Logs.info (fun m -> m "About to type_implementation");
10011001+ let _ = Typemod.type_implementation unit_info env ast in
10021002+ let b = Sys.file_exists (prefix ^ ".cmi") in
10031003+ Environment.remove_failed_cell execution_env id;
10041004+ Logs.info (fun m -> m "file_exists: %s = %b" (prefix ^ ".cmi") b));
10051005+ Ocaml_typing.Cmi_cache.clear ()
10061006+ with
10071007+ | Env.Error _ as exn ->
10081008+ Logs.err (fun m -> m "Env.Error: %a" Location.report_exception exn);
10091009+ Environment.add_failed_cell execution_env id;
10101010+ ()
10111011+ | exn ->
10121012+ let s = Printexc.to_string exn in
10131013+ Logs.err (fun m -> m "Error in add_cmi: %s" s);
10141014+ Logs.err (fun m -> m "Backtrace: %s" (Printexc.get_backtrace ()));
10151015+ let ppf = Format.err_formatter in
10161016+ let _ = Location.report_exception ppf exn in
10171017+ Environment.add_failed_cell execution_env id;
10181018+ ()
10191019+10201020+ let map_pos line1 pos =
10211021+ (* Only subtract line number when there's actually a prepended line *)
10221022+ let line_offset = if line1 = "" then 0 else 1 in
10231023+ Lexing.
10241024+ {
10251025+ pos with
10261026+ pos_bol = pos.pos_bol - String.length line1;
10271027+ pos_lnum = pos.pos_lnum - line_offset;
10281028+ pos_cnum = pos.pos_cnum - String.length line1;
10291029+ }
10301030+10311031+ let map_loc line1 (loc : Ocaml_parsing.Location.t) =
10321032+ {
10331033+ loc with
10341034+ Ocaml_utils.Warnings.loc_start = map_pos line1 loc.loc_start;
10351035+ Ocaml_utils.Warnings.loc_end = map_pos line1 loc.loc_end;
10361036+ }
10371037+10381038+ let query_errors env_id id deps is_toplevel orig_source =
10391039+ let execution_env = resolve_env env_id in
10401040+ try
10411041+ let deps =
10421042+ List.filter (fun dep -> not (Environment.is_cell_failed execution_env dep)) deps
10431043+ in
10441044+ let line1, src = mangle_toplevel is_toplevel orig_source deps in
10451045+ let full_source = line1 ^ src in
10461046+ let source = Merlin_kernel.Msource.make full_source in
10471047+ let query =
10481048+ Query_protocol.Errors { lexing = true; parsing = true; typing = true }
10491049+ in
10501050+ let errors =
10511051+ wdispatch source query
10521052+ |> StdLabels.List.filter_map
10531053+ ~f:(fun
10541054+ (Ocaml_parsing.Location.{ kind; main = _; sub; source; _ } as
10551055+ error)
10561056+ ->
10571057+ let of_sub sub =
10581058+ Ocaml_parsing.Location.print_sub_msg Format.str_formatter sub;
10591059+ String.trim (Format.flush_str_formatter ())
10601060+ in
10611061+ let loc =
10621062+ map_loc line1 (Ocaml_parsing.Location.loc_of_report error)
10631063+ in
10641064+ let main =
10651065+ Format.asprintf "@[%a@]" Ocaml_parsing.Location.print_main
10661066+ error
10671067+ |> String.trim
10681068+ in
10691069+ if loc.loc_start.pos_lnum = 0 then None
10701070+ else
10711071+ Some
10721072+ {
10731073+ Toplevel_api_gen.kind;
10741074+ loc;
10751075+ main;
10761076+ sub = StdLabels.List.map ~f:of_sub sub;
10771077+ source;
10781078+ })
10791079+ in
10801080+ (* Only track cell CMIs when id is provided (notebook mode) *)
10811081+ (match id with
10821082+ | Some cell_id ->
10831083+ if List.length errors = 0 then add_cmi execution_env cell_id deps src
10841084+ else Environment.add_failed_cell execution_env cell_id
10851085+ | None -> ());
10861086+10871087+ (* Logs.info (fun m -> m "Got to end"); *)
10881088+ IdlM.ErrM.return errors
10891089+ with e ->
10901090+ Logs.info (fun m -> m "Error: %s" (Printexc.to_string e));
10911091+ IdlM.ErrM.return_err
10921092+ (Toplevel_api_gen.InternalError (Printexc.to_string e))
10931093+10941094+ let type_enclosing env_id _id deps is_toplevel orig_source position =
10951095+ let execution_env = resolve_env env_id in
10961096+ try
10971097+ let deps =
10981098+ List.filter (fun dep -> not (Environment.is_cell_failed execution_env dep)) deps
10991099+ in
11001100+ let line1, src = mangle_toplevel is_toplevel orig_source deps in
11011101+ let src = line1 ^ src in
11021102+ let position =
11031103+ match position with
11041104+ | Toplevel_api_gen.Start -> `Start
11051105+ | Offset x -> `Offset (x + String.length line1)
11061106+ | Logical (x, y) -> `Logical (x + 1, y)
11071107+ | End -> `End
11081108+ in
11091109+ let source = Merlin_kernel.Msource.make src in
11101110+ let query = Query_protocol.Type_enclosing (None, position, None) in
11111111+ let enclosing = wdispatch source query in
11121112+ let map_index_or_string = function
11131113+ | `Index i -> Toplevel_api_gen.Index i
11141114+ | `String s -> String s
11151115+ in
11161116+ let map_tail_position = function
11171117+ | `No -> Toplevel_api_gen.No
11181118+ | `Tail_position -> Tail_position
11191119+ | `Tail_call -> Tail_call
11201120+ in
11211121+ let enclosing =
11221122+ List.map
11231123+ (fun (x, y, z) ->
11241124+ (map_loc line1 x, map_index_or_string y, map_tail_position z))
11251125+ enclosing
11261126+ in
11271127+ IdlM.ErrM.return enclosing
11281128+ with e ->
11291129+ Logs.info (fun m -> m "Error: %s" (Printexc.to_string e));
11301130+ IdlM.ErrM.return_err
11311131+ (Toplevel_api_gen.InternalError (Printexc.to_string e))
11321132+11331133+ (** {3 Environment Management RPCs} *)
11341134+11351135+ let create_env env_id =
11361136+ Lwt.catch
11371137+ (fun () ->
11381138+ Logs.info (fun m -> m "create_env(%s)" env_id);
11391139+ let _env = Environment.create env_id in
11401140+ Lwt.return (Ok ()))
11411141+ (fun e ->
11421142+ Lwt.return
11431143+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
11441144+11451145+ let destroy_env env_id =
11461146+ Lwt.catch
11471147+ (fun () ->
11481148+ Logs.info (fun m -> m "destroy_env(%s)" env_id);
11491149+ Environment.destroy env_id;
11501150+ Lwt.return (Ok ()))
11511151+ (fun e ->
11521152+ Lwt.return
11531153+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
11541154+11551155+ let list_envs () =
11561156+ Lwt.catch
11571157+ (fun () ->
11581158+ let envs = Environment.list () in
11591159+ Logs.info (fun m -> m "list_envs() -> [%s]" (String.concat ", " envs));
11601160+ Lwt.return (Ok envs))
11611161+ (fun e ->
11621162+ Lwt.return
11631163+ (Error (Toplevel_api_gen.InternalError (Printexc.to_string e))))
11641164+end
+64
js_top_worker/lib/jslib.ml
···11+let log fmt =
22+ Format.kasprintf
33+ (fun s -> Js_of_ocaml.(Console.console##log (Js.string s)))
44+ fmt
55+66+let map_url url =
77+ let open Js_of_ocaml in
88+ let global_rel_url =
99+ let x : Js.js_string Js.t option =
1010+ Js.Unsafe.js_expr "globalThis.__global_rel_url" |> Js.Optdef.to_option
1111+ in
1212+ Option.map Js.to_string x
1313+ in
1414+ match global_rel_url with
1515+ | Some rel ->
1616+ (* If url starts with /, it's relative to server root - just use the scheme/host *)
1717+ if String.length url > 0 && url.[0] = '/' then
1818+ (* Extract scheme://host from rel and append url *)
1919+ match String.index_opt rel ':' with
2020+ | Some colon_idx ->
2121+ let after_scheme = colon_idx + 3 in (* skip "://" *)
2222+ (match String.index_from_opt rel after_scheme '/' with
2323+ | Some slash_idx -> String.sub rel 0 slash_idx ^ url
2424+ | None -> rel ^ url)
2525+ | None -> url
2626+ else
2727+ Filename.concat rel url
2828+ | None -> url
2929+3030+let sync_get url =
3131+ let open Js_of_ocaml in
3232+ let url = map_url url in
3333+ Console.console##log (Js.string ("Fetching: " ^ url));
3434+ let x = XmlHttpRequest.create () in
3535+ x##.responseType := Js.string "arraybuffer";
3636+ x##_open (Js.string "GET") (Js.string url) Js._false;
3737+ x##send Js.null;
3838+ match x##.status with
3939+ | 200 ->
4040+ Js.Opt.case
4141+ (File.CoerceTo.arrayBuffer x##.response)
4242+ (fun () ->
4343+ Console.console##log (Js.string "Failed to receive file");
4444+ None)
4545+ (fun b -> Some (Typed_array.String.of_arrayBuffer b))
4646+ | _ -> None
4747+4848+let async_get url =
4949+ let ( let* ) = Lwt.bind in
5050+ let open Js_of_ocaml in
5151+ let url = map_url url in
5252+ Console.console##log (Js.string ("Fetching: " ^ url));
5353+ let* frame =
5454+ Js_of_ocaml_lwt.XmlHttpRequest.perform_raw ~response_type:ArrayBuffer url
5555+ in
5656+ match frame.code with
5757+ | 200 ->
5858+ Lwt.return
5959+ (Js.Opt.case frame.content
6060+ (fun () -> Error (`Msg "Failed to receive file"))
6161+ (fun b -> Ok (Typed_array.String.of_arrayBuffer b)))
6262+ | _ ->
6363+ Lwt.return
6464+ (Error (`Msg (Printf.sprintf "Failed to fetch %s: %d" url frame.code)))
+38
js_top_worker/lib/ocamltop.ml
···11+let refill_lexbuf s p buffer len =
22+ if !p = String.length s then 0
33+ else
44+ let len' =
55+ try String.index_from s !p '\n' - !p + 1 with _ -> String.length s - !p
66+ in
77+ let len'' = min len len' in
88+ String.blit s !p buffer 0 len'';
99+ p := !p + len'';
1010+ len''
1111+1212+let fallback_parse_toplevel s =
1313+ Printf.printf "fallback parser\n%!";
1414+ let lexbuf = Lexing.from_string s in
1515+ let rec loop pos =
1616+ let _phr = Toplexer.fallback_expression lexbuf in
1717+ Printf.printf "Got phrase\n%!";
1818+ let new_pos = Lexing.lexeme_end lexbuf in
1919+ let phr = String.sub s pos (new_pos - pos) in
2020+ let junk, (cont, output) = Toplexer.entry lexbuf in
2121+ let new_pos = Lexing.lexeme_end lexbuf in
2222+ if cont then (phr, junk, output) :: loop new_pos
2323+ else [ (phr, junk, output) ]
2424+ in
2525+ loop 0
2626+2727+let parse_toplevel s =
2828+ let lexbuf = Lexing.from_string s in
2929+ let rec loop pos =
3030+ let _phr = !Toploop.parse_toplevel_phrase lexbuf in
3131+ let new_pos = Lexing.lexeme_end lexbuf in
3232+ let phr = String.sub s pos (new_pos - pos) in
3333+ let junk, (cont, output) = Toplexer.entry lexbuf in
3434+ let new_pos = Lexing.lexeme_end lexbuf in
3535+ if cont then (phr, junk, output) :: loop new_pos
3636+ else [ (phr, junk, output) ]
3737+ in
3838+ loop 0
···11+(** Minimal test worker for browser client tests.
22+33+ This is a simplified worker that doesn't require dynamic package loading,
44+ making it suitable for isolated browser testing. *)
55+66+open Js_top_worker_rpc
77+open Js_top_worker
88+module Server = Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
99+1010+let server process e =
1111+ let _, id, call = Jsonrpc.version_id_and_call_of_string e in
1212+ Lwt.bind (process call) (fun response ->
1313+ let rtxt = Jsonrpc.string_of_response ~id response in
1414+ Js_of_ocaml.Worker.post_message (Js_of_ocaml.Js.string rtxt);
1515+ Lwt.return ())
1616+1717+module S : Impl.S = struct
1818+ type findlib_t = unit
1919+2020+ let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
2121+ fun f () ->
2222+ let stdout_buff = Buffer.create 1024 in
2323+ let stderr_buff = Buffer.create 1024 in
2424+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
2525+ Js_of_ocaml.Sys_js.set_channel_flusher stderr (Buffer.add_string stderr_buff);
2626+ let x = f () in
2727+ ({ Impl.stdout = Buffer.contents stdout_buff;
2828+ stderr = Buffer.contents stderr_buff }, x)
2929+3030+ let sync_get _ = None
3131+ let async_get _ = Lwt.return (Error (`Msg "Not implemented"))
3232+ let create_file = Js_of_ocaml.Sys_js.create_file
3333+ let get_stdlib_dcs _ = []
3434+ let import_scripts _ = ()
3535+ let findlib_init _ = Lwt.return ()
3636+ let require _ () _ = []
3737+ let init_function _ () = ()
3838+ let path = "/static/cmis"
3939+end
4040+4141+module M = Impl.Make (S)
4242+4343+let run () =
4444+ let open Js_of_ocaml in
4545+ let open M in
4646+ Console.console##log (Js.string "Test worker starting...");
4747+ Server.init (Impl.IdlM.T.lift init);
4848+ Server.create_env (Impl.IdlM.T.lift create_env);
4949+ Server.destroy_env (Impl.IdlM.T.lift destroy_env);
5050+ Server.list_envs (Impl.IdlM.T.lift list_envs);
5151+ Server.setup (Impl.IdlM.T.lift setup);
5252+ Server.exec execute;
5353+ Server.complete_prefix complete_prefix;
5454+ Server.query_errors query_errors;
5555+ Server.type_enclosing type_enclosing;
5656+ Server.exec_toplevel exec_toplevel;
5757+ let rpc_fn = Impl.IdlM.server Server.implementation in
5858+ Worker.set_onmessage (fun x ->
5959+ let s = Js.to_string x in
6060+ ignore (server rpc_fn s));
6161+ Console.console##log (Js.string "Test worker ready")
6262+6363+let () = run ()
+875
js_top_worker/test/cram/directives.t/run.t
···11+Comprehensive test suite for OCaml toplevel directives.
22+Most tests will initially FAIL - this is TDD!
33+44+References:
55+- OCaml Manual: https://ocaml.org/manual/5.4/toplevel.html
66+- Findlib: http://projects.camlcity.org/projects/dl/findlib-1.7.1/doc/ref-html/lib/Topfind.html
77+88+ $ export OCAMLRUNPARAM=b
99+ $ export JS_TOP_WORKER_SOCK="/tmp/js_top_worker_directives_$$.sock"
1010+ $ WORKER_PID=$(sh ../start_worker.sh)
1111+ $ unix_client init '{ findlib_requires:[], execute: true }'
1212+ N
1313+ $ unix_client setup ''
1414+ {mime_vals:[];stderr:S(error while evaluating #enable "pretty";;
1515+ error while evaluating #disable "shortvar";;);stdout:S(OCaml version 5.4.0
1616+ Unknown directive enable.
1717+ Unknown directive disable.)}
1818+1919+==============================================
2020+SECTION 1: Basic Code Execution (Baseline)
2121+==============================================
2222+2323+ $ unix_client exec_toplevel '' '# 1 + 2;;'
2424+ {mime_vals:[];parts:[];script:S(# 1 + 2;;
2525+ - : int = 3)}
2626+2727+ $ unix_client exec_toplevel '' '# let x = 42;;'
2828+ {mime_vals:[];parts:[];script:S(# let x = 42;;
2929+ val x : int = 42)}
3030+3131+==============================================
3232+SECTION 2: #show Directives (Environment Query)
3333+==============================================
3434+3535+Define some types and values to query:
3636+3737+ $ unix_client exec_toplevel '' '# type point = { x: float; y: float };;'
3838+ {mime_vals:[];parts:[];script:S(# type point = { x: float; y: float };;
3939+ type point = { x : float; y : float; })}
4040+4141+ $ unix_client exec_toplevel '' '# let origin = { x = 0.0; y = 0.0 };;'
4242+ {mime_vals:[];parts:[];script:S(# let origin = { x = 0.0; y = 0.0 };;
4343+ val origin : point = {x = 0.; y = 0.})}
4444+4545+ $ unix_client exec_toplevel '' '# module MyMod = struct type t = int let zero = 0 end;;'
4646+ {mime_vals:[];parts:[];script:S(# module MyMod = struct type t = int let zero = 0 end;;
4747+ module MyMod : sig type t = int val zero : int end)}
4848+4949+ $ unix_client exec_toplevel '' '# exception My_error of string;;'
5050+ {mime_vals:[];parts:[];script:S(# exception My_error of string;;
5151+ exception My_error of string)}
5252+5353+Test #show directive:
5454+5555+ $ unix_client exec_toplevel '' '# #show point;;'
5656+ {mime_vals:[];parts:[];script:S(# #show point;;
5757+ type point = { x : float; y : float; })}
5858+5959+ $ unix_client exec_toplevel '' '# #show origin;;'
6060+ {mime_vals:[];parts:[];script:S(# #show origin;;
6161+ val origin : point)}
6262+6363+ $ unix_client exec_toplevel '' '# #show MyMod;;'
6464+ {mime_vals:[];parts:[];script:S(# #show MyMod;;
6565+ module MyMod : sig type t = int val zero : int end)}
6666+6767+ $ unix_client exec_toplevel '' '# #show My_error;;'
6868+ {mime_vals:[];parts:[];script:S(# #show My_error;;
6969+ exception My_error of string)}
7070+7171+Test #show_type directive:
7272+7373+ $ unix_client exec_toplevel '' '# #show_type point;;'
7474+ {mime_vals:[];parts:[];script:S(# #show_type point;;
7575+ type point = { x : float; y : float; })}
7676+7777+ $ unix_client exec_toplevel '' '# #show_type list;;'
7878+ {mime_vals:[];parts:[];script:S(# #show_type list;;
7979+ type 'a list = [] | (::) of 'a * 'a list)}
8080+8181+Test #show_val directive:
8282+8383+ $ unix_client exec_toplevel '' '# #show_val origin;;'
8484+ {mime_vals:[];parts:[];script:S(# #show_val origin;;
8585+ val origin : point)}
8686+8787+ $ unix_client exec_toplevel '' '# #show_val List.map;;'
8888+ {mime_vals:[];parts:[];script:S(# #show_val List.map;;
8989+ val map : ('a -> 'b) -> 'a list -> 'b list)}
9090+9191+Test #show_module directive:
9292+9393+ $ unix_client exec_toplevel '' '# #show_module List;;'
9494+ {mime_vals:[];parts:[];script:S(# #show_module List;;
9595+ module List :
9696+ sig
9797+ type 'a t = 'a list = [] | (::) of 'a * 'a list
9898+ val length : 'a list -> int
9999+ val compare_lengths : 'a list -> 'b list -> int
100100+ val compare_length_with : 'a list -> int -> int
101101+ val is_empty : 'a list -> bool
102102+ val cons : 'a -> 'a list -> 'a list
103103+ val singleton : 'a -> 'a list
104104+ val hd : 'a list -> 'a
105105+ val tl : 'a list -> 'a list
106106+ val nth : 'a list -> int -> 'a
107107+ val nth_opt : 'a list -> int -> 'a option
108108+ val rev : 'a list -> 'a list
109109+ val init : int -> (int -> 'a) -> 'a list
110110+ val append : 'a list -> 'a list -> 'a list
111111+ val rev_append : 'a list -> 'a list -> 'a list
112112+ val concat : 'a list list -> 'a list
113113+ val flatten : 'a list list -> 'a list
114114+ val equal : ('a -> 'a -> bool) -> 'a list -> 'a list -> bool
115115+ val compare : ('a -> 'a -> int) -> 'a list -> 'a list -> int
116116+ val iter : ('a -> unit) -> 'a list -> unit
117117+ val iteri : (int -> 'a -> unit) -> 'a list -> unit
118118+ val map : ('a -> 'b) -> 'a list -> 'b list
119119+ val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list
120120+ val rev_map : ('a -> 'b) -> 'a list -> 'b list
121121+ val filter_map : ('a -> 'b option) -> 'a list -> 'b list
122122+ val concat_map : ('a -> 'b list) -> 'a list -> 'b list
123123+ val fold_left_map :
124124+ ('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a list -> 'acc * 'b list
125125+ val fold_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a list -> 'acc
126126+ val fold_right : ('a -> 'acc -> 'acc) -> 'a list -> 'acc -> 'acc
127127+ val iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit
128128+ val map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
129129+ val rev_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
130130+ val fold_left2 :
131131+ ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a list -> 'b list -> 'acc
132132+ val fold_right2 :
133133+ ('a -> 'b -> 'acc -> 'acc) -> 'a list -> 'b list -> 'acc -> 'acc
134134+ val for_all : ('a -> bool) -> 'a list -> bool
135135+ val exists : ('a -> bool) -> 'a list -> bool
136136+ val for_all2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
137137+ val exists2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
138138+ val mem : 'a -> 'a list -> bool
139139+ val memq : 'a -> 'a list -> bool
140140+ val find : ('a -> bool) -> 'a list -> 'a
141141+ val find_opt : ('a -> bool) -> 'a list -> 'a option
142142+ val find_index : ('a -> bool) -> 'a list -> int option
143143+ val find_map : ('a -> 'b option) -> 'a list -> 'b option
144144+ val find_mapi : (int -> 'a -> 'b option) -> 'a list -> 'b option
145145+ val filter : ('a -> bool) -> 'a list -> 'a list
146146+ val find_all : ('a -> bool) -> 'a list -> 'a list
147147+ val filteri : (int -> 'a -> bool) -> 'a list -> 'a list
148148+ val take : int -> 'a list -> 'a list
149149+ val drop : int -> 'a list -> 'a list
150150+ val take_while : ('a -> bool) -> 'a list -> 'a list
151151+ val drop_while : ('a -> bool) -> 'a list -> 'a list
152152+ val partition : ('a -> bool) -> 'a list -> 'a list * 'a list
153153+ val partition_map :
154154+ ('a -> ('b, 'c) Either.t) -> 'a list -> 'b list * 'c list
155155+ val assoc : 'a -> ('a * 'b) list -> 'b
156156+ val assoc_opt : 'a -> ('a * 'b) list -> 'b option
157157+ val assq : 'a -> ('a * 'b) list -> 'b
158158+ val assq_opt : 'a -> ('a * 'b) list -> 'b option
159159+ val mem_assoc : 'a -> ('a * 'b) list -> bool
160160+ val mem_assq : 'a -> ('a * 'b) list -> bool
161161+ val remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list
162162+ val remove_assq : 'a -> ('a * 'b) list -> ('a * 'b) list
163163+ val split : ('a * 'b) list -> 'a list * 'b list
164164+ val combine : 'a list -> 'b list -> ('a * 'b) list
165165+ val sort : ('a -> 'a -> int) -> 'a list -> 'a list
166166+ val stable_sort : ('a -> 'a -> int) -> 'a list -> 'a list
167167+ val fast_sort : ('a -> 'a -> int) -> 'a list -> 'a list
168168+ val sort_uniq : ('a -> 'a -> int) -> 'a list -> 'a list
169169+ val merge : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list
170170+ val to_seq : 'a list -> 'a Seq.t
171171+ val of_seq : 'a Seq.t -> 'a list
172172+ end)}
173173+174174+Test #show_exception directive:
175175+176176+ $ unix_client exec_toplevel '' '# #show_exception Not_found;;'
177177+ {mime_vals:[];parts:[];script:S(# #show_exception Not_found;;
178178+ exception Not_found)}
179179+180180+ $ unix_client exec_toplevel '' '# #show_exception Invalid_argument;;'
181181+ {mime_vals:[];parts:[];script:S(# #show_exception Invalid_argument;;
182182+ exception Invalid_argument of string)}
183183+184184+==============================================
185185+SECTION 3: #print_depth and #print_length
186186+==============================================
187187+188188+ $ unix_client exec_toplevel '' '# let nested = [[[[1;2;3]]]];;'
189189+ {mime_vals:[];parts:[];script:S(# let nested = [[[[1;2;3]]]];;
190190+ val nested : int list list list list = [[[[1; 2; 3]]]])}
191191+192192+Test #print_depth:
193193+194194+ $ unix_client exec_toplevel '' '# #print_depth 2;;'
195195+ {mime_vals:[];parts:[];script:S(# #print_depth 2;;)}
196196+197197+ $ unix_client exec_toplevel '' '# nested;;'
198198+ {mime_vals:[];parts:[];script:S(# nested;;
199199+ - : int list list list list = [[[...]]])}
200200+201201+ $ unix_client exec_toplevel '' '# #print_depth 100;;'
202202+ {mime_vals:[];parts:[];script:S(# #print_depth 100;;)}
203203+204204+ $ unix_client exec_toplevel '' '# nested;;'
205205+ {mime_vals:[];parts:[];script:S(# nested;;
206206+ - : int list list list list = [[[[1; 2; 3]]]])}
207207+208208+Test #print_length:
209209+210210+ $ unix_client exec_toplevel '' '# let long_list = [1;2;3;4;5;6;7;8;9;10];;'
211211+ {mime_vals:[];parts:[];script:S(# let long_list = [1;2;3;4;5;6;7;8;9;10];;
212212+ val long_list : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10])}
213213+214214+ $ unix_client exec_toplevel '' '# #print_length 3;;'
215215+ {mime_vals:[];parts:[];script:S(# #print_length 3;;)}
216216+217217+ $ unix_client exec_toplevel '' '# long_list;;'
218218+ {mime_vals:[];parts:[];script:S(# long_list;;
219219+ - : int list = [1; 2; ...])}
220220+221221+ $ unix_client exec_toplevel '' '# #print_length 100;;'
222222+ {mime_vals:[];parts:[];script:S(# #print_length 100;;)}
223223+224224+==============================================
225225+SECTION 4: #install_printer and #remove_printer
226226+==============================================
227227+228228+ $ unix_client exec_toplevel '' '# type color = Red | Green | Blue;;'
229229+ {mime_vals:[];parts:[];script:S(# type color = Red | Green | Blue;;
230230+ type color = Red | Green | Blue)}
231231+232232+ $ unix_client exec_toplevel '' '# let pp_color fmt c = Format.fprintf fmt "<color:%s>" (match c with Red -> "red" | Green -> "green" | Blue -> "blue");;'
233233+ {mime_vals:[];parts:[];script:S(# let pp_color fmt c = Format.fprintf fmt "<color:%s>" (match c with Red -> "red" | Green -> "green" | Blue -> "blue");;
234234+ val pp_color : Format.formatter -> color -> unit = <fun>)}
235235+236236+Test #install_printer:
237237+238238+ $ unix_client exec_toplevel '' '# #install_printer pp_color;;'
239239+ {mime_vals:[];parts:[];script:S(# #install_printer pp_color;;)}
240240+241241+ $ unix_client exec_toplevel '' '# Red;;'
242242+ {mime_vals:[];parts:[];script:S(# Red;;
243243+ - : color = <color:red>)}
244244+245245+ $ unix_client exec_toplevel '' '# [Red; Green; Blue];;'
246246+ {mime_vals:[];parts:[];script:S(# [Red; Green; Blue];;
247247+ - : color list = [<color:red>; <color:green>; <color:blue>])}
248248+249249+Test #remove_printer:
250250+251251+ $ unix_client exec_toplevel '' '# #remove_printer pp_color;;'
252252+ {mime_vals:[];parts:[];script:S(# #remove_printer pp_color;;)}
253253+254254+ $ unix_client exec_toplevel '' '# Red;;'
255255+ {mime_vals:[];parts:[];script:S(# Red;;
256256+ - : color = Red)}
257257+258258+==============================================
259259+SECTION 5: #warnings and #warn_error
260260+==============================================
261261+262262+ $ unix_client exec_toplevel '' '# #warnings "-26";;'
263263+ {mime_vals:[];parts:[];script:S(# #warnings "-26";;)}
264264+265265+Code with unused variable should not warn:
266266+267267+ $ unix_client exec_toplevel '' '# let _ = let unused = 1 in 2;;'
268268+ {mime_vals:[];parts:[];script:S(# let _ = let unused = 1 in 2;;
269269+ - : int = 2)}
270270+271271+Re-enable warning:
272272+273273+ $ unix_client exec_toplevel '' '# #warnings "+26";;'
274274+ {mime_vals:[];parts:[];script:S(# #warnings "+26";;)}
275275+276276+Now should warn:
277277+278278+ $ unix_client exec_toplevel '' '# let _ = let unused2 = 1 in 2;;'
279279+ {mime_vals:[];parts:[];script:S(# let _ = let unused2 = 1 in 2;;
280280+ Line 1, characters 12-19:
281281+ Warning 26 [unused-var]: unused variable unused2.
282282+ - : int = 2)}
283283+284284+Test #warn_error:
285285+286286+ $ unix_client exec_toplevel '' '# #warn_error "+26";;'
287287+ {mime_vals:[];parts:[];script:S(# #warn_error "+26";;)}
288288+289289+ $ unix_client exec_toplevel '' '# let _ = let unused3 = 1 in 2;;'
290290+ {mime_vals:[];parts:[];script:S(# let _ = let unused3 = 1 in 2;;
291291+ Line 1, characters 12-19:
292292+ Error (warning 26 [unused-var]): unused variable unused3.)}
293293+294294+Reset:
295295+296296+ $ unix_client exec_toplevel '' '# #warn_error "-a";;'
297297+ {mime_vals:[];parts:[];script:S(# #warn_error "-a";;)}
298298+299299+==============================================
300300+SECTION 6: #rectypes
301301+==============================================
302302+303303+Without rectypes, recursive type should fail:
304304+305305+ $ unix_client exec_toplevel '' "# type 'a t = 'a t -> int;;"
306306+ {mime_vals:[];parts:[];script:S(# type 'a t = 'a t -> int;;
307307+ Line 1, characters 0-23:
308308+ Error: The type abbreviation t is cyclic:
309309+ 'a t = 'a t -> int,
310310+ 'a t -> int contains 'a t)}
311311+312312+Enable rectypes:
313313+314314+ $ unix_client exec_toplevel '' '# #rectypes;;'
315315+ {mime_vals:[];parts:[];script:S(# #rectypes;;)}
316316+317317+Now recursive type should work:
318318+319319+ $ unix_client exec_toplevel '' "# type 'a u = 'a u -> int;;"
320320+ {mime_vals:[];parts:[];script:S(# type 'a u = 'a u -> int;;
321321+ type 'a u = 'a u -> int)}
322322+323323+==============================================
324324+SECTION 7: #directory
325325+==============================================
326326+327327+ $ unix_client exec_toplevel '' '# #directory "/tmp";;'
328328+ {mime_vals:[];parts:[];script:S(# #directory "/tmp";;)}
329329+330330+ $ unix_client exec_toplevel '' '# #remove_directory "/tmp";;'
331331+ {mime_vals:[];parts:[];script:S(# #remove_directory "/tmp";;)}
332332+333333+==============================================
334334+SECTION 8: #help
335335+==============================================
336336+337337+ $ unix_client exec_toplevel '' '# #help;;'
338338+ {mime_vals:[];parts:[];script:S(# #help;;
339339+ General
340340+ #help
341341+ Prints a list of all available directives, with corresponding argument type
342342+ if appropriate.
343343+ #quit
344344+ Exit the toplevel.
345345+346346+ Loading code
347347+ #cd <str>
348348+ Change the current working directory.
349349+ #directory <str>
350350+ Add the given directory to search path for source and compiled files.
351351+ #load <str>
352352+ Load in memory a bytecode object, produced by ocamlc.
353353+ #load_rec <str>
354354+ As #load, but loads dependencies recursively.
355355+ #mod_use <str>
356356+ Usage is identical to #use but #mod_use wraps the contents in a module.
357357+ #remove_directory <str>
358358+ Remove the given directory from the search path.
359359+ #show_dirs
360360+ List directories currently in the search path.
361361+ #use <str>
362362+ Read, compile and execute source phrases from the given file.
363363+ #use_output <str>
364364+ Execute a command and read, compile and execute source phrases from its
365365+ output.
366366+367367+ Environment queries
368368+ #show <ident>
369369+ Print the signatures of components from any of the categories below.
370370+ #show_class <ident>
371371+ Print the signature of the corresponding class.
372372+ #show_class_type <ident>
373373+ Print the signature of the corresponding class type.
374374+ #show_constructor <ident>
375375+ Print the signature of the corresponding value constructor.
376376+ #show_exception <ident>
377377+ Print the signature of the corresponding exception.
378378+ #show_module <ident>
379379+ Print the signature of the corresponding module.
380380+ #show_module_type <ident>
381381+ Print the signature of the corresponding module type.
382382+ #show_type <ident>
383383+ Print the signature of the corresponding type constructor.
384384+ #show_val <ident>
385385+ Print the signature of the corresponding value.
386386+387387+ Findlib
388388+ #require <str>
389389+ Load a package (js_top_worker)
390390+ #require <str>
391391+ Load a package (js_top_worker)
392392+393393+ Pretty-printing
394394+ #install_printer <ident>
395395+ Registers a printer for values of a certain type.
396396+ #print_depth <int>
397397+ Limit the printing of values to a maximal depth of n.
398398+ #print_length <int>
399399+ Limit the number of value nodes printed to at most n.
400400+ #remove_printer <ident>
401401+ Remove the named function from the table of toplevel printers.
402402+403403+ Tracing
404404+ #trace <ident>
405405+ All calls to the function named function-name will be traced.
406406+ #untrace <ident>
407407+ Stop tracing the given function.
408408+ #untrace_all
409409+ Stop tracing all functions traced so far.
410410+411411+ Compiler options
412412+ #debug <bool>
413413+ Choose whether to generate debugging events.
414414+ #labels <bool>
415415+ Choose whether to ignore labels in function types.
416416+ #ppx <str>
417417+ After parsing, pipe the abstract syntax tree through the preprocessor
418418+ command.
419419+ #principal <bool>
420420+ Make sure that all types are derived in a principal way.
421421+ #rectypes
422422+ Allow arbitrary recursive types during type-checking.
423423+ #warn_error <str>
424424+ Treat as errors the warnings enabled by the argument.
425425+ #warnings <str>
426426+ Enable or disable warnings according to the argument.
427427+428428+ Undocumented
429429+ #camlp4o
430430+ #camlp4r
431431+ #list
432432+ #predicates <str>
433433+ #thread)}
434434+435435+==============================================
436436+SECTION 9: #use (File Loading)
437437+==============================================
438438+439439+Create a test file:
440440+441441+ $ cat > /tmp/test_use.ml << 'EOF'
442442+ > let from_file = "loaded via #use"
443443+ > let add x y = x + y
444444+ > EOF
445445+446446+ $ unix_client exec_toplevel '' '# #use "/tmp/test_use.ml";;'
447447+ {mime_vals:[];parts:[];script:S(# #use "/tmp/test_use.ml";;
448448+ val from_file : string = "loaded via #use"
449449+450450+ val add : int -> int -> int = <fun>)}
451451+452452+ $ unix_client exec_toplevel '' '# from_file;;'
453453+ {mime_vals:[];parts:[];script:S(# from_file;;
454454+ - : string = "loaded via #use")}
455455+456456+ $ unix_client exec_toplevel '' '# add 1 2;;'
457457+ {mime_vals:[];parts:[];script:S(# add 1 2;;
458458+ - : int = 3)}
459459+460460+==============================================
461461+SECTION 10: #mod_use
462462+==============================================
463463+464464+Create a test file:
465465+466466+ $ cat > /tmp/test_mod.ml << 'EOF'
467467+ > let value = 42
468468+ > type t = A | B
469469+ > EOF
470470+471471+ $ unix_client exec_toplevel '' '# #mod_use "/tmp/test_mod.ml";;'
472472+ {mime_vals:[];parts:[];script:S(# #mod_use "/tmp/test_mod.ml";;
473473+ module Test_mod : sig val value : int type t = A | B end)}
474474+475475+ $ unix_client exec_toplevel '' '# Test_mod.value;;'
476476+ {mime_vals:[];parts:[];script:S(# Test_mod.value;;
477477+ - : int = 42)}
478478+479479+==============================================
480480+SECTION 11: Findlib #require
481481+==============================================
482482+483483+ $ unix_client exec_toplevel '' '# #require "str";;'
484484+ {mime_vals:[];parts:[];script:S(# #require "str";;
485485+ /home/jons-agent/.opam/default/lib/ocaml/str: added to search path)}
486486+487487+ $ unix_client exec_toplevel '' '# Str.regexp "test";;'
488488+ {mime_vals:[];parts:[];script:S(# Str.regexp "test";;
489489+ - : Str.regexp = <abstr>)}
490490+491491+==============================================
492492+SECTION 12: Findlib #list
493493+==============================================
494494+495495+ $ unix_client exec_toplevel '' '# #list;;'
496496+ {mime_vals:[];parts:[];script:S(# #list;;
497497+ angstrom (version: 0.16.1)
498498+ angstrom.async (version: n/a)
499499+ angstrom.lwt-unix (version: n/a)
500500+ angstrom.unix (version: n/a)
501501+ astring (version: 0.8.5)
502502+ astring.top (version: 0.8.5)
503503+ base (version: v0.17.3)
504504+ base.base_internalhash_types (version: v0.17.3)
505505+ base.md5 (version: v0.17.3)
506506+ base.shadow_stdlib (version: v0.17.3)
507507+ base64 (version: 3.5.2)
508508+ base64.rfc2045 (version: 3.5.2)
509509+ bigstringaf (version: 0.10.0)
510510+ bos (version: 0.2.1)
511511+ bos.setup (version: 0.2.1)
512512+ bos.top (version: 0.2.1)
513513+ brr (version: 0.0.8)
514514+ brr.ocaml_poke (version: 0.0.8)
515515+ brr.ocaml_poke_ui (version: 0.0.8)
516516+ brr.poke (version: 0.0.8)
517517+ brr.poked (version: 0.0.8)
518518+ bytes (version: [distributed with OCaml 4.02 or above])
519519+ bytesrw (version: 0.3.0)
520520+ bytesrw.sysrandom (version: 0.3.0)
521521+ bytesrw.unix (version: 0.3.0)
522522+ camlp-streams (version: n/a)
523523+ cbort (version: 2b102ae)
524524+ chrome-trace (version: 1.6.2-12057-g12f9ecb)
525525+ cmdliner (version: 2.1.0)
526526+ compiler-libs (version: 5.4.0)
527527+ compiler-libs.bytecomp (version: 5.4.0)
528528+ compiler-libs.common (version: 5.4.0)
529529+ compiler-libs.native-toplevel (version: 5.4.0)
530530+ compiler-libs.optcomp (version: 5.4.0)
531531+ compiler-libs.toplevel (version: 5.4.0)
532532+ cppo (version: n/a)
533533+ csexp (version: 1.5.2)
534534+ cstruct (version: 6.2.0)
535535+ domain-local-await (version: 1.0.1)
536536+ dune (version: n/a)
537537+ dune-action-plugin (version: 1.6.2-12057-g12f9ecb)
538538+ dune-build-info (version: 1.6.2-12057-g12f9ecb)
539539+ dune-configurator (version: 1.6.2-12057-g12f9ecb)
540540+ dune-glob (version: 1.6.2-12057-g12f9ecb)
541541+ dune-private-libs (version: n/a)
542542+ dune-private-libs.dune-section (version: 1.6.2-12057-g12f9ecb)
543543+ dune-private-libs.meta_parser (version: 1.6.2-12057-g12f9ecb)
544544+ dune-rpc (version: 1.6.2-12057-g12f9ecb)
545545+ dune-rpc-lwt (version: 1.6.2-12057-g12f9ecb)
546546+ dune-rpc.private (version: 1.6.2-12057-g12f9ecb)
547547+ dune-site (version: 1.6.2-12057-g12f9ecb)
548548+ dune-site.dynlink (version: 1.6.2-12057-g12f9ecb)
549549+ dune-site.linker (version: 1.6.2-12057-g12f9ecb)
550550+ dune-site.plugins (version: 1.6.2-12057-g12f9ecb)
551551+ dune-site.private (version: 1.6.2-12057-g12f9ecb)
552552+ dune-site.toplevel (version: 1.6.2-12057-g12f9ecb)
553553+ dune.configurator (version: n/a)
554554+ dyn (version: 1.6.2-12057-g12f9ecb)
555555+ dynlink (version: 5.4.0)
556556+ eio (version: n/a)
557557+ eio.core (version: n/a)
558558+ eio.mock (version: n/a)
559559+ eio.runtime_events (version: n/a)
560560+ eio.unix (version: n/a)
561561+ eio.utils (version: n/a)
562562+ eio_linux (version: n/a)
563563+ eio_main (version: n/a)
564564+ eio_posix (version: n/a)
565565+ either (version: 1.0.0)
566566+ findlib (version: 1.9.8)
567567+ findlib.dynload (version: 1.9.8)
568568+ findlib.internal (version: 1.9.8)
569569+ findlib.top (version: 1.9.8)
570570+ fix (version: n/a)
571571+ fmt (version: 0.11.0)
572572+ fmt.cli (version: 0.11.0)
573573+ fmt.top (version: 0.11.0)
574574+ fmt.tty (version: 0.11.0)
575575+ fpath (version: 0.7.3)
576576+ fpath.top (version: 0.7.3)
577577+ fs-io (version: 1.6.2-12057-g12f9ecb)
578578+ gen (version: 1.1)
579579+ hmap (version: 0.8.1)
580580+ iomux (version: v0.4)
581581+ jane-street-headers (version: v0.17.0)
582582+ js_of_ocaml (version: 6.2.0)
583583+ js_of_ocaml-compiler (version: 6.2.0)
584584+ js_of_ocaml-compiler.dynlink (version: 6.2.0)
585585+ js_of_ocaml-compiler.findlib-support (version: 6.2.0)
586586+ js_of_ocaml-compiler.runtime (version: 6.2.0)
587587+ js_of_ocaml-compiler.runtime-files (version: 6.2.0)
588588+ js_of_ocaml-lwt (version: 6.2.0)
589589+ js_of_ocaml-ppx (version: 6.2.0)
590590+ js_of_ocaml-ppx.as-lib (version: 6.2.0)
591591+ js_of_ocaml-toplevel (version: 6.2.0)
592592+ js_of_ocaml.deriving (version: 6.2.0)
593593+ js_top_worker (version: 0.0.1)
594594+ js_top_worker-bin (version: n/a)
595595+ js_top_worker-client (version: 0.0.1)
596596+ js_top_worker-client.msg (version: 0.0.1)
597597+ js_top_worker-client_fut (version: 0.0.1)
598598+ js_top_worker-rpc (version: 0.0.1)
599599+ js_top_worker-rpc.message (version: 0.0.1)
600600+ js_top_worker-unix (version: n/a)
601601+ js_top_worker-web (version: 0.0.1)
602602+ js_top_worker_rpc_def (version: n/a)
603603+ js_top_worker_rpc_def.__private__ (version: n/a)
604604+ js_top_worker_rpc_def.__private__.js_top_worker_rpc_def (version: 0.0.1)
605605+ jsonm (version: 1.0.2)
606606+ jst-config (version: v0.17.0)
607607+ logs (version: 0.10.0)
608608+ logs.browser (version: 0.10.0)
609609+ logs.cli (version: 0.10.0)
610610+ logs.fmt (version: 0.10.0)
611611+ logs.lwt (version: 0.10.0)
612612+ logs.threaded (version: 0.10.0)
613613+ logs.top (version: 0.10.0)
614614+ lwt (version: 6.0.0)
615615+ lwt-dllist (version: 1.1.0)
616616+ lwt.unix (version: 6.0.0)
617617+ menhir (version: n/a)
618618+ menhirCST (version: 20260122)
619619+ menhirGLR (version: 20260122)
620620+ menhirLib (version: 20260122)
621621+ menhirSdk (version: 20260122)
622622+ merlin-lib (version: n/a)
623623+ merlin-lib.analysis (version: 5.6.1-504)
624624+ merlin-lib.commands (version: 5.6.1-504)
625625+ merlin-lib.config (version: 5.6.1-504)
626626+ merlin-lib.dot_protocol (version: 5.6.1-504)
627627+ merlin-lib.extend (version: 5.6.1-504)
628628+ merlin-lib.index_format (version: 5.6.1-504)
629629+ merlin-lib.kernel (version: 5.6.1-504)
630630+ merlin-lib.ocaml_compression (version: 5.6.1-504)
631631+ merlin-lib.ocaml_merlin_specific (version: 5.6.1-504)
632632+ merlin-lib.ocaml_parsing (version: 5.6.1-504)
633633+ merlin-lib.ocaml_preprocess (version: 5.6.1-504)
634634+ merlin-lib.ocaml_typing (version: 5.6.1-504)
635635+ merlin-lib.ocaml_utils (version: 5.6.1-504)
636636+ merlin-lib.os_ipc (version: 5.6.1-504)
637637+ merlin-lib.query_commands (version: 5.6.1-504)
638638+ merlin-lib.query_protocol (version: 5.6.1-504)
639639+ merlin-lib.sherlodoc (version: 5.6.1-504)
640640+ merlin-lib.utils (version: 5.6.1-504)
641641+ mime_printer (version: e46cb08)
642642+ mtime (version: 2.1.0)
643643+ mtime.clock (version: 2.1.0)
644644+ mtime.clock.os (version: 2.1.0)
645645+ mtime.top (version: 2.1.0)
646646+ ocaml-compiler-libs (version: n/a)
647647+ ocaml-compiler-libs.bytecomp (version: v0.17.0)
648648+ ocaml-compiler-libs.common (version: v0.17.0)
649649+ ocaml-compiler-libs.optcomp (version: v0.17.0)
650650+ ocaml-compiler-libs.shadow (version: v0.17.0)
651651+ ocaml-compiler-libs.toplevel (version: v0.17.0)
652652+ ocaml-syntax-shims (version: n/a)
653653+ ocaml-version (version: n/a)
654654+ ocaml_intrinsics_kernel (version: v0.17.1)
655655+ ocamlbuild (version: 0.16.1)
656656+ ocamlc-loc (version: 1.6.2-12057-g12f9ecb)
657657+ ocamldoc (version: 5.4.0)
658658+ ocamlformat-lib (version: 0.28.1)
659659+ ocamlformat-lib.format_ (version: 0.28.1)
660660+ ocamlformat-lib.ocaml_common (version: 0.28.1)
661661+ ocamlformat-lib.ocamlformat_stdlib (version: 0.28.1)
662662+ ocamlformat-lib.odoc_parser (version: 0.28.1)
663663+ ocamlformat-lib.parser_extended (version: 0.28.1)
664664+ ocamlformat-lib.parser_shims (version: 0.28.1)
665665+ ocamlformat-lib.parser_standard (version: 0.28.1)
666666+ ocamlformat-lib.stdlib_shims (version: 0.28.1)
667667+ ocamlgraph (version: 2.2.0)
668668+ ocp-indent (version: n/a)
669669+ ocp-indent.dynlink (version: 1.9.0)
670670+ ocp-indent.lexer (version: 1.9.0)
671671+ ocp-indent.lib (version: 1.9.0)
672672+ ocp-indent.utils (version: 1.9.0)
673673+ ocplib-endian (version: n/a)
674674+ ocplib-endian.bigstring (version: n/a)
675675+ opam-core (version: n/a)
676676+ opam-core.cmdliner (version: n/a)
677677+ opam-file-format (version: 2.2.0)
678678+ opam-format (version: n/a)
679679+ optint (version: 0.3.0)
680680+ ordering (version: 1.6.2-12057-g12f9ecb)
681681+ patch (version: 3.1.0)
682682+ pp (version: 2.0.0)
683683+ ppx_assert (version: v0.17.0)
684684+ ppx_assert.runtime-lib (version: v0.17.0)
685685+ ppx_base (version: v0.17.0)
686686+ ppx_blob (version: 0.9.0)
687687+ ppx_cold (version: v0.17.0)
688688+ ppx_compare (version: v0.17.0)
689689+ ppx_compare.expander (version: v0.17.0)
690690+ ppx_compare.runtime-lib (version: v0.17.0)
691691+ ppx_derivers (version: n/a)
692692+ ppx_deriving (version: n/a)
693693+ ppx_deriving.api (version: 6.1.1)
694694+ ppx_deriving.create (version: 6.1.1)
695695+ ppx_deriving.enum (version: 6.1.1)
696696+ ppx_deriving.eq (version: 6.1.1)
697697+ ppx_deriving.fold (version: 6.1.1)
698698+ ppx_deriving.iter (version: 6.1.1)
699699+ ppx_deriving.make (version: 6.1.1)
700700+ ppx_deriving.map (version: 6.1.1)
701701+ ppx_deriving.ord (version: 6.1.1)
702702+ ppx_deriving.runtime (version: 6.1.1)
703703+ ppx_deriving.show (version: 6.1.1)
704704+ ppx_deriving.std (version: 6.1.1)
705705+ ppx_deriving_rpc (version: 10.0.0)
706706+ ppx_enumerate (version: v0.17.0)
707707+ ppx_enumerate.runtime-lib (version: v0.17.0)
708708+ ppx_expect (version: v0.17.3)
709709+ ppx_expect.config (version: v0.17.3)
710710+ ppx_expect.config_types (version: v0.17.3)
711711+ ppx_expect.evaluator (version: v0.17.3)
712712+ ppx_expect.make_corrected_file (version: v0.17.3)
713713+ ppx_expect.runtime (version: v0.17.3)
714714+ ppx_globalize (version: v0.17.2)
715715+ ppx_hash (version: v0.17.0)
716716+ ppx_hash.expander (version: v0.17.0)
717717+ ppx_hash.runtime-lib (version: v0.17.0)
718718+ ppx_here (version: v0.17.0)
719719+ ppx_here.expander (version: v0.17.0)
720720+ ppx_here.runtime-lib (version: v0.17.0)
721721+ ppx_inline_test (version: v0.17.1)
722722+ ppx_inline_test.config (version: v0.17.1)
723723+ ppx_inline_test.drop (version: v0.17.1)
724724+ ppx_inline_test.libname (version: v0.17.1)
725725+ ppx_inline_test.runner (version: v0.17.1)
726726+ ppx_inline_test.runner.lib (version: v0.17.1)
727727+ ppx_inline_test.runtime-lib (version: v0.17.1)
728728+ ppx_optcomp (version: v0.17.1)
729729+ ppx_sexp_conv (version: v0.17.1)
730730+ ppx_sexp_conv.expander (version: v0.17.1)
731731+ ppx_sexp_conv.runtime-lib (version: v0.17.1)
732732+ ppxlib (version: 0.37.0)
733733+ ppxlib.__private__ (version: n/a)
734734+ ppxlib.__private__.ppx_foo_deriver (version: 0.37.0)
735735+ ppxlib.ast (version: 0.37.0)
736736+ ppxlib.astlib (version: 0.37.0)
737737+ ppxlib.metaquot (version: 0.37.0)
738738+ ppxlib.metaquot_lifters (version: 0.37.0)
739739+ ppxlib.print_diff (version: 0.37.0)
740740+ ppxlib.runner (version: 0.37.0)
741741+ ppxlib.runner_as_ppx (version: 0.37.0)
742742+ ppxlib.stdppx (version: 0.37.0)
743743+ ppxlib.traverse (version: 0.37.0)
744744+ ppxlib.traverse_builtins (version: 0.37.0)
745745+ ppxlib_jane (version: v0.17.4)
746746+ psq (version: 0.2.1)
747747+ re (version: n/a)
748748+ re.emacs (version: n/a)
749749+ re.glob (version: n/a)
750750+ re.pcre (version: n/a)
751751+ re.perl (version: n/a)
752752+ re.posix (version: n/a)
753753+ re.str (version: n/a)
754754+ result (version: 1.5)
755755+ rpclib (version: 10.0.0)
756756+ rpclib-lwt (version: 10.0.0)
757757+ rpclib.cmdliner (version: 10.0.0)
758758+ rpclib.core (version: 10.0.0)
759759+ rpclib.internals (version: 10.0.0)
760760+ rpclib.json (version: 10.0.0)
761761+ rpclib.markdown (version: 10.0.0)
762762+ rpclib.xml (version: 10.0.0)
763763+ rresult (version: 0.7.0)
764764+ rresult.top (version: 0.7.0)
765765+ runtime_events (version: 5.4.0)
766766+ sedlex (version: 3.7)
767767+ sedlex.ppx (version: 3.7)
768768+ sedlex.utils (version: 3.7)
769769+ seq (version: [distributed with OCaml 4.07 or above])
770770+ sexplib0 (version: v0.17.0)
771771+ sha (version: v1.15.4)
772772+ stdio (version: v0.17.0)
773773+ stdlib (version: 5.4.0)
774774+ stdlib-shims (version: 0.3.0)
775775+ stdune (version: 1.6.2-12057-g12f9ecb)
776776+ str (version: 5.4.0)
777777+ stringext (version: 1.6.0)
778778+ swhid_core (version: n/a)
779779+ thread-table (version: 1.0.0)
780780+ threads (version: 5.4.0)
781781+ threads.posix (version: [internal])
782782+ time_now (version: v0.17.0)
783783+ top-closure (version: 1.6.2-12057-g12f9ecb)
784784+ topkg (version: 1.1.1)
785785+ tyxml (version: 4.6.0)
786786+ tyxml.functor (version: 4.6.0)
787787+ unix (version: 5.4.0)
788788+ uri (version: 4.4.0)
789789+ uri.services (version: 4.4.0)
790790+ uri.services_full (version: 4.4.0)
791791+ uring (version: v2.7.0)
792792+ uucp (version: 17.0.0)
793793+ uuseg (version: 17.0.0)
794794+ uuseg.string (version: 17.0.0)
795795+ uutf (version: 1.0.4)
796796+ xdg (version: 1.6.2-12057-g12f9ecb)
797797+ xmlm (version: 1.4.0)
798798+ yojson (version: 3.0.0)
799799+ zarith (version: 1.14)
800800+ zarith.top (version: 1.13)
801801+ zarith_stubs_js (version: v0.17.0))}
802802+803803+==============================================
804804+SECTION 13: #labels and #principal
805805+==============================================
806806+807807+ $ unix_client exec_toplevel '' '# #labels true;;'
808808+ {mime_vals:[];parts:[];script:S(# #labels true;;)}
809809+810810+ $ unix_client exec_toplevel '' '# #labels false;;'
811811+ {mime_vals:[];parts:[];script:S(# #labels false;;)}
812812+813813+ $ unix_client exec_toplevel '' '# #principal true;;'
814814+ {mime_vals:[];parts:[];script:S(# #principal true;;)}
815815+816816+ $ unix_client exec_toplevel '' '# #principal false;;'
817817+ {mime_vals:[];parts:[];script:S(# #principal false;;)}
818818+819819+==============================================
820820+SECTION 14: Error Cases
821821+==============================================
822822+823823+Unknown directive:
824824+825825+ $ unix_client exec_toplevel '' '# #unknown_directive;;'
826826+ {mime_vals:[];parts:[];script:S(# #unknown_directive;;
827827+ Unknown directive unknown_directive.)}
828828+829829+#show with non-existent identifier:
830830+831831+ $ unix_client exec_toplevel '' '# #show nonexistent_value;;'
832832+ {mime_vals:[];parts:[];script:S(# #show nonexistent_value;;
833833+ Unknown element.)}
834834+835835+#require non-existent package:
836836+837837+ $ unix_client exec_toplevel '' '# #require "nonexistent_package_12345";;'
838838+ {mime_vals:[];parts:[];script:S(# #require "nonexistent_package_12345";;
839839+ No such package: nonexistent_package_12345)}
840840+841841+#use non-existent file:
842842+843843+ $ unix_client exec_toplevel '' '# #use "/nonexistent/file.ml";;'
844844+ {mime_vals:[];parts:[];script:S(# #use "/nonexistent/file.ml";;
845845+ Cannot find file /nonexistent/file.ml.)}
846846+847847+==============================================
848848+SECTION 15: #load (bytecode loading)
849849+==============================================
850850+851851+Note: #load may not work in js_of_ocaml context
852852+853853+ $ unix_client exec_toplevel '' '# #load "str.cma";;'
854854+ {mime_vals:[];parts:[];script:S(# #load "str.cma";;)}
855855+856856+==============================================
857857+SECTION 16: Classes (#show_class)
858858+==============================================
859859+860860+ $ unix_client exec_toplevel '' '# class counter = object val mutable n = 0 method incr = n <- n + 1 method get = n end;;'
861861+ {mime_vals:[];parts:[];script:S(# class counter = object val mutable n = 0 method incr = n <- n + 1 method get = n end;;
862862+ class counter :
863863+ object val mutable n : int method get : int method incr : unit end)}
864864+865865+ $ unix_client exec_toplevel '' '# #show_class counter;;'
866866+ {mime_vals:[];parts:[];script:S(# #show_class counter;;
867867+ class counter :
868868+ object val mutable n : int method get : int method incr : unit end)}
869869+870870+==============================================
871871+Cleanup
872872+==============================================
873873+874874+ $ kill $WORKER_PID 2>/dev/null || true
875875+ $ rm -f "$JS_TOP_WORKER_SOCK"
···11+ $ ./script.sh
22+ N
33+ {mime_vals:[];stderr:S(error while evaluating #enable "pretty";;
44+ error while evaluating #disable "shortvar";;);stdout:S(OCaml version 5.4.0
55+ Unknown directive enable.
66+ Unknown directive disable.)}
77+ {mime_vals:[];parts:[];script:S(# Printf.printf "Hello, world\n";;
88+ Hello, world
99+ - : unit = ())}
1010+ {mime_vals:[];parts:[];script:S(# let x = 1 + 2;;
1111+ val x : int = 3
1212+ # let x = 2+3;;
1313+ val x : int = 5)}
1414+ {mime_vals:[];parts:[];script:S(# let x = 1 + 2;;
1515+ val x : int = 3
1616+ # let x = 2+3;;
1717+ val x : int = 5)}
+4
js_top_worker/test/cram/simple.t/s1
···11+# let x = 1 + 2;;
22+foobarbaz
33+# let x = 2+3;;
44+foobarbz
+4
js_top_worker/test/cram/simple.t/s2
···11+# let x = 1 + 2;;
22+ foobarbaz
33+# let x = 2+3;;
44+ foobarbz
···11+#!/bin/bash
22+# Start the worker - it prints child PID and only returns once ready
33+44+if [ -z "$JS_TOP_WORKER_SOCK" ]; then
55+ echo "ERROR: JS_TOP_WORKER_SOCK not set" >&2
66+ exit 1
77+fi
88+99+rm -f "$JS_TOP_WORKER_SOCK"
1010+unix_worker
···11+22+fs=require('fs');
33+vm=require('vm');// vm must be in the global context to work properly
44+55+66+function include(filename){
77+ var code = fs.readFileSync(filename, 'utf-8');
88+ vm.runInThisContext(code, filename);
99+}
1010+1111+function importScripts(filename){
1212+ console.log('importScripts: ' + filename);
1313+ filename='./_opam/'+filename;
1414+ include(filename);
1515+}
1616+1717+global.importScripts=importScripts;
1818+global.include=include;
1919+
···11+=== Node.js Directive Tests ===
22+33+node_directive_test.js: [INFO] init()
44+Initializing findlib
55+Loaded findlib_index findlib_index: 10 META files, 0 universes
66+Parsed uri: ./lib/stdlib-shims/META
77+Reading library: stdlib-shims
88+Number of children: 0
99+Parsed uri: ./lib/sexplib0/META
1010+Reading library: sexplib0
1111+Number of children: 0
1212+Parsed uri: ./lib/ppxlib/META
1313+Reading library: ppxlib
1414+Number of children: 11
1515+Found child: __private__
1616+Reading library: ppxlib.__private__
1717+Number of children: 1
1818+Found child: ppx_foo_deriver
1919+Reading library: ppxlib.__private__.ppx_foo_deriver
2020+Number of children: 0
2121+Found child: ast
2222+Reading library: ppxlib.ast
2323+Number of children: 0
2424+Found child: astlib
2525+Reading library: ppxlib.astlib
2626+Number of children: 0
2727+Found child: metaquot
2828+Reading library: ppxlib.metaquot
2929+Number of children: 0
3030+Found child: metaquot_lifters
3131+Reading library: ppxlib.metaquot_lifters
3232+Number of children: 0
3333+Found child: print_diff
3434+Reading library: ppxlib.print_diff
3535+Number of children: 0
3636+Found child: runner
3737+Reading library: ppxlib.runner
3838+Number of children: 0
3939+Found child: runner_as_ppx
4040+Reading library: ppxlib.runner_as_ppx
4141+Number of children: 0
4242+Found child: stdppx
4343+Reading library: ppxlib.stdppx
4444+Number of children: 0
4545+Found child: traverse
4646+Reading library: ppxlib.traverse
4747+Number of children: 0
4848+Found child: traverse_builtins
4949+Reading library: ppxlib.traverse_builtins
5050+Number of children: 0
5151+Parsed uri: ./lib/ppx_deriving/META
5252+Reading library: ppx_deriving
5353+Number of children: 12
5454+Found child: api
5555+Reading library: ppx_deriving.api
5656+Number of children: 0
5757+Found child: create
5858+Reading library: ppx_deriving.create
5959+Number of children: 0
6060+Found child: enum
6161+Reading library: ppx_deriving.enum
6262+Number of children: 0
6363+Found child: eq
6464+Reading library: ppx_deriving.eq
6565+Number of children: 0
6666+Found child: fold
6767+Reading library: ppx_deriving.fold
6868+Number of children: 0
6969+Found child: iter
7070+Reading library: ppx_deriving.iter
7171+Number of children: 0
7272+Found child: make
7373+Reading library: ppx_deriving.make
7474+Number of children: 0
7575+Found child: map
7676+Reading library: ppx_deriving.map
7777+Number of children: 0
7878+Found child: ord
7979+Reading library: ppx_deriving.ord
8080+Number of children: 0
8181+Found child: runtime
8282+Reading library: ppx_deriving.runtime
8383+Number of children: 0
8484+Found child: show
8585+Reading library: ppx_deriving.show
8686+Number of children: 0
8787+Found child: std
8888+Reading library: ppx_deriving.std
8989+Number of children: 0
9090+Parsed uri: ./lib/ppx_derivers/META
9191+Reading library: ppx_derivers
9292+Number of children: 0
9393+Parsed uri: ./lib/ocaml_intrinsics_kernel/META
9494+Reading library: ocaml_intrinsics_kernel
9595+Number of children: 0
9696+Parsed uri: ./lib/ocaml/stdlib/META
9797+Reading library: stdlib
9898+Number of children: 0
9999+Parsed uri: ./lib/ocaml/compiler-libs/META
100100+Reading library: compiler-libs
101101+Number of children: 5
102102+Found child: common
103103+Reading library: compiler-libs.common
104104+Number of children: 0
105105+Found child: bytecomp
106106+Reading library: compiler-libs.bytecomp
107107+Number of children: 0
108108+Found child: optcomp
109109+Reading library: compiler-libs.optcomp
110110+Number of children: 0
111111+Found child: toplevel
112112+Reading library: compiler-libs.toplevel
113113+Number of children: 0
114114+Found child: native-toplevel
115115+Reading library: compiler-libs.native-toplevel
116116+Number of children: 0
117117+Parsed uri: ./lib/ocaml-compiler-libs/META
118118+Reading library: ocaml-compiler-libs
119119+Number of children: 5
120120+Found child: bytecomp
121121+Reading library: ocaml-compiler-libs.bytecomp
122122+Number of children: 0
123123+Found child: common
124124+Reading library: ocaml-compiler-libs.common
125125+Number of children: 0
126126+Found child: optcomp
127127+Reading library: ocaml-compiler-libs.optcomp
128128+Number of children: 0
129129+Found child: shadow
130130+Reading library: ocaml-compiler-libs.shadow
131131+Number of children: 0
132132+Found child: toplevel
133133+Reading library: ocaml-compiler-libs.toplevel
134134+Number of children: 0
135135+Parsed uri: ./lib/base/META
136136+Reading library: base
137137+Number of children: 3
138138+Found child: base_internalhash_types
139139+Reading library: base.base_internalhash_types
140140+Number of children: 0
141141+Found child: md5
142142+Reading library: base.md5
143143+Number of children: 0
144144+Found child: shadow_stdlib
145145+Reading library: base.shadow_stdlib
146146+Number of children: 0
147147+node_directive_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/
148148+node_directive_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO
149149+node_directive_test.js: [INFO] init() finished
150150+node_directive_test.js: [INFO] setup() for env default...
151151+node_directive_test.js: [INFO] Fetching stdlib__Format.cmi
152152+153153+node_directive_test.js: [INFO] Fetching stdlib__Sys.cmi
154154+155155+error while evaluating #enable "pretty";;
156156+error while evaluating #disable "shortvar";;
157157+node_directive_test.js: [INFO] Setup complete
158158+node_directive_test.js: [INFO] setup() finished for env default
159159+--- Section 1: Basic Execution ---
160160+[PASS] basic_eval: # 1 + 2;;
161161+ - : int = 3
162162+[PASS] let_binding: # let x = 42;;
163163+ val x : int = 42
164164+165165+--- Section 2: #show Directives ---
166166+[PASS] show_type_point: # #show point;;
167167+ type point = { x : float; y : float; }
168168+[PASS] show_val_origin: # #show origin;;
169169+ val origin : point
170170+[PASS] show_module: # #show MyMod;;
171171+ module MyMod : sig type t = int val zero : int end
172172+[PASS] show_exception: # #show My_error;;
173173+ exception My_error of string
174174+[PASS] show_type_list: # #show_type list;;
175175+ type 'a list = [] | (::) of 'a * 'a list
176176+node_directive_test.js: [INFO] Fetching stdlib__List.cmi
177177+178178+[PASS] show_val_list_map: # #show_val List.map;;
179179+ val map : ('a -> 'b) -> 'a list -> 'b list
180180+[PASS] show_module_list: # #show_module List;;
181181+ module List :
182182+ sig
183183+ type 'a t = 'a list = [] | (::) of 'a * 'a list
184184+ val length : 'a list -> int
185185+ val compare_lengths : 'a list -> 'b list -> int
186186+ val compare_length_with : 'a list -> int -> int
187187+ val is_empty : 'a list -> bool
188188+ val cons : 'a -> 'a list -> 'a list
189189+ val singleton : 'a -> 'a list
190190+ val hd : 'a list -> 'a
191191+ val tl : 'a list -> 'a list
192192+ val nth : 'a list -> int -> 'a
193193+ val nth_opt : 'a list -> int -> 'a option
194194+ val rev : 'a list -> 'a list
195195+ val init : int -> (int -> 'a) -> 'a list
196196+ val append : 'a list -> 'a list -> 'a list
197197+ val rev_append : 'a list -> 'a list -> 'a list
198198+ val concat : 'a list list -> 'a list
199199+ val flatten : 'a list list -> 'a list
200200+ val equal : ('a -> 'a -> bool) -> 'a list -> 'a list -> bool
201201+ val compare : ('a -> 'a -> int) -> 'a list -> 'a list -> int
202202+ val iter : ('a -> unit) -> 'a list -> unit
203203+ val iteri : (int -> 'a -> unit) -> 'a list -> unit
204204+ val map : ('a -> 'b) -> 'a list -> 'b list
205205+ val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list
206206+ val rev_map : ('a -> 'b) -> 'a list -> 'b list
207207+ val filter_map : ('a -> 'b option) -> 'a list -> 'b list
208208+ val concat_map : ('a -> 'b list) -> 'a list -> 'b list
209209+ val fold_left_map :
210210+ ('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a list -> 'acc * 'b list
211211+ val fold_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a list -> 'acc
212212+ val fold_right : ('a -> 'acc -> 'acc) -> 'a list -> 'acc -> 'acc
213213+ val iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit
214214+ val map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
215215+ val rev_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
216216+ val fold_left2 :
217217+ ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a list -> 'b list -> 'acc
218218+ val fold_right2 :
219219+ ('a -> 'b -> 'acc -> 'acc) -> 'a list -> 'b list -> 'acc -> 'acc
220220+ val for_all : ('a -> bool) -> 'a list -> bool
221221+ val exists : ('a -> bool) -> 'a list -> bool
222222+ val for_all2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
223223+ val exists2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
224224+ val mem : 'a -> 'a list -> bool
225225+ val memq : 'a -> 'a list -> bool
226226+ val find : ('a -> bool) -> 'a list -> 'a
227227+ val find_opt : ('a -> bool) -> 'a list -> 'a option
228228+ val find_index : ('a -> bool) -> 'a list -> int option
229229+ val find_map : ('a -> 'b option) -> 'a list -> 'b option
230230+ val find_mapi : (int -> 'a -> 'b option) -> 'a list -> 'b option
231231+ val filter : ('a -> bool) -> 'a list -> 'a list
232232+ val find_all : ('a -> bool) -> 'a list -> 'a list
233233+ val filteri : (int -> 'a -> bool) -> 'a list -> 'a list
234234+ val take : int -> 'a list -> 'a list
235235+ val drop : int -> 'a list -> 'a list
236236+ val take_while : ('a -> bool) -> 'a list -> 'a list
237237+ val drop_while : ('a -> bool) -> 'a list -> 'a list
238238+ val partition : ('a -> bool) -> 'a list -> 'a list * 'a list
239239+ val partition_map :
240240+ ('a -> ('b, 'c) Either.t) -> 'a list -> 'b list * 'c list
241241+ val assoc : 'a -> ('a * 'b) list -> 'b
242242+ val assoc_opt : 'a -> ('a * 'b) list -> 'b option
243243+ val assq : 'a -> ('a * 'b) list -> 'b
244244+ val assq_opt : 'a -> ('a * 'b) list -> 'b option
245245+ val mem_assoc : 'a -> ('a * 'b) list -> bool
246246+ val mem_assq : 'a -> ('a * 'b) list -> bool
247247+ val remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list
248248+ val remove_assq : 'a -> ('a * 'b) list -> ('a * 'b) list
249249+ val split : ('a * 'b) list -> 'a list * 'b list
250250+ val combine : 'a list -> 'b list -> ('a * 'b) list
251251+ val sort : ('a -> 'a -> int) -> 'a list -> 'a list
252252+ val stable_sort : ('a -> 'a -> int) -> 'a list -> 'a list
253253+ val fast_sort : ('a -> 'a -> int) -> 'a list -> 'a list
254254+ val sort_uniq : ('a -> 'a -> int) -> 'a list -> 'a list
255255+ val merge : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list
256256+ val to_seq : 'a list -> 'a Seq.t
257257+ val of_seq : 'a Seq.t -> 'a list
258258+ end
259259+[PASS] show_exception_not_found: # #show_exception Not_found;;
260260+ exception Not_found
261261+262262+--- Section 3: #print_depth and #print_length ---
263263+[PASS] print_depth_truncated: # nested;;
264264+ - : int list list list list = [[[...]]]
265265+[PASS] print_depth_full: # nested;;
266266+ - : int list list list list = [[[[1; 2; 3]]]]
267267+[PASS] print_length_truncated: # long_list;;
268268+ - : int list = [1; 2; ...]
269269+[PASS] print_length_full: # long_list;;
270270+ - : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
271271+272272+--- Section 4: #install_printer / #remove_printer ---
273273+[PASS] install_printer: # Red;;
274274+ - : color = <color:red>
275275+[PASS] remove_printer: # Red;;
276276+ - : color = Red
277277+278278+--- Section 5: #warnings / #warn_error ---
279279+[PASS] warnings_disabled: # let _ = let unused = 1 in 2;;
280280+ - : int = 2
281281+Line 1, characters 12-19:
282282+Warning 26 [unused-var]: unused variable unused2.
283283+[PASS] warnings_enabled: # let _ = let unused2 = 1 in 2;;
284284+ - : int = 2
285285+286286+Line 1, characters 12-19:
287287+Error (warning 26 [unused-var]): unused variable unused3.
288288+[FAIL] warn_error: # let _ = let unused3 = 1 in 2;;
289289+290290+--- Section 6: #rectypes ---
291291+292292+Line 1, characters 0-23:
293293+Error: The type abbreviation t is cyclic:
294294+ 'a t = 'a t -> int,
295295+ 'a t -> int contains 'a t
296296+[FAIL] rectypes_before: # type 'a t = 'a t -> int;;
297297+[PASS] rectypes_after: # type 'a u = 'a u -> int;;
298298+ type 'a u = 'a u -> int
299299+300300+--- Section 7: #directory ---
301301+[PASS] directory_add: (no error)
302302+[PASS] directory_remove: (no error)
303303+304304+--- Section 8: #help ---
305305+[PASS] help: # #help;;
306306+ General
307307+ #help
308308+ Prints a list of all available directives, with corresponding argume...
309309+310310+--- Section 9: #labels / #principal ---
311311+[PASS] labels_true: (no error)
312312+[PASS] labels_false: (no error)
313313+[PASS] principal_true: (no error)
314314+[PASS] principal_false: (no error)
315315+316316+--- Section 10: Error Cases ---
317317+[PASS] unknown_directive: # #unknown_directive;;
318318+ Unknown directive unknown_directive.
319319+[PASS] show_nonexistent: # #show nonexistent_value;;
320320+ Unknown element.
321321+322322+--- Section 11: Classes ---
323323+[PASS] show_class: # #show_class counter;;
324324+ class counter :
325325+ object val mutable n : int method get : int method incr : unit end
326326+327327+=== Results: 29/31 tests passed ===
328328+FAILURE: Some tests failed.
+328
js_top_worker/test/node/node_directive_test.ml
···11+(** Node.js test for OCaml toplevel directives.
22+33+ This tests the js_of_ocaml implementation of the toplevel,
44+ running in Node.js to verify directives work in the JS context.
55+66+ Directives tested:
77+ - Environment query: #show, #show_type, #show_val, #show_module, #show_exception
88+ - Pretty-printing: #print_depth, #print_length
99+ - Custom printers: #install_printer, #remove_printer
1010+ - Warnings: #warnings, #warn_error
1111+ - Type system: #rectypes
1212+ - Directory: #directory, #remove_directory
1313+ - Help: #help
1414+ - Compiler options: #labels, #principal
1515+ - Error handling: unknown directives, missing identifiers
1616+1717+ NOT tested (require file system or special setup):
1818+ - #use, #mod_use (file loading)
1919+ - #load (bytecode loading)
2020+ - #require, #list (findlib - tested separately)
2121+ - #trace (excluded per user request)
2222+*)
2323+2424+open Js_top_worker
2525+open Js_top_worker_rpc.Toplevel_api_gen
2626+open Impl
2727+2828+(* Flusher that writes to process.stdout in Node.js *)
2929+let console_flusher (s : string) : unit =
3030+ let open Js_of_ocaml in
3131+ let process = Js.Unsafe.get Js.Unsafe.global (Js.string "process") in
3232+ let stdout = Js.Unsafe.get process (Js.string "stdout") in
3333+ let write = Js.Unsafe.get stdout (Js.string "write") in
3434+ ignore (Js.Unsafe.call write stdout [| Js.Unsafe.inject (Js.string s) |])
3535+3636+let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
3737+ fun f () ->
3838+ let stdout_buff = Buffer.create 1024 in
3939+ let stderr_buff = Buffer.create 1024 in
4040+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
4141+ (* Note: Do NOT set stderr flusher - it causes hangs in js_of_ocaml *)
4242+ let x = f () in
4343+ let captured =
4444+ {
4545+ Impl.stdout = Buffer.contents stdout_buff;
4646+ stderr = Buffer.contents stderr_buff;
4747+ }
4848+ in
4949+ (* Restore flusher that writes to console so Printf.printf works for test output *)
5050+ Js_of_ocaml.Sys_js.set_channel_flusher stdout console_flusher;
5151+ (captured, x)
5252+5353+module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
5454+5555+module S : Impl.S = struct
5656+ type findlib_t = Js_top_worker_web.Findlibish.t
5757+5858+ let capture = capture
5959+6060+ let sync_get f =
6161+ let f = Fpath.v ("_opam/" ^ f) in
6262+ try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all)
6363+ with _ -> None
6464+6565+ let async_get f =
6666+ let f = Fpath.v ("_opam/" ^ f) in
6767+ try
6868+ let content =
6969+ In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all
7070+ in
7171+ Lwt.return (Ok content)
7272+ with e -> Lwt.return (Error (`Msg (Printexc.to_string e)))
7373+7474+ let create_file = Js_of_ocaml.Sys_js.create_file
7575+7676+ let import_scripts urls =
7777+ let open Js_of_ocaml.Js in
7878+ let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in
7979+ List.iter
8080+ (fun url ->
8181+ let (_ : 'a) =
8282+ Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |]
8383+ in
8484+ ())
8585+ urls
8686+8787+ let init_function _ () = failwith "Not implemented"
8888+ let findlib_init = Js_top_worker_web.Findlibish.init async_get
8989+9090+ let get_stdlib_dcs uri =
9191+ Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri
9292+ |> Result.to_list
9393+9494+ let require b v = function
9595+ | [] -> []
9696+ | packages ->
9797+ Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v
9898+ packages
9999+100100+ let path = "/static/cmis"
101101+end
102102+103103+module U = Impl.Make (S)
104104+105105+let start_server () =
106106+ let open U in
107107+ Logs.set_reporter (Logs_fmt.reporter ());
108108+ Logs.set_level (Some Logs.Info);
109109+ Server.init (IdlM.T.lift init);
110110+ Server.create_env (IdlM.T.lift create_env);
111111+ Server.destroy_env (IdlM.T.lift destroy_env);
112112+ Server.list_envs (IdlM.T.lift list_envs);
113113+ Server.setup (IdlM.T.lift setup);
114114+ Server.exec execute;
115115+ Server.complete_prefix complete_prefix;
116116+ Server.query_errors query_errors;
117117+ Server.type_enclosing type_enclosing;
118118+ Server.exec_toplevel exec_toplevel;
119119+ IdlM.server Server.implementation
120120+121121+module Client = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenClient ())
122122+123123+(* Test result tracking *)
124124+let total_tests = ref 0
125125+let passed_tests = ref 0
126126+127127+let test name check message =
128128+ incr total_tests;
129129+ let passed = check in
130130+ if passed then incr passed_tests;
131131+ let status = if passed then "PASS" else "FAIL" in
132132+ Printf.printf "[%s] %s: %s\n%!" status name message
133133+134134+let contains s substr =
135135+ try
136136+ let _ = Str.search_forward (Str.regexp_string substr) s 0 in
137137+ true
138138+ with Not_found -> false
139139+140140+let run_directive rpc code =
141141+ let ( let* ) = IdlM.ErrM.bind in
142142+ let* result = Client.exec_toplevel rpc "" ("# " ^ code) in
143143+ IdlM.ErrM.return result.script
144144+145145+let _ =
146146+ Printf.printf "=== Node.js Directive Tests ===\n\n%!";
147147+148148+ let rpc = start_server () in
149149+ let ( let* ) = IdlM.ErrM.bind in
150150+151151+ let init_config =
152152+ { stdlib_dcs = None; findlib_requires = []; findlib_index = None; execute = true }
153153+ in
154154+155155+ let test_sequence =
156156+ (* Initialize *)
157157+ let* _ = Client.init rpc init_config in
158158+ let* _ = Client.setup rpc "" in
159159+160160+ Printf.printf "--- Section 1: Basic Execution ---\n%!";
161161+162162+ let* r = run_directive rpc "1 + 2;;" in
163163+ test "basic_eval" (contains r "- : int = 3") r;
164164+165165+ let* r = run_directive rpc "let x = 42;;" in
166166+ test "let_binding" (contains r "val x : int = 42") r;
167167+168168+ Printf.printf "\n--- Section 2: #show Directives ---\n%!";
169169+170170+ (* Define types/values to query *)
171171+ let* _ = run_directive rpc "type point = { x: float; y: float };;" in
172172+ let* _ = run_directive rpc "let origin = { x = 0.0; y = 0.0 };;" in
173173+ let* _ =
174174+ run_directive rpc
175175+ "module MyMod = struct type t = int let zero = 0 end;;"
176176+ in
177177+ let* _ = run_directive rpc "exception My_error of string;;" in
178178+179179+ let* r = run_directive rpc "#show point;;" in
180180+ test "show_type_point" (contains r "type point") r;
181181+182182+ let* r = run_directive rpc "#show origin;;" in
183183+ test "show_val_origin" (contains r "val origin") r;
184184+185185+ let* r = run_directive rpc "#show MyMod;;" in
186186+ test "show_module" (contains r "module MyMod") r;
187187+188188+ let* r = run_directive rpc "#show My_error;;" in
189189+ test "show_exception" (contains r "exception My_error") r;
190190+191191+ let* r = run_directive rpc "#show_type list;;" in
192192+ test "show_type_list" (contains r "type 'a list") r;
193193+194194+ let* r = run_directive rpc "#show_val List.map;;" in
195195+ test "show_val_list_map" (contains r "val map") r;
196196+197197+ let* r = run_directive rpc "#show_module List;;" in
198198+ test "show_module_list" (contains r "module List") r;
199199+200200+ let* r = run_directive rpc "#show_exception Not_found;;" in
201201+ test "show_exception_not_found" (contains r "exception Not_found") r;
202202+203203+ Printf.printf "\n--- Section 3: #print_depth and #print_length ---\n%!";
204204+205205+ let* _ = run_directive rpc "let nested = [[[[1;2;3]]]];;" in
206206+ let* _ = run_directive rpc "#print_depth 2;;" in
207207+ let* r = run_directive rpc "nested;;" in
208208+ test "print_depth_truncated" (contains r "...") r;
209209+210210+ let* _ = run_directive rpc "#print_depth 100;;" in
211211+ let* r = run_directive rpc "nested;;" in
212212+ test "print_depth_full" (contains r "1; 2; 3") r;
213213+214214+ let* _ = run_directive rpc "let long_list = [1;2;3;4;5;6;7;8;9;10];;" in
215215+ let* _ = run_directive rpc "#print_length 3;;" in
216216+ let* r = run_directive rpc "long_list;;" in
217217+ test "print_length_truncated" (contains r "...") r;
218218+219219+ let* _ = run_directive rpc "#print_length 100;;" in
220220+ let* r = run_directive rpc "long_list;;" in
221221+ test "print_length_full" (contains r "10") r;
222222+223223+ Printf.printf "\n--- Section 4: #install_printer / #remove_printer ---\n%!";
224224+225225+ let* _ = run_directive rpc "type color = Red | Green | Blue;;" in
226226+ let* _ =
227227+ run_directive rpc
228228+ {|let pp_color fmt c = Format.fprintf fmt "<color:%s>" (match c with Red -> "red" | Green -> "green" | Blue -> "blue");;|}
229229+ in
230230+ let* _ = run_directive rpc "#install_printer pp_color;;" in
231231+ let* r = run_directive rpc "Red;;" in
232232+ test "install_printer" (contains r "<color:red>") r;
233233+234234+ let* _ = run_directive rpc "#remove_printer pp_color;;" in
235235+ let* r = run_directive rpc "Red;;" in
236236+ test "remove_printer" (contains r "Red" && not (contains r "<color:red>")) r;
237237+238238+ Printf.printf "\n--- Section 5: #warnings / #warn_error ---\n%!";
239239+240240+ let* _ = run_directive rpc "#warnings \"-26\";;" in
241241+ let* r = run_directive rpc "let _ = let unused = 1 in 2;;" in
242242+ test "warnings_disabled"
243243+ (not (contains r "Warning") || contains r "- : int = 2")
244244+ r;
245245+246246+ let* _ = run_directive rpc "#warnings \"+26\";;" in
247247+ let* r = run_directive rpc "let _ = let unused2 = 1 in 2;;" in
248248+ test "warnings_enabled" (contains r "Warning" || contains r "unused2") r;
249249+250250+ let* _ = run_directive rpc "#warn_error \"+26\";;" in
251251+ let* r = run_directive rpc "let _ = let unused3 = 1 in 2;;" in
252252+ test "warn_error" (contains r "Error") r;
253253+254254+ let* _ = run_directive rpc "#warn_error \"-a\";;" in
255255+256256+ Printf.printf "\n--- Section 6: #rectypes ---\n%!";
257257+258258+ let* r = run_directive rpc "type 'a t = 'a t -> int;;" in
259259+ test "rectypes_before" (contains r "Error" || contains r "cyclic") r;
260260+261261+ let* _ = run_directive rpc "#rectypes;;" in
262262+ let* r = run_directive rpc "type 'a u = 'a u -> int;;" in
263263+ test "rectypes_after" (contains r "type 'a u") r;
264264+265265+ Printf.printf "\n--- Section 7: #directory ---\n%!";
266266+267267+ let* r = run_directive rpc "#directory \"/tmp\";;" in
268268+ test "directory_add" (String.length r >= 0) "(no error)";
269269+270270+ let* r = run_directive rpc "#remove_directory \"/tmp\";;" in
271271+ test "directory_remove" (String.length r >= 0) "(no error)";
272272+273273+ Printf.printf "\n--- Section 8: #help ---\n%!";
274274+275275+ let* r = run_directive rpc "#help;;" in
276276+ test "help"
277277+ (contains r "directive" || contains r "Directive" || contains r "#")
278278+ (String.sub r 0 (min 100 (String.length r)) ^ "...");
279279+280280+ Printf.printf "\n--- Section 9: #labels / #principal ---\n%!";
281281+282282+ let* r = run_directive rpc "#labels true;;" in
283283+ test "labels_true" (String.length r >= 0) "(no error)";
284284+285285+ let* r = run_directive rpc "#labels false;;" in
286286+ test "labels_false" (String.length r >= 0) "(no error)";
287287+288288+ let* r = run_directive rpc "#principal true;;" in
289289+ test "principal_true" (String.length r >= 0) "(no error)";
290290+291291+ let* r = run_directive rpc "#principal false;;" in
292292+ test "principal_false" (String.length r >= 0) "(no error)";
293293+294294+ Printf.printf "\n--- Section 10: Error Cases ---\n%!";
295295+296296+ let* r = run_directive rpc "#unknown_directive;;" in
297297+ test "unknown_directive" (contains r "Unknown") r;
298298+299299+ let* r = run_directive rpc "#show nonexistent_value;;" in
300300+ test "show_nonexistent" (contains r "Unknown" || contains r "not found") r;
301301+302302+ Printf.printf "\n--- Section 11: Classes ---\n%!";
303303+304304+ let* _ =
305305+ run_directive rpc
306306+ "class counter = object val mutable n = 0 method incr = n <- n + 1 \
307307+ method get = n end;;"
308308+ in
309309+ let* r = run_directive rpc "#show_class counter;;" in
310310+ test "show_class" (contains r "class counter") r;
311311+312312+ IdlM.ErrM.return ()
313313+ in
314314+315315+ let promise = test_sequence |> IdlM.T.get in
316316+ (match Lwt.state promise with
317317+ | Lwt.Return (Ok ()) -> ()
318318+ | Lwt.Return (Error (InternalError s)) ->
319319+ Printf.printf "\n[ERROR] Test failed with: %s\n%!" s
320320+ | Lwt.Fail e ->
321321+ Printf.printf "\n[ERROR] Exception: %s\n%!" (Printexc.to_string e)
322322+ | Lwt.Sleep -> Printf.printf "\n[ERROR] Promise still pending\n%!");
323323+324324+ Printf.printf "\n=== Results: %d/%d tests passed ===\n%!" !passed_tests
325325+ !total_tests;
326326+ if !passed_tests = !total_tests then
327327+ Printf.printf "SUCCESS: All directive tests passed!\n%!"
328328+ else Printf.printf "FAILURE: Some tests failed.\n%!"
+237
js_top_worker/test/node/node_env_test.expected
···11+=== Node.js Environment Tests ===
22+33+node_env_test.js: [INFO] init()
44+Initializing findlib
55+Loaded findlib_index findlib_index: 10 META files, 0 universes
66+Parsed uri: ./lib/stdlib-shims/META
77+Reading library: stdlib-shims
88+Number of children: 0
99+Parsed uri: ./lib/sexplib0/META
1010+Reading library: sexplib0
1111+Number of children: 0
1212+Parsed uri: ./lib/ppxlib/META
1313+Reading library: ppxlib
1414+Number of children: 11
1515+Found child: __private__
1616+Reading library: ppxlib.__private__
1717+Number of children: 1
1818+Found child: ppx_foo_deriver
1919+Reading library: ppxlib.__private__.ppx_foo_deriver
2020+Number of children: 0
2121+Found child: ast
2222+Reading library: ppxlib.ast
2323+Number of children: 0
2424+Found child: astlib
2525+Reading library: ppxlib.astlib
2626+Number of children: 0
2727+Found child: metaquot
2828+Reading library: ppxlib.metaquot
2929+Number of children: 0
3030+Found child: metaquot_lifters
3131+Reading library: ppxlib.metaquot_lifters
3232+Number of children: 0
3333+Found child: print_diff
3434+Reading library: ppxlib.print_diff
3535+Number of children: 0
3636+Found child: runner
3737+Reading library: ppxlib.runner
3838+Number of children: 0
3939+Found child: runner_as_ppx
4040+Reading library: ppxlib.runner_as_ppx
4141+Number of children: 0
4242+Found child: stdppx
4343+Reading library: ppxlib.stdppx
4444+Number of children: 0
4545+Found child: traverse
4646+Reading library: ppxlib.traverse
4747+Number of children: 0
4848+Found child: traverse_builtins
4949+Reading library: ppxlib.traverse_builtins
5050+Number of children: 0
5151+Parsed uri: ./lib/ppx_deriving/META
5252+Reading library: ppx_deriving
5353+Number of children: 12
5454+Found child: api
5555+Reading library: ppx_deriving.api
5656+Number of children: 0
5757+Found child: create
5858+Reading library: ppx_deriving.create
5959+Number of children: 0
6060+Found child: enum
6161+Reading library: ppx_deriving.enum
6262+Number of children: 0
6363+Found child: eq
6464+Reading library: ppx_deriving.eq
6565+Number of children: 0
6666+Found child: fold
6767+Reading library: ppx_deriving.fold
6868+Number of children: 0
6969+Found child: iter
7070+Reading library: ppx_deriving.iter
7171+Number of children: 0
7272+Found child: make
7373+Reading library: ppx_deriving.make
7474+Number of children: 0
7575+Found child: map
7676+Reading library: ppx_deriving.map
7777+Number of children: 0
7878+Found child: ord
7979+Reading library: ppx_deriving.ord
8080+Number of children: 0
8181+Found child: runtime
8282+Reading library: ppx_deriving.runtime
8383+Number of children: 0
8484+Found child: show
8585+Reading library: ppx_deriving.show
8686+Number of children: 0
8787+Found child: std
8888+Reading library: ppx_deriving.std
8989+Number of children: 0
9090+Parsed uri: ./lib/ppx_derivers/META
9191+Reading library: ppx_derivers
9292+Number of children: 0
9393+Parsed uri: ./lib/ocaml_intrinsics_kernel/META
9494+Reading library: ocaml_intrinsics_kernel
9595+Number of children: 0
9696+Parsed uri: ./lib/ocaml/stdlib/META
9797+Reading library: stdlib
9898+Number of children: 0
9999+Parsed uri: ./lib/ocaml/compiler-libs/META
100100+Reading library: compiler-libs
101101+Number of children: 5
102102+Found child: common
103103+Reading library: compiler-libs.common
104104+Number of children: 0
105105+Found child: bytecomp
106106+Reading library: compiler-libs.bytecomp
107107+Number of children: 0
108108+Found child: optcomp
109109+Reading library: compiler-libs.optcomp
110110+Number of children: 0
111111+Found child: toplevel
112112+Reading library: compiler-libs.toplevel
113113+Number of children: 0
114114+Found child: native-toplevel
115115+Reading library: compiler-libs.native-toplevel
116116+Number of children: 0
117117+Parsed uri: ./lib/ocaml-compiler-libs/META
118118+Reading library: ocaml-compiler-libs
119119+Number of children: 5
120120+Found child: bytecomp
121121+Reading library: ocaml-compiler-libs.bytecomp
122122+Number of children: 0
123123+Found child: common
124124+Reading library: ocaml-compiler-libs.common
125125+Number of children: 0
126126+Found child: optcomp
127127+Reading library: ocaml-compiler-libs.optcomp
128128+Number of children: 0
129129+Found child: shadow
130130+Reading library: ocaml-compiler-libs.shadow
131131+Number of children: 0
132132+Found child: toplevel
133133+Reading library: ocaml-compiler-libs.toplevel
134134+Number of children: 0
135135+Parsed uri: ./lib/base/META
136136+Reading library: base
137137+Number of children: 3
138138+Found child: base_internalhash_types
139139+Reading library: base.base_internalhash_types
140140+Number of children: 0
141141+Found child: md5
142142+Reading library: base.md5
143143+Number of children: 0
144144+Found child: shadow_stdlib
145145+Reading library: base.shadow_stdlib
146146+Number of children: 0
147147+node_env_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/
148148+node_env_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO
149149+node_env_test.js: [INFO] init() finished
150150+--- Section 1: Default Environment ---
151151+node_env_test.js: [INFO] setup() for env default...
152152+node_env_test.js: [INFO] Fetching stdlib__Format.cmi
153153+154154+node_env_test.js: [INFO] Fetching stdlib__Sys.cmi
155155+156156+error while evaluating #enable "pretty";;
157157+error while evaluating #disable "shortvar";;
158158+node_env_test.js: [INFO] Setup complete
159159+node_env_test.js: [INFO] setup() finished for env default
160160+[PASS] default_setup: Default environment setup
161161+[PASS] default_define: # let default_val = 42;;
162162+ val default_val : int = 42
163163+164164+--- Section 2: Creating New Environments ---
165165+node_env_test.js: [INFO] create_env(env1)
166166+[PASS] create_env1: Created environment env1
167167+node_env_test.js: [INFO] setup() for env env1...
168168+error while evaluating #enable "pretty";;
169169+error while evaluating #disable "shortvar";;
170170+node_env_test.js: [INFO] Setup complete
171171+node_env_test.js: [INFO] setup() finished for env env1
172172+[PASS] setup_env1: Setup environment env1
173173+[PASS] env1_define: # let env1_val = 100;;
174174+ val env1_val : int = 100
175175+176176+--- Section 3: Environment Isolation ---
177177+Line 1, characters 0-11:
178178+Error: Unbound value default_val
179179+[PASS] isolation_default_from_env1: No leakage: # default_val;;
180180+181181+Line 1, characters 0-8:
182182+Error: Unbound value env1_val
183183+[PASS] isolation_env1_from_default: No leakage: # env1_val;;
184184+[PASS] default_still_works: # default_val;;
185185+ - : int = 42
186186+187187+--- Section 4: Multiple Environments ---
188188+node_env_test.js: [INFO] create_env(env2)
189189+node_env_test.js: [INFO] setup() for env env2...
190190+error while evaluating #enable "pretty";;
191191+error while evaluating #disable "shortvar";;
192192+node_env_test.js: [INFO] Setup complete
193193+node_env_test.js: [INFO] setup() finished for env env2
194194+[PASS] create_and_setup_env2: Created and setup env2
195195+[PASS] env2_define: # let env2_val = 200;;
196196+ val env2_val : int = 200
197197+198198+Line 1, characters 0-8:
199199+Error: Unbound value env1_val
200200+Hint: Did you mean env2_val?
201201+[PASS] isolation_env1_from_env2: No leakage: # env1_val;;
202202+203203+Line 1, characters 0-8:
204204+Error: Unbound value env2_val
205205+Hint: Did you mean env1_val?
206206+[PASS] isolation_env2_from_env1: No leakage: # env2_val;;
207207+208208+--- Section 5: List Environments ---
209209+node_env_test.js: [INFO] list_envs() -> [env2, default, env1]
210210+[PASS] list_envs_count: Found 3 environments
211211+[PASS] list_envs_has_default: env2, default, env1
212212+[PASS] list_envs_has_env1: env2, default, env1
213213+[PASS] list_envs_has_env2: env2, default, env1
214214+215215+--- Section 6: Destroy Environment ---
216216+node_env_test.js: [INFO] destroy_env(env2)
217217+[PASS] destroy_env2: Destroyed env2
218218+node_env_test.js: [INFO] list_envs() -> [default, env1]
219219+[PASS] env2_destroyed: default, env1
220220+[PASS] env1_still_exists: default, env1
221221+222222+--- Section 7: Reuse Environment Name ---
223223+node_env_test.js: [INFO] create_env(env2)
224224+node_env_test.js: [INFO] setup() for env env2...
225225+error while evaluating #enable "pretty";;
226226+error while evaluating #disable "shortvar";;
227227+node_env_test.js: [INFO] Setup complete
228228+node_env_test.js: [INFO] setup() finished for env env2
229229+230230+Line 1, characters 0-8:
231231+Error: Unbound value env2_val
232232+[PASS] new_env2_clean: Old value gone: # env2_val;;
233233+[PASS] new_env2_define: # let new_env2_val = 999;;
234234+ val new_env2_val : int = 999
235235+236236+=== Results: 21/21 tests passed ===
237237+SUCCESS: All environment tests passed!
+264
js_top_worker/test/node/node_env_test.ml
···11+(** Node.js test for multiple environment support.
22+33+ This tests that multiple isolated execution environments work correctly,
44+ including:
55+ - Creating and destroying environments
66+ - Isolation between environments (values defined in one don't leak to another)
77+ - Using the default environment
88+ - Listing environments
99+*)
1010+1111+open Js_top_worker
1212+open Js_top_worker_rpc.Toplevel_api_gen
1313+open Impl
1414+1515+(* Flusher that writes to process.stdout in Node.js *)
1616+let console_flusher (s : string) : unit =
1717+ let open Js_of_ocaml in
1818+ let process = Js.Unsafe.get Js.Unsafe.global (Js.string "process") in
1919+ let stdout = Js.Unsafe.get process (Js.string "stdout") in
2020+ let write = Js.Unsafe.get stdout (Js.string "write") in
2121+ ignore (Js.Unsafe.call write stdout [| Js.Unsafe.inject (Js.string s) |])
2222+2323+let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
2424+ fun f () ->
2525+ let stdout_buff = Buffer.create 1024 in
2626+ let stderr_buff = Buffer.create 1024 in
2727+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
2828+ let x = f () in
2929+ let captured =
3030+ {
3131+ Impl.stdout = Buffer.contents stdout_buff;
3232+ stderr = Buffer.contents stderr_buff;
3333+ }
3434+ in
3535+ Js_of_ocaml.Sys_js.set_channel_flusher stdout console_flusher;
3636+ (captured, x)
3737+3838+module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
3939+4040+module S : Impl.S = struct
4141+ type findlib_t = Js_top_worker_web.Findlibish.t
4242+4343+ let capture = capture
4444+4545+ let sync_get f =
4646+ let f = Fpath.v ("_opam/" ^ f) in
4747+ try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all)
4848+ with _ -> None
4949+5050+ let async_get f =
5151+ let f = Fpath.v ("_opam/" ^ f) in
5252+ try
5353+ let content =
5454+ In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all
5555+ in
5656+ Lwt.return (Ok content)
5757+ with e -> Lwt.return (Error (`Msg (Printexc.to_string e)))
5858+5959+ let create_file = Js_of_ocaml.Sys_js.create_file
6060+6161+ let import_scripts urls =
6262+ let open Js_of_ocaml.Js in
6363+ let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in
6464+ List.iter
6565+ (fun url ->
6666+ let (_ : 'a) =
6767+ Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |]
6868+ in
6969+ ())
7070+ urls
7171+7272+ let init_function _ () = failwith "Not implemented"
7373+ let findlib_init = Js_top_worker_web.Findlibish.init async_get
7474+7575+ let get_stdlib_dcs uri =
7676+ Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri
7777+ |> Result.to_list
7878+7979+ let require b v = function
8080+ | [] -> []
8181+ | packages ->
8282+ Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v
8383+ packages
8484+8585+ let path = "/static/cmis"
8686+end
8787+8888+module U = Impl.Make (S)
8989+9090+let start_server () =
9191+ let open U in
9292+ Logs.set_reporter (Logs_fmt.reporter ());
9393+ Logs.set_level (Some Logs.Info);
9494+ Server.init (IdlM.T.lift init);
9595+ Server.create_env (IdlM.T.lift create_env);
9696+ Server.destroy_env (IdlM.T.lift destroy_env);
9797+ Server.list_envs (IdlM.T.lift list_envs);
9898+ Server.setup (IdlM.T.lift setup);
9999+ Server.exec execute;
100100+ Server.complete_prefix complete_prefix;
101101+ Server.query_errors query_errors;
102102+ Server.type_enclosing type_enclosing;
103103+ Server.exec_toplevel exec_toplevel;
104104+ IdlM.server Server.implementation
105105+106106+module Client = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenClient ())
107107+108108+(* Test result tracking *)
109109+let total_tests = ref 0
110110+let passed_tests = ref 0
111111+112112+let test name check message =
113113+ incr total_tests;
114114+ let passed = check in
115115+ if passed then incr passed_tests;
116116+ let status = if passed then "PASS" else "FAIL" in
117117+ Printf.printf "[%s] %s: %s\n%!" status name message
118118+119119+let contains s substr =
120120+ try
121121+ let _ = Str.search_forward (Str.regexp_string substr) s 0 in
122122+ true
123123+ with Not_found -> false
124124+125125+let run_toplevel rpc env_id code =
126126+ let ( let* ) = IdlM.ErrM.bind in
127127+ let* result = Client.exec_toplevel rpc env_id ("# " ^ code) in
128128+ IdlM.ErrM.return result.script
129129+130130+let _ =
131131+ Printf.printf "=== Node.js Environment Tests ===\n\n%!";
132132+133133+ let rpc = start_server () in
134134+ let ( let* ) = IdlM.ErrM.bind in
135135+136136+ let init_config =
137137+ { stdlib_dcs = None; findlib_requires = []; findlib_index = None; execute = true }
138138+ in
139139+140140+ let test_sequence =
141141+ (* Initialize *)
142142+ let* _ = Client.init rpc init_config in
143143+144144+ Printf.printf "--- Section 1: Default Environment ---\n%!";
145145+146146+ (* Setup default environment *)
147147+ let* _ = Client.setup rpc "" in
148148+ test "default_setup" true "Default environment setup";
149149+150150+ (* Define a value in default environment *)
151151+ let* r = run_toplevel rpc "" "let default_val = 42;;" in
152152+ test "default_define" (contains r "val default_val : int = 42") r;
153153+154154+ Printf.printf "\n--- Section 2: Creating New Environments ---\n%!";
155155+156156+ (* Create a new environment "env1" *)
157157+ let* _ = Client.create_env rpc "env1" in
158158+ test "create_env1" true "Created environment env1";
159159+160160+ (* Setup env1 *)
161161+ let* _ = Client.setup rpc "env1" in
162162+ test "setup_env1" true "Setup environment env1";
163163+164164+ (* Define a different value in env1 *)
165165+ let* r = run_toplevel rpc "env1" "let env1_val = 100;;" in
166166+ test "env1_define" (contains r "val env1_val : int = 100") r;
167167+168168+ Printf.printf "\n--- Section 3: Environment Isolation ---\n%!";
169169+170170+ (* Check that default_val is NOT visible in env1 - the script output
171171+ should NOT contain "val default_val" if there was an error *)
172172+ let* r = run_toplevel rpc "env1" "default_val;;" in
173173+ test "isolation_default_from_env1" (not (contains r "val default_val"))
174174+ ("No leakage: " ^ String.sub r 0 (min 40 (String.length r)));
175175+176176+ (* Check that env1_val is NOT visible in default env *)
177177+ let* r = run_toplevel rpc "" "env1_val;;" in
178178+ test "isolation_env1_from_default" (not (contains r "val env1_val"))
179179+ ("No leakage: " ^ String.sub r 0 (min 40 (String.length r)));
180180+181181+ (* Check that default_val IS still visible in default env *)
182182+ let* r = run_toplevel rpc "" "default_val;;" in
183183+ test "default_still_works" (contains r "- : int = 42") r;
184184+185185+ Printf.printf "\n--- Section 4: Multiple Environments ---\n%!";
186186+187187+ (* Create a second environment *)
188188+ let* _ = Client.create_env rpc "env2" in
189189+ let* _ = Client.setup rpc "env2" in
190190+ test "create_and_setup_env2" true "Created and setup env2";
191191+192192+ (* Define value in env2 *)
193193+ let* r = run_toplevel rpc "env2" "let env2_val = 200;;" in
194194+ test "env2_define" (contains r "val env2_val : int = 200") r;
195195+196196+ (* Verify isolation between all three environments *)
197197+ let* r = run_toplevel rpc "env2" "env1_val;;" in
198198+ test "isolation_env1_from_env2" (not (contains r "val env1_val"))
199199+ ("No leakage: " ^ String.sub r 0 (min 40 (String.length r)));
200200+201201+ let* r = run_toplevel rpc "env1" "env2_val;;" in
202202+ test "isolation_env2_from_env1" (not (contains r "val env2_val"))
203203+ ("No leakage: " ^ String.sub r 0 (min 40 (String.length r)));
204204+205205+ Printf.printf "\n--- Section 5: List Environments ---\n%!";
206206+207207+ (* List all environments *)
208208+ let* envs = Client.list_envs rpc () in
209209+ test "list_envs_count" (List.length envs >= 3)
210210+ (Printf.sprintf "Found %d environments" (List.length envs));
211211+ test "list_envs_has_default" (List.mem "default" envs)
212212+ (String.concat ", " envs);
213213+ test "list_envs_has_env1" (List.mem "env1" envs)
214214+ (String.concat ", " envs);
215215+ test "list_envs_has_env2" (List.mem "env2" envs)
216216+ (String.concat ", " envs);
217217+218218+ Printf.printf "\n--- Section 6: Destroy Environment ---\n%!";
219219+220220+ (* Destroy env2 *)
221221+ let* _ = Client.destroy_env rpc "env2" in
222222+ test "destroy_env2" true "Destroyed env2";
223223+224224+ (* Verify env2 is gone from list *)
225225+ let* envs = Client.list_envs rpc () in
226226+ test "env2_destroyed" (not (List.mem "env2" envs))
227227+ (String.concat ", " envs);
228228+229229+ (* env1 should still exist *)
230230+ test "env1_still_exists" (List.mem "env1" envs)
231231+ (String.concat ", " envs);
232232+233233+ Printf.printf "\n--- Section 7: Reuse Environment Name ---\n%!";
234234+235235+ (* Re-create env2 *)
236236+ let* _ = Client.create_env rpc "env2" in
237237+ let* _ = Client.setup rpc "env2" in
238238+239239+ (* Old values should not exist - checking that it doesn't find the old value *)
240240+ let* r = run_toplevel rpc "env2" "env2_val;;" in
241241+ test "new_env2_clean" (not (contains r "- : int = 200"))
242242+ ("Old value gone: " ^ String.sub r 0 (min 40 (String.length r)));
243243+244244+ (* Define new value *)
245245+ let* r = run_toplevel rpc "env2" "let new_env2_val = 999;;" in
246246+ test "new_env2_define" (contains r "val new_env2_val : int = 999") r;
247247+248248+ IdlM.ErrM.return ()
249249+ in
250250+251251+ let promise = test_sequence |> IdlM.T.get in
252252+ (match Lwt.state promise with
253253+ | Lwt.Return (Ok ()) -> ()
254254+ | Lwt.Return (Error (InternalError s)) ->
255255+ Printf.printf "\n[ERROR] Test failed with: %s\n%!" s
256256+ | Lwt.Fail e ->
257257+ Printf.printf "\n[ERROR] Exception: %s\n%!" (Printexc.to_string e)
258258+ | Lwt.Sleep -> Printf.printf "\n[ERROR] Promise still pending\n%!");
259259+260260+ Printf.printf "\n=== Results: %d/%d tests passed ===\n%!" !passed_tests
261261+ !total_tests;
262262+ if !passed_tests = !total_tests then
263263+ Printf.printf "SUCCESS: All environment tests passed!\n%!"
264264+ else Printf.printf "FAILURE: Some tests failed.\n%!"
···11+node_incremental_test.js: [INFO] init()
22+Initializing findlib
33+node_incremental_test.js: [INFO] async_get: _opam/findlib_index
44+Loaded findlib_index findlib_index: 10 META files, 0 universes
55+node_incremental_test.js: [INFO] async_get: _opam/./lib/stdlib-shims/META
66+Parsed uri: ./lib/stdlib-shims/META
77+Reading library: stdlib-shims
88+Number of children: 0
99+node_incremental_test.js: [INFO] async_get: _opam/./lib/sexplib0/META
1010+Parsed uri: ./lib/sexplib0/META
1111+Reading library: sexplib0
1212+Number of children: 0
1313+node_incremental_test.js: [INFO] async_get: _opam/./lib/ppxlib/META
1414+Parsed uri: ./lib/ppxlib/META
1515+Reading library: ppxlib
1616+Number of children: 11
1717+Found child: __private__
1818+Reading library: ppxlib.__private__
1919+Number of children: 1
2020+Found child: ppx_foo_deriver
2121+Reading library: ppxlib.__private__.ppx_foo_deriver
2222+Number of children: 0
2323+Found child: ast
2424+Reading library: ppxlib.ast
2525+Number of children: 0
2626+Found child: astlib
2727+Reading library: ppxlib.astlib
2828+Number of children: 0
2929+Found child: metaquot
3030+Reading library: ppxlib.metaquot
3131+Number of children: 0
3232+Found child: metaquot_lifters
3333+Reading library: ppxlib.metaquot_lifters
3434+Number of children: 0
3535+Found child: print_diff
3636+Reading library: ppxlib.print_diff
3737+Number of children: 0
3838+Found child: runner
3939+Reading library: ppxlib.runner
4040+Number of children: 0
4141+Found child: runner_as_ppx
4242+Reading library: ppxlib.runner_as_ppx
4343+Number of children: 0
4444+Found child: stdppx
4545+Reading library: ppxlib.stdppx
4646+Number of children: 0
4747+Found child: traverse
4848+Reading library: ppxlib.traverse
4949+Number of children: 0
5050+Found child: traverse_builtins
5151+Reading library: ppxlib.traverse_builtins
5252+Number of children: 0
5353+node_incremental_test.js: [INFO] async_get: _opam/./lib/ppx_deriving/META
5454+Parsed uri: ./lib/ppx_deriving/META
5555+Reading library: ppx_deriving
5656+Number of children: 12
5757+Found child: api
5858+Reading library: ppx_deriving.api
5959+Number of children: 0
6060+Found child: create
6161+Reading library: ppx_deriving.create
6262+Number of children: 0
6363+Found child: enum
6464+Reading library: ppx_deriving.enum
6565+Number of children: 0
6666+Found child: eq
6767+Reading library: ppx_deriving.eq
6868+Number of children: 0
6969+Found child: fold
7070+Reading library: ppx_deriving.fold
7171+Number of children: 0
7272+Found child: iter
7373+Reading library: ppx_deriving.iter
7474+Number of children: 0
7575+Found child: make
7676+Reading library: ppx_deriving.make
7777+Number of children: 0
7878+Found child: map
7979+Reading library: ppx_deriving.map
8080+Number of children: 0
8181+Found child: ord
8282+Reading library: ppx_deriving.ord
8383+Number of children: 0
8484+Found child: runtime
8585+Reading library: ppx_deriving.runtime
8686+Number of children: 0
8787+Found child: show
8888+Reading library: ppx_deriving.show
8989+Number of children: 0
9090+Found child: std
9191+Reading library: ppx_deriving.std
9292+Number of children: 0
9393+node_incremental_test.js: [INFO] async_get: _opam/./lib/ppx_derivers/META
9494+Parsed uri: ./lib/ppx_derivers/META
9595+Reading library: ppx_derivers
9696+Number of children: 0
9797+node_incremental_test.js: [INFO] async_get: _opam/./lib/ocaml_intrinsics_kernel/META
9898+Parsed uri: ./lib/ocaml_intrinsics_kernel/META
9999+Reading library: ocaml_intrinsics_kernel
100100+Number of children: 0
101101+node_incremental_test.js: [INFO] async_get: _opam/./lib/ocaml/stdlib/META
102102+Parsed uri: ./lib/ocaml/stdlib/META
103103+Reading library: stdlib
104104+Number of children: 0
105105+node_incremental_test.js: [INFO] async_get: _opam/./lib/ocaml/compiler-libs/META
106106+Parsed uri: ./lib/ocaml/compiler-libs/META
107107+Reading library: compiler-libs
108108+Number of children: 5
109109+Found child: common
110110+Reading library: compiler-libs.common
111111+Number of children: 0
112112+Found child: bytecomp
113113+Reading library: compiler-libs.bytecomp
114114+Number of children: 0
115115+Found child: optcomp
116116+Reading library: compiler-libs.optcomp
117117+Number of children: 0
118118+Found child: toplevel
119119+Reading library: compiler-libs.toplevel
120120+Number of children: 0
121121+Found child: native-toplevel
122122+Reading library: compiler-libs.native-toplevel
123123+Number of children: 0
124124+node_incremental_test.js: [INFO] async_get: _opam/./lib/ocaml-compiler-libs/META
125125+Parsed uri: ./lib/ocaml-compiler-libs/META
126126+Reading library: ocaml-compiler-libs
127127+Number of children: 5
128128+Found child: bytecomp
129129+Reading library: ocaml-compiler-libs.bytecomp
130130+Number of children: 0
131131+Found child: common
132132+Reading library: ocaml-compiler-libs.common
133133+Number of children: 0
134134+Found child: optcomp
135135+Reading library: ocaml-compiler-libs.optcomp
136136+Number of children: 0
137137+Found child: shadow
138138+Reading library: ocaml-compiler-libs.shadow
139139+Number of children: 0
140140+Found child: toplevel
141141+Reading library: ocaml-compiler-libs.toplevel
142142+Number of children: 0
143143+node_incremental_test.js: [INFO] async_get: _opam/./lib/base/META
144144+Parsed uri: ./lib/base/META
145145+Reading library: base
146146+Number of children: 3
147147+Found child: base_internalhash_types
148148+Reading library: base.base_internalhash_types
149149+Number of children: 0
150150+Found child: md5
151151+Reading library: base.md5
152152+Number of children: 0
153153+Found child: shadow_stdlib
154154+Reading library: base.shadow_stdlib
155155+Number of children: 0
156156+node_incremental_test.js: [INFO] sync_get: _opam/lib/ocaml/dynamic_cmis.json
157157+node_incremental_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/
158158+node_incremental_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO
159159+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/camlinternalFormat.cmi
160160+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/camlinternalLazy.cmi
161161+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/camlinternalFormatBasics.cmi
162162+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/camlinternalMod.cmi
163163+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/std_exit.cmi
164164+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/stdlib.cmi
165165+node_incremental_test.js: [INFO] async_get: _opam/lib/ocaml/camlinternalOO.cmi
166166+node_incremental_test.js: [INFO] init() finished
167167+node_incremental_test.js: [INFO] setup() for env default...
168168+node_incremental_test.js: [INFO] Fetching stdlib__Format.cmi
169169+170170+node_incremental_test.js: [INFO] sync_get: _opam/lib/ocaml/stdlib__Format.cmi
171171+node_incremental_test.js: [INFO] Fetching stdlib__Sys.cmi
172172+173173+node_incremental_test.js: [INFO] sync_get: _opam/lib/ocaml/stdlib__Sys.cmi
174174+error while evaluating #enable "pretty";;
175175+error while evaluating #disable "shortvar";;
176176+node_incremental_test.js: [INFO] Setup complete
177177+node_incremental_test.js: [INFO] setup() finished for env default
178178+node_incremental_test.js: [INFO] Setup complete, testing incremental output...
179179+node_incremental_test.js: [INFO] Evaluating: let x = 1;; let y = 2;; let z = x + y;;
180180+node_incremental_test.js: [INFO] execute_incremental() for env_id=
181181+node_incremental_test.js: [INFO] OutputAt: loc=9 caml_ppf=val x : int = 1
182182+node_incremental_test.js: [INFO] OutputAt: loc=21 caml_ppf=val y : int = 2
183183+node_incremental_test.js: [INFO] OutputAt: loc=37 caml_ppf=val z : int = 3
184184+node_incremental_test.js: [INFO] execute_incremental() done for env_id=
185185+node_incremental_test.js: [INFO] Number of OutputAt callbacks: 3 (expected 3)
186186+node_incremental_test.js: [INFO] PASS: Got expected number of callbacks
187187+node_incremental_test.js: [INFO] PASS: Locations are in increasing order: 9, 21, 37
188188+node_incremental_test.js: [INFO] Final result caml_ppf: <none>
189189+node_incremental_test.js: [INFO] Final result stdout: <none>
190190+node_incremental_test.js: [INFO] Test completed successfully
+140
js_top_worker/test/node/node_incremental_test.ml
···11+(* Test incremental output *)
22+open Js_top_worker
33+open Js_top_worker_rpc.Toplevel_api_gen
44+open Impl
55+66+let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
77+ fun f () ->
88+ let stdout_buff = Buffer.create 1024 in
99+ let stderr_buff = Buffer.create 1024 in
1010+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
1111+1212+ let x = f () in
1313+ let captured =
1414+ {
1515+ Impl.stdout = Buffer.contents stdout_buff;
1616+ stderr = Buffer.contents stderr_buff;
1717+ }
1818+ in
1919+ (captured, x)
2020+2121+module S : Impl.S = struct
2222+ type findlib_t = Js_top_worker_web.Findlibish.t
2323+2424+ let capture = capture
2525+2626+ let sync_get f =
2727+ let f = Fpath.v ("_opam/" ^ f) in
2828+ Logs.info (fun m -> m "sync_get: %a" Fpath.pp f);
2929+ try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all)
3030+ with e ->
3131+ Logs.err (fun m ->
3232+ m "Error reading file %a: %s" Fpath.pp f (Printexc.to_string e));
3333+ None
3434+3535+ let async_get f =
3636+ let f = Fpath.v ("_opam/" ^ f) in
3737+ Logs.info (fun m -> m "async_get: %a" Fpath.pp f);
3838+ try
3939+ let content =
4040+ In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all
4141+ in
4242+ Lwt.return (Ok content)
4343+ with e ->
4444+ Logs.err (fun m ->
4545+ m "Error reading file %a: %s" Fpath.pp f (Printexc.to_string e));
4646+ Lwt.return (Error (`Msg (Printexc.to_string e)))
4747+4848+ let create_file = Js_of_ocaml.Sys_js.create_file
4949+5050+ let import_scripts urls =
5151+ let open Js_of_ocaml.Js in
5252+ let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in
5353+ List.iter
5454+ (fun url ->
5555+ let (_ : 'a) =
5656+ Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |]
5757+ in
5858+ ())
5959+ urls
6060+6161+ let init_function _ () = failwith "Not implemented"
6262+ let findlib_init = Js_top_worker_web.Findlibish.init async_get
6363+6464+ let get_stdlib_dcs uri =
6565+ Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri
6666+ |> Result.to_list
6767+6868+ let require b v = function
6969+ | [] -> []
7070+ | packages -> Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v packages
7171+7272+ let path = "/static/cmis"
7373+end
7474+7575+module U = Impl.Make (S)
7676+7777+let _ =
7878+ Logs.set_reporter (Logs_fmt.reporter ());
7979+ Logs.set_level (Some Logs.Info);
8080+8181+ let ( let* ) = IdlM.ErrM.bind in
8282+8383+ let init_config =
8484+ { stdlib_dcs = None; findlib_requires = []; findlib_index = None; execute = true }
8585+ in
8686+8787+ let x =
8888+ let* _ = IdlM.T.lift U.init init_config in
8989+ let* _ = IdlM.T.lift U.setup "" in
9090+ Logs.info (fun m -> m "Setup complete, testing incremental output...");
9191+9292+ (* Test incremental output with multiple phrases *)
9393+ let phrase_outputs = ref [] in
9494+ let on_phrase_output (p : U.phrase_output) =
9595+ Logs.info (fun m -> m " OutputAt: loc=%d caml_ppf=%s"
9696+ p.loc
9797+ (Option.value ~default:"<none>" p.caml_ppf));
9898+ phrase_outputs := p :: !phrase_outputs
9999+ in
100100+101101+ let code = "let x = 1;; let y = 2;; let z = x + y;;" in
102102+ Logs.info (fun m -> m "Evaluating: %s" code);
103103+104104+ let* result = U.execute_incremental "" code ~on_phrase_output in
105105+106106+ let num_callbacks = List.length !phrase_outputs in
107107+ Logs.info (fun m -> m "Number of OutputAt callbacks: %d (expected 3)" num_callbacks);
108108+109109+ (* Verify we got 3 callbacks (one per phrase) *)
110110+ if num_callbacks <> 3 then
111111+ Logs.err (fun m -> m "FAIL: Expected 3 callbacks, got %d" num_callbacks)
112112+ else
113113+ Logs.info (fun m -> m "PASS: Got expected number of callbacks");
114114+115115+ (* Verify the locations are increasing *)
116116+ let locs = List.rev_map (fun (p : U.phrase_output) -> p.loc) !phrase_outputs in
117117+ let sorted = List.sort compare locs in
118118+ if locs = sorted then
119119+ Logs.info (fun m -> m "PASS: Locations are in increasing order: %s"
120120+ (String.concat ", " (List.map string_of_int locs)))
121121+ else
122122+ Logs.err (fun m -> m "FAIL: Locations are not in order");
123123+124124+ (* Verify final result has expected values *)
125125+ Logs.info (fun m -> m "Final result caml_ppf: %s"
126126+ (Option.value ~default:"<none>" result.caml_ppf));
127127+ Logs.info (fun m -> m "Final result stdout: %s"
128128+ (Option.value ~default:"<none>" result.stdout));
129129+130130+ IdlM.ErrM.return ()
131131+ in
132132+133133+ let promise = x |> IdlM.T.get in
134134+ match Lwt.state promise with
135135+ | Lwt.Return (Ok ()) -> Logs.info (fun m -> m "Test completed successfully")
136136+ | Lwt.Return (Error (InternalError s)) -> Logs.err (fun m -> m "Error: %s" s)
137137+ | Lwt.Fail e ->
138138+ Logs.err (fun m -> m "Unexpected failure: %s" (Printexc.to_string e))
139139+ | Lwt.Sleep ->
140140+ Logs.err (fun m -> m "Error: Promise is still pending")
+188
js_top_worker/test/node/node_mime_test.expected
···11+=== Node.js MIME Infrastructure Tests ===
22+33+node_mime_test.js: [INFO] init()
44+Initializing findlib
55+Loaded findlib_index findlib_index: 10 META files, 0 universes
66+Parsed uri: ./lib/stdlib-shims/META
77+Reading library: stdlib-shims
88+Number of children: 0
99+Parsed uri: ./lib/sexplib0/META
1010+Reading library: sexplib0
1111+Number of children: 0
1212+Parsed uri: ./lib/ppxlib/META
1313+Reading library: ppxlib
1414+Number of children: 11
1515+Found child: __private__
1616+Reading library: ppxlib.__private__
1717+Number of children: 1
1818+Found child: ppx_foo_deriver
1919+Reading library: ppxlib.__private__.ppx_foo_deriver
2020+Number of children: 0
2121+Found child: ast
2222+Reading library: ppxlib.ast
2323+Number of children: 0
2424+Found child: astlib
2525+Reading library: ppxlib.astlib
2626+Number of children: 0
2727+Found child: metaquot
2828+Reading library: ppxlib.metaquot
2929+Number of children: 0
3030+Found child: metaquot_lifters
3131+Reading library: ppxlib.metaquot_lifters
3232+Number of children: 0
3333+Found child: print_diff
3434+Reading library: ppxlib.print_diff
3535+Number of children: 0
3636+Found child: runner
3737+Reading library: ppxlib.runner
3838+Number of children: 0
3939+Found child: runner_as_ppx
4040+Reading library: ppxlib.runner_as_ppx
4141+Number of children: 0
4242+Found child: stdppx
4343+Reading library: ppxlib.stdppx
4444+Number of children: 0
4545+Found child: traverse
4646+Reading library: ppxlib.traverse
4747+Number of children: 0
4848+Found child: traverse_builtins
4949+Reading library: ppxlib.traverse_builtins
5050+Number of children: 0
5151+Parsed uri: ./lib/ppx_deriving/META
5252+Reading library: ppx_deriving
5353+Number of children: 12
5454+Found child: api
5555+Reading library: ppx_deriving.api
5656+Number of children: 0
5757+Found child: create
5858+Reading library: ppx_deriving.create
5959+Number of children: 0
6060+Found child: enum
6161+Reading library: ppx_deriving.enum
6262+Number of children: 0
6363+Found child: eq
6464+Reading library: ppx_deriving.eq
6565+Number of children: 0
6666+Found child: fold
6767+Reading library: ppx_deriving.fold
6868+Number of children: 0
6969+Found child: iter
7070+Reading library: ppx_deriving.iter
7171+Number of children: 0
7272+Found child: make
7373+Reading library: ppx_deriving.make
7474+Number of children: 0
7575+Found child: map
7676+Reading library: ppx_deriving.map
7777+Number of children: 0
7878+Found child: ord
7979+Reading library: ppx_deriving.ord
8080+Number of children: 0
8181+Found child: runtime
8282+Reading library: ppx_deriving.runtime
8383+Number of children: 0
8484+Found child: show
8585+Reading library: ppx_deriving.show
8686+Number of children: 0
8787+Found child: std
8888+Reading library: ppx_deriving.std
8989+Number of children: 0
9090+Parsed uri: ./lib/ppx_derivers/META
9191+Reading library: ppx_derivers
9292+Number of children: 0
9393+Parsed uri: ./lib/ocaml_intrinsics_kernel/META
9494+Reading library: ocaml_intrinsics_kernel
9595+Number of children: 0
9696+Parsed uri: ./lib/ocaml/stdlib/META
9797+Reading library: stdlib
9898+Number of children: 0
9999+Parsed uri: ./lib/ocaml/compiler-libs/META
100100+Reading library: compiler-libs
101101+Number of children: 5
102102+Found child: common
103103+Reading library: compiler-libs.common
104104+Number of children: 0
105105+Found child: bytecomp
106106+Reading library: compiler-libs.bytecomp
107107+Number of children: 0
108108+Found child: optcomp
109109+Reading library: compiler-libs.optcomp
110110+Number of children: 0
111111+Found child: toplevel
112112+Reading library: compiler-libs.toplevel
113113+Number of children: 0
114114+Found child: native-toplevel
115115+Reading library: compiler-libs.native-toplevel
116116+Number of children: 0
117117+Parsed uri: ./lib/ocaml-compiler-libs/META
118118+Reading library: ocaml-compiler-libs
119119+Number of children: 5
120120+Found child: bytecomp
121121+Reading library: ocaml-compiler-libs.bytecomp
122122+Number of children: 0
123123+Found child: common
124124+Reading library: ocaml-compiler-libs.common
125125+Number of children: 0
126126+Found child: optcomp
127127+Reading library: ocaml-compiler-libs.optcomp
128128+Number of children: 0
129129+Found child: shadow
130130+Reading library: ocaml-compiler-libs.shadow
131131+Number of children: 0
132132+Found child: toplevel
133133+Reading library: ocaml-compiler-libs.toplevel
134134+Number of children: 0
135135+Parsed uri: ./lib/base/META
136136+Reading library: base
137137+Number of children: 3
138138+Found child: base_internalhash_types
139139+Reading library: base.base_internalhash_types
140140+Number of children: 0
141141+Found child: md5
142142+Reading library: base.md5
143143+Number of children: 0
144144+Found child: shadow_stdlib
145145+Reading library: base.shadow_stdlib
146146+Number of children: 0
147147+node_mime_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/
148148+node_mime_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO
149149+node_mime_test.js: [INFO] init() finished
150150+node_mime_test.js: [INFO] setup() for env default...
151151+node_mime_test.js: [INFO] Fetching stdlib__Format.cmi
152152+153153+node_mime_test.js: [INFO] Fetching stdlib__Sys.cmi
154154+155155+error while evaluating #enable "pretty";;
156156+error while evaluating #disable "shortvar";;
157157+node_mime_test.js: [INFO] Setup complete
158158+node_mime_test.js: [INFO] setup() finished for env default
159159+--- Section 1: exec_result Has mime_vals Field ---
160160+node_mime_test.js: [INFO] execute() for env_id=
161161+node_mime_test.js: [INFO] execute() done for env_id=
162162+[PASS] has_mime_vals_field: exec_result has mime_vals field
163163+[PASS] mime_vals_is_list: mime_vals is a list (length=0)
164164+[PASS] mime_vals_empty_no_output: mime_vals is empty when no MIME output
165165+166166+--- Section 2: MIME Type Definitions ---
167167+[PASS] mime_type_field: mime_val has mime_type field
168168+[PASS] encoding_noencoding: Noencoding variant works
169169+[PASS] data_field: mime_val has data field
170170+[PASS] encoding_base64: Base64 variant works
171171+172172+--- Section 3: Multiple Executions ---
173173+node_mime_test.js: [INFO] execute() for env_id=
174174+node_mime_test.js: [INFO] execute() done for env_id=
175175+node_mime_test.js: [INFO] execute() for env_id=
176176+node_mime_test.js: [INFO] execute() done for env_id=
177177+node_mime_test.js: [INFO] execute() for env_id=
178178+node_mime_test.js: [INFO] execute() done for env_id=
179179+[PASS] r1_mime_empty: First exec: mime_vals empty
180180+[PASS] r2_mime_empty: Second exec: mime_vals empty
181181+[PASS] r3_mime_empty: Third exec: mime_vals empty
182182+183183+--- Section 4: exec_toplevel Has mime_vals ---
184184+[PASS] toplevel_has_mime_vals: exec_toplevel_result has mime_vals field
185185+[PASS] toplevel_mime_vals_list: toplevel mime_vals is a list (length=0)
186186+187187+=== Results: 12/12 tests passed ===
188188+SUCCESS: All MIME infrastructure tests passed!
+208
js_top_worker/test/node/node_mime_test.ml
···11+(** Node.js test for MIME output infrastructure.
22+33+ This tests that the MIME output infrastructure is wired up correctly:
44+ - exec_result.mime_vals field is returned
55+ - Field is empty when no MIME output occurs
66+ - API types are correctly defined
77+88+ Note: The mime_printer library is used internally by the worker to
99+ capture MIME output. User code can call Mime_printer.push to produce
1010+ MIME values when the mime_printer package is loaded in the toplevel.
1111+*)
1212+1313+open Js_top_worker
1414+open Js_top_worker_rpc.Toplevel_api_gen
1515+open Impl
1616+1717+(* Flusher that writes to process.stdout in Node.js *)
1818+let console_flusher (s : string) : unit =
1919+ let open Js_of_ocaml in
2020+ let process = Js.Unsafe.get Js.Unsafe.global (Js.string "process") in
2121+ let stdout = Js.Unsafe.get process (Js.string "stdout") in
2222+ let write = Js.Unsafe.get stdout (Js.string "write") in
2323+ ignore (Js.Unsafe.call write stdout [| Js.Unsafe.inject (Js.string s) |])
2424+2525+let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
2626+ fun f () ->
2727+ let stdout_buff = Buffer.create 1024 in
2828+ let stderr_buff = Buffer.create 1024 in
2929+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
3030+ let x = f () in
3131+ let captured =
3232+ {
3333+ Impl.stdout = Buffer.contents stdout_buff;
3434+ stderr = Buffer.contents stderr_buff;
3535+ }
3636+ in
3737+ Js_of_ocaml.Sys_js.set_channel_flusher stdout console_flusher;
3838+ (captured, x)
3939+4040+module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
4141+4242+module S : Impl.S = struct
4343+ type findlib_t = Js_top_worker_web.Findlibish.t
4444+4545+ let capture = capture
4646+4747+ let sync_get f =
4848+ let f = Fpath.v ("_opam/" ^ f) in
4949+ try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all)
5050+ with _ -> None
5151+5252+ let async_get f =
5353+ let f = Fpath.v ("_opam/" ^ f) in
5454+ try
5555+ let content =
5656+ In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all
5757+ in
5858+ Lwt.return (Ok content)
5959+ with e -> Lwt.return (Error (`Msg (Printexc.to_string e)))
6060+6161+ let create_file = Js_of_ocaml.Sys_js.create_file
6262+6363+ let import_scripts urls =
6464+ let open Js_of_ocaml.Js in
6565+ let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in
6666+ List.iter
6767+ (fun url ->
6868+ let (_ : 'a) =
6969+ Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |]
7070+ in
7171+ ())
7272+ urls
7373+7474+ let init_function _ () = failwith "Not implemented"
7575+ let findlib_init = Js_top_worker_web.Findlibish.init async_get
7676+7777+ let get_stdlib_dcs uri =
7878+ Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri
7979+ |> Result.to_list
8080+8181+ let require b v = function
8282+ | [] -> []
8383+ | packages ->
8484+ Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v
8585+ packages
8686+8787+ let path = "/static/cmis"
8888+end
8989+9090+module U = Impl.Make (S)
9191+9292+let start_server () =
9393+ let open U in
9494+ Logs.set_reporter (Logs_fmt.reporter ());
9595+ Logs.set_level (Some Logs.Info);
9696+ Server.init (IdlM.T.lift init);
9797+ Server.create_env (IdlM.T.lift create_env);
9898+ Server.destroy_env (IdlM.T.lift destroy_env);
9999+ Server.list_envs (IdlM.T.lift list_envs);
100100+ Server.setup (IdlM.T.lift setup);
101101+ Server.exec execute;
102102+ Server.complete_prefix complete_prefix;
103103+ Server.query_errors query_errors;
104104+ Server.type_enclosing type_enclosing;
105105+ Server.exec_toplevel exec_toplevel;
106106+ IdlM.server Server.implementation
107107+108108+module Client = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenClient ())
109109+110110+(* Test result tracking *)
111111+let total_tests = ref 0
112112+let passed_tests = ref 0
113113+114114+let test name check message =
115115+ incr total_tests;
116116+ let passed = check in
117117+ if passed then incr passed_tests;
118118+ let status = if passed then "PASS" else "FAIL" in
119119+ Printf.printf "[%s] %s: %s\n%!" status name message
120120+121121+let run_exec rpc code =
122122+ let ( let* ) = IdlM.ErrM.bind in
123123+ let* result = Client.exec rpc "" code in
124124+ IdlM.ErrM.return result
125125+126126+let _ =
127127+ Printf.printf "=== Node.js MIME Infrastructure Tests ===\n\n%!";
128128+129129+ let rpc = start_server () in
130130+ let ( let* ) = IdlM.ErrM.bind in
131131+132132+ let init_config =
133133+ { stdlib_dcs = None; findlib_requires = []; findlib_index = None; execute = true }
134134+ in
135135+136136+ let test_sequence =
137137+ (* Initialize *)
138138+ let* _ = Client.init rpc init_config in
139139+ let* _ = Client.setup rpc "" in
140140+141141+ Printf.printf "--- Section 1: exec_result Has mime_vals Field ---\n%!";
142142+143143+ (* Basic execution returns a result with mime_vals *)
144144+ let* r = run_exec rpc {|let x = 1 + 2;;|} in
145145+ test "has_mime_vals_field" true "exec_result has mime_vals field";
146146+ test "mime_vals_is_list" (List.length r.mime_vals >= 0)
147147+ (Printf.sprintf "mime_vals is a list (length=%d)" (List.length r.mime_vals));
148148+ test "mime_vals_empty_no_output" (List.length r.mime_vals = 0)
149149+ "mime_vals is empty when no MIME output";
150150+151151+ Printf.printf "\n--- Section 2: MIME Type Definitions ---\n%!";
152152+153153+ (* Verify API types are accessible *)
154154+ let mime_val_example : mime_val = {
155155+ mime_type = "text/html";
156156+ encoding = Noencoding;
157157+ data = "<b>test</b>";
158158+ } in
159159+ test "mime_type_field" (mime_val_example.mime_type = "text/html")
160160+ "mime_val has mime_type field";
161161+ test "encoding_noencoding" (mime_val_example.encoding = Noencoding)
162162+ "Noencoding variant works";
163163+ test "data_field" (mime_val_example.data = "<b>test</b>")
164164+ "mime_val has data field";
165165+166166+ let mime_val_base64 : mime_val = {
167167+ mime_type = "image/png";
168168+ encoding = Base64;
169169+ data = "iVBORw0KGgo=";
170170+ } in
171171+ test "encoding_base64" (mime_val_base64.encoding = Base64)
172172+ "Base64 variant works";
173173+174174+ Printf.printf "\n--- Section 3: Multiple Executions ---\n%!";
175175+176176+ (* Verify mime_vals is fresh for each execution *)
177177+ let* r1 = run_exec rpc {|let a = 1;;|} in
178178+ let* r2 = run_exec rpc {|let b = 2;;|} in
179179+ let* r3 = run_exec rpc {|let c = 3;;|} in
180180+ test "r1_mime_empty" (List.length r1.mime_vals = 0) "First exec: mime_vals empty";
181181+ test "r2_mime_empty" (List.length r2.mime_vals = 0) "Second exec: mime_vals empty";
182182+ test "r3_mime_empty" (List.length r3.mime_vals = 0) "Third exec: mime_vals empty";
183183+184184+ Printf.printf "\n--- Section 4: exec_toplevel Has mime_vals ---\n%!";
185185+186186+ (* exec_toplevel also returns mime_vals *)
187187+ let* tr = Client.exec_toplevel rpc "" "# let z = 42;;" in
188188+ test "toplevel_has_mime_vals" true "exec_toplevel_result has mime_vals field";
189189+ test "toplevel_mime_vals_list" (List.length tr.mime_vals >= 0)
190190+ (Printf.sprintf "toplevel mime_vals is a list (length=%d)" (List.length tr.mime_vals));
191191+192192+ IdlM.ErrM.return ()
193193+ in
194194+195195+ let promise = test_sequence |> IdlM.T.get in
196196+ (match Lwt.state promise with
197197+ | Lwt.Return (Ok ()) -> ()
198198+ | Lwt.Return (Error (InternalError s)) ->
199199+ Printf.printf "\n[ERROR] Test failed with: %s\n%!" s
200200+ | Lwt.Fail e ->
201201+ Printf.printf "\n[ERROR] Exception: %s\n%!" (Printexc.to_string e)
202202+ | Lwt.Sleep -> Printf.printf "\n[ERROR] Promise still pending\n%!");
203203+204204+ Printf.printf "\n=== Results: %d/%d tests passed ===\n%!" !passed_tests
205205+ !total_tests;
206206+ if !passed_tests = !total_tests then
207207+ Printf.printf "SUCCESS: All MIME infrastructure tests passed!\n%!"
208208+ else Printf.printf "FAILURE: Some tests failed.\n%!"
+174
js_top_worker/test/node/node_ppx_test.expected
···11+=== Node.js PPX Tests ===
22+33+node_ppx_test.js: [INFO] init()
44+Initializing findlib
55+Loaded findlib_index findlib_index: 10 META files, 0 universes
66+Parsed uri: ./lib/stdlib-shims/META
77+Reading library: stdlib-shims
88+Number of children: 0
99+Parsed uri: ./lib/sexplib0/META
1010+Reading library: sexplib0
1111+Number of children: 0
1212+Parsed uri: ./lib/ppxlib/META
1313+Reading library: ppxlib
1414+Number of children: 11
1515+Found child: __private__
1616+Reading library: ppxlib.__private__
1717+Number of children: 1
1818+Found child: ppx_foo_deriver
1919+Reading library: ppxlib.__private__.ppx_foo_deriver
2020+Number of children: 0
2121+Found child: ast
2222+Reading library: ppxlib.ast
2323+Number of children: 0
2424+Found child: astlib
2525+Reading library: ppxlib.astlib
2626+Number of children: 0
2727+Found child: metaquot
2828+Reading library: ppxlib.metaquot
2929+Number of children: 0
3030+Found child: metaquot_lifters
3131+Reading library: ppxlib.metaquot_lifters
3232+Number of children: 0
3333+Found child: print_diff
3434+Reading library: ppxlib.print_diff
3535+Number of children: 0
3636+Found child: runner
3737+Reading library: ppxlib.runner
3838+Number of children: 0
3939+Found child: runner_as_ppx
4040+Reading library: ppxlib.runner_as_ppx
4141+Number of children: 0
4242+Found child: stdppx
4343+Reading library: ppxlib.stdppx
4444+Number of children: 0
4545+Found child: traverse
4646+Reading library: ppxlib.traverse
4747+Number of children: 0
4848+Found child: traverse_builtins
4949+Reading library: ppxlib.traverse_builtins
5050+Number of children: 0
5151+Parsed uri: ./lib/ppx_deriving/META
5252+Reading library: ppx_deriving
5353+Number of children: 12
5454+Found child: api
5555+Reading library: ppx_deriving.api
5656+Number of children: 0
5757+Found child: create
5858+Reading library: ppx_deriving.create
5959+Number of children: 0
6060+Found child: enum
6161+Reading library: ppx_deriving.enum
6262+Number of children: 0
6363+Found child: eq
6464+Reading library: ppx_deriving.eq
6565+Number of children: 0
6666+Found child: fold
6767+Reading library: ppx_deriving.fold
6868+Number of children: 0
6969+Found child: iter
7070+Reading library: ppx_deriving.iter
7171+Number of children: 0
7272+Found child: make
7373+Reading library: ppx_deriving.make
7474+Number of children: 0
7575+Found child: map
7676+Reading library: ppx_deriving.map
7777+Number of children: 0
7878+Found child: ord
7979+Reading library: ppx_deriving.ord
8080+Number of children: 0
8181+Found child: runtime
8282+Reading library: ppx_deriving.runtime
8383+Number of children: 0
8484+Found child: show
8585+Reading library: ppx_deriving.show
8686+Number of children: 0
8787+Found child: std
8888+Reading library: ppx_deriving.std
8989+Number of children: 0
9090+Parsed uri: ./lib/ppx_derivers/META
9191+Reading library: ppx_derivers
9292+Number of children: 0
9393+Parsed uri: ./lib/ocaml_intrinsics_kernel/META
9494+Reading library: ocaml_intrinsics_kernel
9595+Number of children: 0
9696+Parsed uri: ./lib/ocaml/stdlib/META
9797+Reading library: stdlib
9898+Number of children: 0
9999+Parsed uri: ./lib/ocaml/compiler-libs/META
100100+Reading library: compiler-libs
101101+Number of children: 5
102102+Found child: common
103103+Reading library: compiler-libs.common
104104+Number of children: 0
105105+Found child: bytecomp
106106+Reading library: compiler-libs.bytecomp
107107+Number of children: 0
108108+Found child: optcomp
109109+Reading library: compiler-libs.optcomp
110110+Number of children: 0
111111+Found child: toplevel
112112+Reading library: compiler-libs.toplevel
113113+Number of children: 0
114114+Found child: native-toplevel
115115+Reading library: compiler-libs.native-toplevel
116116+Number of children: 0
117117+Parsed uri: ./lib/ocaml-compiler-libs/META
118118+Reading library: ocaml-compiler-libs
119119+Number of children: 5
120120+Found child: bytecomp
121121+Reading library: ocaml-compiler-libs.bytecomp
122122+Number of children: 0
123123+Found child: common
124124+Reading library: ocaml-compiler-libs.common
125125+Number of children: 0
126126+Found child: optcomp
127127+Reading library: ocaml-compiler-libs.optcomp
128128+Number of children: 0
129129+Found child: shadow
130130+Reading library: ocaml-compiler-libs.shadow
131131+Number of children: 0
132132+Found child: toplevel
133133+Reading library: ocaml-compiler-libs.toplevel
134134+Number of children: 0
135135+Parsed uri: ./lib/base/META
136136+Reading library: base
137137+Number of children: 3
138138+Found child: base_internalhash_types
139139+Reading library: base.base_internalhash_types
140140+Number of children: 0
141141+Found child: md5
142142+Reading library: base.md5
143143+Number of children: 0
144144+Found child: shadow_stdlib
145145+Reading library: base.shadow_stdlib
146146+Number of children: 0
147147+node_ppx_test.js: [INFO] Adding toplevel modules for dynamic cmis from lib/ocaml/
148148+node_ppx_test.js: [INFO] toplevel modules: CamlinternalFormat, CamlinternalLazy, CamlinternalFormatBasics, CamlinternalMod, Std_exit, Stdlib, CamlinternalOO
149149+node_ppx_test.js: [INFO] init() finished
150150+node_ppx_test.js: [INFO] setup() for env default...
151151+node_ppx_test.js: [INFO] Fetching stdlib__Format.cmi
152152+153153+node_ppx_test.js: [INFO] Fetching stdlib__Sys.cmi
154154+155155+error while evaluating #enable "pretty";;
156156+error while evaluating #disable "shortvar";;
157157+node_ppx_test.js: [INFO] Setup complete
158158+node_ppx_test.js: [INFO] setup() finished for env default
159159+--- Loading PPX dynamically ---
160160+node_ppx_test.js: [INFO] Custom #require: loading ppx_deriving.show
161161+Loading package ppx_deriving.show
162162+lib.dir: show
163163+Loading package ppx_deriving.runtime
164164+lib.dir: runtime
165165+uri: ./lib/ppx_deriving/runtime/dynamic_cmis.json
166166+importScripts: ./lib/ppx_deriving/runtime/ppx_deriving_runtime.cma.js
167167+Finished loading package ppx_deriving.runtime
168168+Loading package ppx_deriving
169169+lib.dir: None
170170+uri: ./lib/ppx_deriving/dynamic_cmis.json
171171+Failed to unmarshal dynamic_cms from url ./lib/ppx_deriving/dynamic_cmis.json: Failed to fetch dynamic cmis
172172+uri: ./lib/ppx_deriving/show/dynamic_cmis.json
173173+importScripts: ./lib/ppx_deriving/show/ppx_deriving_show.cma.js
174174+node_ppx_test.js: [INFO] Error: TypeError: k is not a function
+259
js_top_worker/test/node/node_ppx_test.ml
···11+(** Node.js test for PPX preprocessing support.
22+33+ This tests that the PPX preprocessing pipeline works correctly with
44+ ppx_deriving. We verify that:
55+ 1. [@@deriving show] generates working pp and show functions
66+ 2. [@@deriving eq] generates working equal functions
77+ 3. Multiple derivers work together
88+ 4. Basic code still works through the PPX pipeline
99+1010+ The PPX pipeline in js_top_worker applies old-style Ast_mapper PPXs
1111+ followed by ppxlib-based PPXs via Ppxlib.Driver.map_structure.
1212+*)
1313+1414+open Js_top_worker
1515+open Js_top_worker_rpc.Toplevel_api_gen
1616+open Impl
1717+1818+(* Flusher that writes to process.stdout in Node.js *)
1919+let console_flusher (s : string) : unit =
2020+ let open Js_of_ocaml in
2121+ let process = Js.Unsafe.get Js.Unsafe.global (Js.string "process") in
2222+ let stdout = Js.Unsafe.get process (Js.string "stdout") in
2323+ let write = Js.Unsafe.get stdout (Js.string "write") in
2424+ ignore (Js.Unsafe.call write stdout [| Js.Unsafe.inject (Js.string s) |])
2525+2626+let capture : (unit -> 'a) -> unit -> Impl.captured * 'a =
2727+ fun f () ->
2828+ let stdout_buff = Buffer.create 1024 in
2929+ let stderr_buff = Buffer.create 1024 in
3030+ Js_of_ocaml.Sys_js.set_channel_flusher stdout (Buffer.add_string stdout_buff);
3131+ let x = f () in
3232+ let captured =
3333+ {
3434+ Impl.stdout = Buffer.contents stdout_buff;
3535+ stderr = Buffer.contents stderr_buff;
3636+ }
3737+ in
3838+ Js_of_ocaml.Sys_js.set_channel_flusher stdout console_flusher;
3939+ (captured, x)
4040+4141+module Server = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenServer ())
4242+4343+module S : Impl.S = struct
4444+ type findlib_t = Js_top_worker_web.Findlibish.t
4545+4646+ let capture = capture
4747+4848+ let sync_get f =
4949+ let f = Fpath.v ("_opam/" ^ f) in
5050+ try Some (In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all)
5151+ with _ -> None
5252+5353+ let async_get f =
5454+ let f = Fpath.v ("_opam/" ^ f) in
5555+ try
5656+ let content =
5757+ In_channel.with_open_bin (Fpath.to_string f) In_channel.input_all
5858+ in
5959+ Lwt.return (Ok content)
6060+ with e -> Lwt.return (Error (`Msg (Printexc.to_string e)))
6161+6262+ let create_file = Js_of_ocaml.Sys_js.create_file
6363+6464+ let import_scripts urls =
6565+ let open Js_of_ocaml.Js in
6666+ let import_scripts_fn = Unsafe.get Unsafe.global (string "importScripts") in
6767+ List.iter
6868+ (fun url ->
6969+ let (_ : 'a) =
7070+ Unsafe.fun_call import_scripts_fn [| Unsafe.inject (string url) |]
7171+ in
7272+ ())
7373+ urls
7474+7575+ let init_function _ () = failwith "Not implemented"
7676+ let findlib_init = Js_top_worker_web.Findlibish.init async_get
7777+7878+ let get_stdlib_dcs uri =
7979+ Js_top_worker_web.Findlibish.fetch_dynamic_cmis sync_get uri
8080+ |> Result.to_list
8181+8282+ let require b v = function
8383+ | [] -> []
8484+ | packages -> Js_top_worker_web.Findlibish.require ~import_scripts sync_get b v packages
8585+8686+ let path = "/static/cmis"
8787+end
8888+8989+module U = Impl.Make (S)
9090+9191+let start_server () =
9292+ let open U in
9393+ Logs.set_reporter (Logs_fmt.reporter ());
9494+ Logs.set_level (Some Logs.Info);
9595+ Server.init (IdlM.T.lift init);
9696+ Server.create_env (IdlM.T.lift create_env);
9797+ Server.destroy_env (IdlM.T.lift destroy_env);
9898+ Server.list_envs (IdlM.T.lift list_envs);
9999+ Server.setup (IdlM.T.lift setup);
100100+ Server.exec execute;
101101+ Server.complete_prefix complete_prefix;
102102+ Server.query_errors query_errors;
103103+ Server.type_enclosing type_enclosing;
104104+ Server.exec_toplevel exec_toplevel;
105105+ IdlM.server Server.implementation
106106+107107+module Client = Js_top_worker_rpc.Toplevel_api_gen.Make (Impl.IdlM.GenClient ())
108108+109109+(* Test state *)
110110+let passed_tests = ref 0
111111+let total_tests = ref 0
112112+113113+let test name condition message =
114114+ incr total_tests;
115115+ let status = if condition then (incr passed_tests; "PASS") else "FAIL" in
116116+ Printf.printf "[%s] %s: %s\n%!" status name message
117117+118118+let contains s substr =
119119+ try
120120+ let _ = Str.search_forward (Str.regexp_string substr) s 0 in
121121+ true
122122+ with Not_found -> false
123123+124124+let run_toplevel rpc code =
125125+ let ( let* ) = IdlM.ErrM.bind in
126126+ let* result = Client.exec_toplevel rpc "" ("# " ^ code) in
127127+ IdlM.ErrM.return result.script
128128+129129+let _ =
130130+ Printf.printf "=== Node.js PPX Tests ===\n\n%!";
131131+132132+ let rpc = start_server () in
133133+ let ( let* ) = IdlM.ErrM.bind in
134134+135135+ let init_config =
136136+ { stdlib_dcs = None; findlib_requires = []; findlib_index = None; execute = true }
137137+ in
138138+139139+ let test_sequence =
140140+ (* Initialize *)
141141+ let* _ = Client.init rpc init_config in
142142+ let* _ = Client.setup rpc "" in
143143+144144+ Printf.printf "--- Loading PPX dynamically ---\n%!";
145145+146146+ (* Dynamically load ppx_deriving.show - this should:
147147+ 1. Load the PPX deriver (registers with ppxlib)
148148+ 2. Auto-load ppx_deriving.runtime (via findlibish -ppx_driver predicate) *)
149149+ let* r = run_toplevel rpc "#require \"ppx_deriving.show\";;" in
150150+ test "load_ppx_show" (not (contains r "Error"))
151151+ (if contains r "Error" then r else "ppx_deriving.show loaded");
152152+153153+ (* Also load eq deriver *)
154154+ let* r = run_toplevel rpc "#require \"ppx_deriving.eq\";;" in
155155+ test "load_ppx_eq" (not (contains r "Error"))
156156+ (if contains r "Error" then r else "ppx_deriving.eq loaded");
157157+158158+ Printf.printf "\n--- Section 1: ppx_deriving.show ---\n%!";
159159+160160+ (* Test [@@deriving show] generates pp and show functions *)
161161+ let* r = run_toplevel rpc "type color = Red | Green | Blue [@@deriving show];;" in
162162+ test "show_type_defined" (contains r "type color") "type color defined";
163163+ test "show_pp_generated" (contains r "val pp_color")
164164+ (if contains r "val pp_color" then "pp_color generated" else r);
165165+ test "show_fn_generated" (contains r "val show_color")
166166+ (if contains r "val show_color" then "show_color generated" else r);
167167+168168+ (* Test the generated show function works *)
169169+ let* r = run_toplevel rpc "show_color Red;;" in
170170+ test "show_fn_works" (contains r "Red")
171171+ (String.sub r 0 (min 60 (String.length r)));
172172+173173+ (* Test with a record type *)
174174+ let* r = run_toplevel rpc "type point = { x: int; y: int } [@@deriving show];;" in
175175+ test "show_record_type" (contains r "type point") "point type defined";
176176+ test "show_record_pp" (contains r "val pp_point")
177177+ (if contains r "val pp_point" then "pp_point generated" else r);
178178+179179+ let* r = run_toplevel rpc "show_point { x = 10; y = 20 };;" in
180180+ test "show_record_works" (contains r "10" && contains r "20")
181181+ (String.sub r 0 (min 60 (String.length r)));
182182+183183+ Printf.printf "\n--- Section 2: ppx_deriving.eq ---\n%!";
184184+185185+ (* Test [@@deriving eq] generates equal function *)
186186+ let* r = run_toplevel rpc "type status = Active | Inactive [@@deriving eq];;" in
187187+ test "eq_type_defined" (contains r "type status") "status type defined";
188188+ test "eq_fn_generated" (contains r "val equal_status")
189189+ (if contains r "val equal_status" then "equal_status generated" else r);
190190+191191+ (* Test the generated equal function works *)
192192+ let* r = run_toplevel rpc "equal_status Active Active;;" in
193193+ test "eq_same_true" (contains r "true") r;
194194+195195+ let* r = run_toplevel rpc "equal_status Active Inactive;;" in
196196+ test "eq_diff_false" (contains r "false") r;
197197+198198+ Printf.printf "\n--- Section 3: Combined Derivers ---\n%!";
199199+200200+ (* Test multiple derivers on one type *)
201201+ let* r = run_toplevel rpc "type expr = Num of int | Add of expr * expr [@@deriving show, eq];;" in
202202+ test "combined_type" (contains r "type expr") "expr type defined";
203203+ test "combined_pp" (contains r "val pp_expr")
204204+ (if contains r "val pp_expr" then "pp_expr generated" else r);
205205+ test "combined_eq" (contains r "val equal_expr")
206206+ (if contains r "val equal_expr" then "equal_expr generated" else r);
207207+208208+ (* Test they work together *)
209209+ let* r = run_toplevel rpc "let e1 = Add (Num 1, Num 2);;" in
210210+ test "combined_value" (contains r "val e1") r;
211211+212212+ let* r = run_toplevel rpc "show_expr e1;;" in
213213+ test "combined_show_works" (contains r "Add" || contains r "Num")
214214+ (String.sub r 0 (min 80 (String.length r)));
215215+216216+ let* r = run_toplevel rpc "equal_expr e1 e1;;" in
217217+ test "combined_eq_self" (contains r "true") r;
218218+219219+ let* r = run_toplevel rpc "equal_expr e1 (Num 1);;" in
220220+ test "combined_eq_diff" (contains r "false") r;
221221+222222+ Printf.printf "\n--- Section 4: Basic Code Still Works ---\n%!";
223223+224224+ (* Verify normal code without PPX still works *)
225225+ let* r = run_toplevel rpc "let x = 1 + 2;;" in
226226+ test "basic_arithmetic" (contains r "val x : int = 3") r;
227227+228228+ let* r = run_toplevel rpc "let rec fib n = if n <= 1 then n else fib (n-1) + fib (n-2);;" in
229229+ test "recursive_fn" (contains r "val fib : int -> int") r;
230230+231231+ let* r = run_toplevel rpc "fib 10;;" in
232232+ test "fib_result" (contains r "55") r;
233233+234234+ Printf.printf "\n--- Section 5: Module Support ---\n%!";
235235+236236+ let* r = run_toplevel rpc "module M = struct type t = A | B [@@deriving show] end;;" in
237237+ test "module_with_deriving" (contains r "module M") r;
238238+239239+ let* r = run_toplevel rpc "M.show_t M.A;;" in
240240+ test "module_show_works" (contains r "A")
241241+ (String.sub r 0 (min 60 (String.length r)));
242242+243243+ IdlM.ErrM.return ()
244244+ in
245245+246246+ let promise = test_sequence |> IdlM.T.get in
247247+ (match Lwt.state promise with
248248+ | Lwt.Return (Ok ()) -> ()
249249+ | Lwt.Return (Error (InternalError s)) ->
250250+ Printf.printf "\n[ERROR] Test failed with: %s\n%!" s
251251+ | Lwt.Fail e ->
252252+ Printf.printf "\n[ERROR] Exception: %s\n%!" (Printexc.to_string e)
253253+ | Lwt.Sleep -> Printf.printf "\n[ERROR] Promise still pending\n%!");
254254+255255+ Printf.printf "\n=== Results: %d/%d tests passed ===\n%!" !passed_tests
256256+ !total_tests;
257257+ if !passed_tests = !total_tests then
258258+ Printf.printf "SUCCESS: All PPX tests passed!\n%!"
259259+ else Printf.printf "FAILURE: Some tests failed.\n%!"