Handling `stdin` when converting a REPL to an interactive web page


I have a REPL written in OCaml that I would like to embed in a web page through Js_of_ocaml. The REPL needs some initial data to get started. Then it enters a loop where it awaits commands on stdin and outputs results on stdout. I would like to keep the REPL untouched and write a wrapper that will handle interaction with the web page.

The output to stdout can easily be captured by Sys_js.set_channel_flusher stdout append where append adds the new output to the web page.
However, I don’t see how to handle the input: I would like to wait from the user to type some input and the enter key before passing this to the REPL (with itself called read_line). Waiting on the user can be done with onkeydown and a Dom_html.handler. However, I don’t see how make this handler interact with the Sys_js.set_channel_filler for stdin? Maybe using Lwt?

NB: I plan to use some web workers to separate the DOM handling and the REPL, in order to avoid freezes during long computations on the REPL side. I don’t think it changes this specific issue though.

I think you are making your life much more complicated than needed by wanting to use the main loop of your REPL directly. Just make your REPL’s initialization function public, as well as the core processing function from the main loop. That way, you won’t have to worry about control inversion. This might require to modify a bit the REPL, but the changes could be as innocuous as putting a few function declarations in .mli files.

In case what I am saying is a bit too abstract, let’s make it clearer. Assume the main code of your REPL is

let () =
  initialize ();
  while true do
    let s = read_string () in
    process_input s

then your embedded code becomes

let () =
  initialize ();
  set_on_message (fun s -> process_input s)
1 Like

Thank you @silene for your answer! Unfortunately the application is quite complex and extracting the processing function would take a significant time. That’s why I am looking for a way to invert the control, if possible.

I think that the toplevel I have in brzo does what you want, the code is here. Basically you are looking for JsooTop.execute.

1 Like

Ah and now that I think of it you may also want to have a look at brr’s browser console which also has input and toplevel execution in different contexts similar to what you want to do with webworkers (here the browser dev tool extension handles input and your webpage execution). These are the execution bits.

Thank you @dbuenzli for your answer. Just to be sure, is this a way to obtain an Ocaml repl/top-level?

To clarify, I’m trying to expose a REPL I wrote by myself, which targets another language (implemented through an interpreter in OCaml).

Indeed, sorry I read your message too quickly. Forget about what I said it’s OT :–) I’d say @silene puts you on the right track here.

That being said you could try to invert the control using an OCaml 5 effect. Basically you raise an effect from the function you give to Sys_js.set_channel_filler and invoke the continuation whenever some data you get from the handler.

1 Like

Thank you @dbuenzli, I’ll try to play with effects and report back when this is done.