Created a REPL for ReScript

Hey all,

I saw that there were a few threads opened 1-2 years ago asking about a REPL.

I wanted to use one and created a quick and dirty one awhile ago, but recently fixed it up a bit more.

rescript-repl - npm (npmjs.com)

I still need to clean things up and test some more and I have some rough blog posts detailing how I thought about structuring things. Nonetheless, it is functional at the moment and so I just wanted to put it out there for now.

Any constructive feedback is welcome.

14 Likes

Great.
CC @ryyppy and @cknitt, who are interested in bringing a repl directly into the compiler-blessed set of tools, alongside the playground.

2 Likes

Is there any link to a github repo? For some reason npm decided to not list the repo url and google can’t find it either.

4 Likes

Very nice! Thanks for making this.

1 Like

Yes, I like the repl. I want this tool since last year!

I want to more features like evaluate expression, get the type of variable, etc

I agree with you. After messing around a little with Idris2 a bit, I’ve been wanting some of those features as well (being able to view type definitions/documentation in the repl for all of the stdlib functions), but also being able to view the type of variables in your own project would be an extension of that.

I can imagine the second feature to be more straightforward; as after a quick glance at how to collect the types/docs/example usages for the Rescript API Js.Array-2 | ReScript API it seems that performing a webscrape of this page one time after rescript-repl is installed and executed, and saving the result for later lookup is the only option. When I looked into the files within the node_modules/rescript, I didn’t see anything that could be parsed which contains those same types/docs/example usages.

That sounds very intricate. Would it not work to use the analysis binary from the ReScript-LSP to analyze the code?

The automatic doc generation could probably be used for this once it lands. You’ll be able to point that at a ReScript file and get a full set of types/names/modules etc from that file, in JSON format.

2 Likes

Yeah, the auto doc generation sounds like it would work. When is that going to be included?

As for the ReScript-LSP, I took a quick glance at the link you included, but it wasn’t immediately apparent to me exactly what I’d need to do to gain access to types/docs/examples of the rescript API; however, that may be due to a lack of professional experience on my part.

I’ll give you some hints then:

  1. If you haven’t, download and install opam (Install OCaml)
  2. Do the “Analysis binary” steps here
  3. Then do cd analysis && make

so that you get a binary (analysis/rescript-editor-analysis.exe)

Then you can use that to analyze ReScript files. Have a look at Cli.ml for some examples.

Yeah, that gives me a better idea of how to go about it, thank you.

I haven’t gotten around to following the steps just yet, but if executing the .exe file provides a suitable json output then that would work.

Although, one concern would be the distribution of the OCaml binary along with the rescript-repl npm package.

These are a bit older (and the SO link provides a solution), but due to my OCaml inexperience I can’t say for sure before working on this whether it will actually be a hurdle or not.

A native binary will probably be faster and thus better for developer experience. GitHub CI will gladly build for Windows, macOS or Linux (x86_64 Arch) and if your REPL becomes interesting enough, we may be able to build it for macOS ARM as well with the ReScript Association’s own macOS ARM server. Silicon Macs could still run the x86_64 version via Rosetta though.

  • Alternatively, you could try to compile the whole binary with js_of_ocaml to JavaScript.

  • Or you could find out what parts you really need and convert them file by file with ReScript’s compiler like so: node_modules/.bin/bsc -format Test.ml > Test.res (may not always work)

There is certainly some research involved with all of the suggestions. But I suggest you try out locally what you can get out of the native binary first and then decide how to move on.

EDIT: You can also download the binaries directly from GitHub actions.

All right, so I followed the steps.

After having access to the .exe, this was the result.

I created a simple Add.res file

let add = (x: int, y: int): int => x + y

rescript-editor-analysis.exe documentSymbol Add.res
[
{
“name”: “add”,
“kind”: 12,
“range”: {“start”: {“line”: 0, “character”: 0}, “end”: {“line”: 0, “character”: 40}},
“selectionRange”: {“start”: {“line”: 0, “character”: 0}, “end”: {“line”: 0, “character”: 40}}
}
]

from the Cli.ml file you linked, I figured definition is the next command I would need.

definition: get definition for item in MyFile.res at line 10 column 2:

./rescript-editor-analysis.exe definition src/MyFile.res 10 2

rescript-editor-analysis.exe definition Add.res 0 0
null

I’ve tried running typeDefinition on a line/column in a file that has a type definition as well and the result is the same.

edit:

Took a quick glance at the available commands again and thought maybe this could yield the info needed:

signatureHelp: get signature help if available for position at line 10 column 2 in src/MyFile.res

./rescript-editor-analysis.exe signatureHelp src/MyFile.res 10 2

However, it just prints out the help string whenever I run that command.

Since you’re already deep into it, try this branch instead: [PoC] Doc extraction by zth · Pull Request #732 · rescript-lang/rescript-vscode · GitHub

And run:

./rescript-editor-analysis.exe extractDocs src/MyFile.res

That should give you a JSON blob back that you can use.

Ran

git checkout doc-extraction

Deleted the previous .exe and re-executed make.

executing rescript-editor-analysis.exe extractDocs Add.res isn’t yielding any output.

I referenced this file for including a docstring in the same manner:

I see that the implementation is written here:

But you’ll have to forgive me for not immediately being able to discern everything that’s going on or whether any of my efforts will contribute to any meaningful progress on resolving the kind of result I received if the expected output is a json blob.

This works for me locally:

./rescript-editor-analysis.exe extractDocs tests/src/DocExtractionRes.res

It produces:

{
  "name": "DocExtractionRes",
  "docstrings": ["Module level documentation goes here."],
  "items": [
  {
    "id": "DocExtractionRes.t",
    "kind": "type",
    "name": "t",
    "signature": "type t = {name: string, online: bool}",
    "docstrings": ["This type represents stuff."],
    "detail": 
    {
      "kind": "record",
      "fieldDocs": [{
        "fieldName": "name",
        "optional": false,
        "docstrings": ["The name of the stuff."],
        "signature": "string"
      }, {
        "fieldName": "online",
        "optional": false,
        "docstrings": ["Whether stuff is online."],
        "signature": "bool"
      }]
    }
  }, 
  {
    "id": "DocExtractionRes.make",
    "kind": "value",
    "name": "make",
    "signature": "let make: string => t",
    "docstrings": ["Create stuff.\n\n```rescript example\nlet stuff = make(\"My name\")\n```"]
  }, 
  {
    "id": "DocExtractionRes.asOffline",
    "kind": "value",
    "name": "asOffline",
    "signature": "let asOffline: t => t",
    "docstrings": ["Stuff goes offline."]
  }, 
  {
    "id": "SomeInnerModule.DocExtractionRes",
    "kind": "module",
    "item": 
    {
      "name": "SomeInnerModule",
      "docstrings": ["Another module level docstring here."],
      "items": [
      {
        "id": "DocExtractionRes.SomeInnerModule.status",
        "kind": "type",
        "name": "status",
        "signature": "type status = Started(t) | Stopped | Idle",
        "docstrings": [],
        "detail": 
        {
          "kind": "variant",
          "constructorDocs": [
          {
            "constructorName": "Started",
            "docstrings": ["If this is started or not"],
            "signature": "Started(t)"
          }, 
          {
            "constructorName": "Stopped",
            "docstrings": ["Stopped?"],
            "signature": "Stopped"
          }, 
          {
            "constructorName": "Idle",
            "docstrings": ["Now idle."],
            "signature": "Idle"
          }]
        }
      }, 
      {
        "id": "DocExtractionRes.SomeInnerModule.validInputs",
        "kind": "type",
        "name": "validInputs",
        "signature": "type validInputs = [\\n  | #\\\"needs-escaping\\\"\\n  | #something\\n  | #status(status)\\n  | #withPayload(int)\\n]",
        "docstrings": ["These are all the valid inputs."]
      }, 
      {
        "id": "DocExtractionRes.SomeInnerModule.callback",
        "kind": "type",
        "name": "callback",
        "signature": "type callback = (t, ~status: status) => unit",
        "docstrings": []
      }]
    }
  }, 
  {
    "id": "AnotherModule.DocExtractionRes",
    "kind": "module",
    "item": 
    {
      "name": "AnotherModule",
      "docstrings": ["Mighty fine module here too!"],
      "items": [
      {
        "id": "DocExtractionRes.AnotherModule.SomeInnerModule",
        "kind": "moduleAlias",
        "docstrings": ["This links another module. Neat."],
        "signature": "module LinkedModule = SomeInnerModule"
      }, 
      {
        "id": "DocExtractionRes.AnotherModule.callback",
        "kind": "type",
        "name": "callback",
        "signature": "type callback = SomeInnerModule.status => unit",
        "docstrings": ["Testing what this looks like."]
      }, 
      {
        "id": "DocExtractionRes.AnotherModule.isGoodStatus",
        "kind": "value",
        "name": "isGoodStatus",
        "signature": "let isGoodStatus: SomeInnerModule.status => bool",
        "docstrings": []
      }, 
      {
        "id": "DocExtractionRes.AnotherModule.someVariantWithInlineRecords",
        "kind": "type",
        "name": "someVariantWithInlineRecords",
        "signature": "type someVariantWithInlineRecords = SomeStuff({offline: bool})",
        "docstrings": ["Trying how it looks with an inline record in a variant."],
        "detail": 
        {
          "kind": "variant",
          "constructorDocs": [
          {
            "constructorName": "SomeStuff",
            "docstrings": ["This has inline records..."],
            "signature": "SomeStuff"
          }]
        }
      }, 
      {
        "id": "DocExtractionRes.AnotherModule.domRoot",
        "kind": "type",
        "name": "domRoot",
        "signature": "type domRoot = unit => ReactDOM.Client.Root.t",
        "docstrings": ["Callback to get the DOM root..."]
      }]
    }
  }, 
  {
    "id": "ModuleWithThingsThatShouldNotBeExported.DocExtractionRes",
    "kind": "module",
    "item": 
    {
      "name": "ModuleWithThingsThatShouldNotBeExported",
      "docstrings": [],
      "items": [
      {
        "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.t",
        "kind": "type",
        "name": "t",
        "signature": "type t",
        "docstrings": ["The type t is stuff."]
      }, 
      {
        "id": "DocExtractionRes.ModuleWithThingsThatShouldNotBeExported.make",
        "kind": "value",
        "name": "make",
        "signature": "let make: unit => t",
        "docstrings": ["The maker of stuff!"]
      }]
    }
  }]
}

Maybe you haven’t build the ReScript code? It needs to be built, it uses the compiler artifacts and not just the syntax, so anything you extract from needs to have compiled first.

make test builds the test files I referred to above in the VSCode repo.

By build the ReScript code, do you mean the rescript-vscode project or the project that has the Add.res file?

In any case, I tried npm run compile followed by cd analysis && make test as you suggested.

make test
rm -f rescript-editor-analysis.exe
dune build
cp _build/install/default/bin/rescript-editor-analysis rescript-editor-analysis.exe
make -C tests test
node_modules/.bin/rescript
./test.sh
:white_check_mark: No unstaged tests difference.
make -C reanalyze test
make -C examples/deadcode test
node_modules/.bin/rescript
./test.sh
Entering directory ‘/rescript-projs/rescript-vscode/analysis’
Leaving directory ‘/rescript-projs/rescript-vscode/analysis’
Entering directory ‘/Desktop/dev/rescript-projs/rescript-vscode/analysis’
Leaving directory ‘/rescript-projs/rescript-vscode/analysis’
:white_check_mark: No unstaged tests difference.
make -C examples/termination test
node_modules/.bin/rescript
./test.sh
Entering directory ‘/rescript-projs/rescript-vscode/analysis’
Leaving directory ‘/rescript-projs/rescript-vscode/analysis’
:white_check_mark: No unstaged tests difference.

Running the .exe on Add.res is still not yielding any json after those steps.