Could merlin be compiled with js_of_ocaml and work in the browser

Not at all. I’m not quite sure if the question relates to just interacting with web workers or interacting with web workers that are running the top-level like in try-eio. If it is the former, then I highly recommend the Brr library for working with browser APIs in jsoo. There is an example of communicating with a web worker too.

If the question is more related to how try-eio works then this is actually all abstracted away behind GitHub - patricoferris/js_top_worker at 9b2be884deb1fcbb3b869609a72fab7df6eb45d0 (sorry we’re working with lots of forks etc. hence the submodules in the repository). The idea being that js_top_worker provides a uniform interface to the “thing” running the top-level (whether it be in a web-worker or elsewhere). Right now it only supports a web worker so it might seem like overkill, but you could imagine instead having the top-level on the server and communicating via a web socket or something (you could then properly support parallelism with domains).

The library is a little hard to understand, but it is using rpclib to do all this abstraction. The README gives quite a good overview. But for example, you can see the function you are describing:

With callbacks being implemented with the postMessage as you mentioned:

And you receive messages via the onMessage event handler

The client implementation is then defined using the Web worker bindings provided by brr.

And finally in the client you can initialise the worker RPC and then call things like the exec function. Hopefully that’s helpful, let me know if I misunderstood or can further clarify :))

2 Likes

Many thanks! I am starting checking learn-ocaml, but it seems that the project is massive enough where a quick glance didn’t land me on the relevant part of code.

If it’s not too much to ask, could you give a list of files I should be start reading to understand how the compilation is performed in that project?

I am afraid I am too busy at the moment to find the time to guide you through the code, which I would otherwise have been happy to. It’s indeed kind of a beast :grimacing: .

The part related to running the user user-code is in the grader subdirectory. Also this the upcoming version is able to mix that with pre-compiled bytecode.

Good luck! :wink:

1 Like

Many thanks and no problem! I think with some time and patient I will be eventually be able to figure it out.

This narrows down the target a lot! Will be starting reading the code from their.

Wow, it’s awesome. I will definitely also check the grader folder on this PR.

In this particular demo, on the client side, is CodeMirror directly talking to Merlin-Js, or are both talking LSP ?

[I am trying to build something similar with ReScript, and am trying to figure out how much of the client side can be re-used.]

If I am understanding correctly (correct me if not!), the client (in this case CodeMirror extension) is directly talking to merlin running in a web worker. An example of the data path is:

2 Likes

Correct understanding. Thanks for the code pointers!

@patricoferris : I have a basic followup question on Try Eio . I think there is an easy way to do this, but it is not obvious to me.

In side the browser, inside the Try-EIO codemirror window, I would like to define a function

let my_alert (x: string) = 

  BEGIN_MAGIC
  Js/console.log($x)
  END_MAGIC

Is something like this possible? I.e. my goals here are:

  1. do not write ML that requires recompiling Try-EIO

  2. define a FFI function in pure JS, with some $_var_name to indicate substituting in an OCaml value

  3. somehow bind this to a function we can call at the Try-EIO top level

If I understand correctly, you can achieve something a bit like this by wrapping the jsoo FFI functions that are there, just not exposed in any meaningful way right now (like by the Js_of_ocaml.Js module or Brr’s Jv module). Reusing the code from Jv this will work in try-eio today:

type t
external repr : 'a -> t = "%identity"
external pure_js_expr : string -> 'a = "caml_pure_js_expr"
external apply : t -> t array -> 'a = "caml_js_fun_call"
external get : t -> string -> t = "caml_js_get"
external call : t -> string -> t array -> 'a = "caml_js_meth_call"
external to_int : t -> int = "%identity"

let global = pure_js_expr "globalThis"

module Console = struct
  let console = get global "console"

  let log i = ignore (call console "log" [| repr i |])
end

let add_one (x : int) = 
  let fn = pure_js_expr "((x) => x + 1)" in
  apply fn [| repr x |] |> to_int

let () =
  let i = 1 + 1 in
  Console.log i;
  let j = add_one i in
  print_int j

A bit long, but I’m trying to show both defining JS functions and reusing browser APIs.

Not quite as slick as your variable name substitution but it works. If it was a feature you really wanted I would recommend pulling in Brr along with Eio and using the proper FFI there and as a bonus you will have all of Brr’s browser bindings to call directly in the editor too.

2 Likes

This example was perfect. Thanks!

For anyone running into issues: note, the evaluation is happening in the context of the webworker, not the DOM, so we can’t do things like window.alert

Work in progress for our use case (many thanks to GitHub - voodoos/merlin-js)

Please note that this is still an early prototype and is hosted as a password protected preview deployment on Vercel.com. The link above should work since it contains an authentication token, but links generated using the “generate snippet link” button would ask for authentication in most cases.