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

**URL:** <https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733>\
**Category:** Learning\
**Tags:** lwt, js\_of\_ocaml\
**Created:** [June 1, 2024, 10:03am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733 "2024-06-01T10:03:02Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 1, 2024, 10:03am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/1 "2024-06-01T10:03:02Z")

</div>

Hi,

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`](https://ocsigen.org/js_of_ocaml/3.5.1/api/js_of_ocaml/Js_of_ocaml/Sys_js/index.html#val-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.

---

<div class="post-metadata">

**Author:** ![silene](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/silene/32/2707_2.png) [@silene](https://discuss.ocaml.org/u/silene)\
**Post date:** [June 1, 2024, 11:20am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/2 "2024-06-01T11:20:13Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![silene](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/silene/32/2707_2.png) [@silene](https://discuss.ocaml.org/u/silene)\
**Post date:** [June 1, 2024, 11:27am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/3 "2024-06-01T11:27:27Z")

</div>

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

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

```

then your embedded code becomes

```ocaml
let () =
  initialize ();
  set_on_message (fun s -> process_input s)

```

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 2, 2024, 3:35pm UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/4 "2024-06-02T15:35:19Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [June 3, 2024, 7:05am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/5 "2024-06-03T07:05:33Z")

</div>

I think that the toplevel I have in [`brzo`](https://erratique.ch/software/brzo) does what you want, the code is [here](https://github.com/b0-system/brzo/blob/565a1e0eda680ee23cf10a45f31368230d2754a4/src-ocaml/brzo_b0_js_of_ocaml.ml#L143-L299). Basically you are looking for `JsooTop.execute`.

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [June 3, 2024, 7:21am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/6 "2024-06-03T07:21:56Z")

</div>

Ah and now that I think of it you may also want to have a look at `brr`’s [browser console](https://erratique.ch/software/brr/doc/ocaml_console.html) 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](https://github.com/dbuenzli/brr/blob/master/src/poke/brr_poke.ml).

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 3, 2024, 9:06pm UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/7 "2024-06-03T21:06:14Z")

</div>

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).

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [June 4, 2024, 7:24am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/8 "2024-06-04T07:24:12Z")

</div>

> [@rmonat](#):
>
> Just to be sure, is this a way to obtain an Ocaml repl/top-level?

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.

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 6, 2024, 12:47pm UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/9 "2024-06-06T12:47:23Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 17, 2025, 6:15am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/10 "2025-06-17T06:15:01Z")

</div>

I finally had time to play a bit with effects and the control inversion, I added a sample here: [Raphaël Monat / jsoo-inversion · GitLab](https://gitlab.com/rmonat/jsoo-inversion/)

The [web part](https://gitlab.com/rmonat/jsoo-inversion/-/blob/main/web/toplevel.ml?ref_type=heads) sets up `Sys_js.set_channel_filler` on stdin to perform the inversion. When this inversion is called, a blocking function `js_handle_stdin` is called. This function loops until an input is provided by the user in the web page.

In its current shape, the project does not work: the effect is not caught (`Stdlib.Effect.Unhandled` on `Dune __exe__ Toplevel.Input_inversion`; I have enabled effects in jsoo). Would anyone have an idea of workaround?

---

<div class="post-metadata">

**Author:** ![silene](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/silene/32/2707_2.png) [@silene](https://discuss.ocaml.org/u/silene)\
**Post date:** [June 17, 2025, 6:50am UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/11 "2025-06-17T06:50:07Z")

</div>

> [@rmonat](#):
>
> This function loops until an input is provided by the user in the web page.

I have no idea why effects do not work for you. But I can at least tell you that the above is a very bad idea. Indeed, there is no such thing as concurrent execution in javascript. (The closest thing to it is the use of workers, which is some kind of event-driven programming, but it will not solve your issue here.) When some code starts, no other code is executed until it exits. In other words, the following snippet is effectively an infinite loop:

```ocaml
let content = ref None
let rec js_handle_stdin () =
  match !content with
  | None -> js_handle_stdin ()

```

Consequently, if you were to call the function, your browser would soon tell you that some javascript code has been running for too long on the web page and it would ask you whether you want to kill it. (Either that or you would get a stack overflow.)

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [June 20, 2025, 3:50pm UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/12 "2025-06-20T15:50:57Z")

</div>

Thank you for your answer! Yes, this is indeed blocking. My rationale was to first have a way to handle the control inversion before having a clean, non-blocking version of `js_handle_stdin`.

---

<div class="post-metadata">

**Author:** ![rmonat](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/rmonat/32/3935_2.png) [@rmonat](https://discuss.ocaml.org/u/rmonat)\
**Post date:** [August 18, 2025, 1:21pm UTC](https://discuss.ocaml.org/t/handling-stdin-when-converting-a-repl-to-an-interactive-web-page/14733/13 "2025-08-18T13:21:44Z")

</div>

Update: regarding the effect not handled properly in `Sys_js.set_channel_filler`, this is [an expected behavior from the compiler](https://github.com/ocsigen/js_of_ocaml/issues/2068).

In the end, one can use the [`sync-message`](https://github.com/alexmojaki/sync-message) javascript library, providing synchronous communication between the main page thread and workers. I wrote a small [PoC](https://rmonat.gitlab.io/jsoo-inversion/) to show how to wrap all this around a toy CLI app, and the [source code is here](https://gitlab.com/rmonat/jsoo-inversion).
