Skip to content

Bincaml-based LSP#116

Open
katrinafyi wants to merge 83 commits intomainfrom
lsp
Open

Bincaml-based LSP#116
katrinafyi wants to merge 83 commits intomainfrom
lsp

Conversation

@katrinafyi
Copy link
Copy Markdown
Collaborator

This is a basic LSP which has:

  • go to definition of block/global/parameter variables, blocks, procedures, ADT cases, and declared type names.
  • hover information of the above terms,
  • autocompletion of the above terms,
  • symbol tree view,
  • procedure DOT graph generation,
  • syntax error highlighting from lexer / parser,
  • loading error highlighting (e.g., due to undefined variable errors),
  • debug highlight mode where every token is highlighted,
  • an attempt at a cached on-demand dependency system for internal LSP state.

You will need to configure the LSP in your editor for the appropriate file type. In Astronvim, it's something like

    config = {
      basillsp = {
        cmd={"/home/rina/progs/obasil/_build/default/lsp/bin/main.exe"}, filetypes={"basilir"}, root_dir = require("lspconfig.util").root_pattern("."),},

    },

inside the ~/.config/nvim/lua/plugins/astrolsp.lua file.

The DOT graph generation and highlighting mode is accessed using LSP code actions. In Astronvim, it's <Space>la.

Screenshots

Hover information

Screenshot_20260409_163445 Screenshot_20260409_163620

Symbol outline

image

Error and token highlighting

image image image

Code actions

image

Comment thread lsp/bin/main.ml
let open Lwt.Syntax in
let* () =
run_command ~notify_back
(Filename.quote_command "dot"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so lsp requires dot?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but only for the generate graph code action.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, xdg-open is pre cool, did not know about it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, won't work on macos tho ;-;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katrinafyi katrinafyi force-pushed the lsp branch 2 times, most recently from 04707ff to 3b73fdf Compare April 10, 2026 08:57
@JTrenerry
Copy link
Copy Markdown
Collaborator

JTrenerry commented Apr 14, 2026

flake doesn't have some of the packages needed, although my nix is dying right now so unsure if skill issue

@agle
Copy link
Copy Markdown
Owner

agle commented Apr 14, 2026

Go to definition of globals goes to declaration rather than last definition

Copy link
Copy Markdown
Owner

@agle agle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's nothing really objectionable here, its very tightly coupled to the concrete ast from bincaml which might produce some maintenence churn but we'll see. needs some docs

Comment thread lsp/bin/index.mld Outdated
Comment thread lsp/bin/index.mld Outdated
Comment thread lsp/bin/main.ml Outdated
Comment thread lsp/bin/main.ml Outdated
Comment thread lsp/raw_tokens.ml
Comment thread lsp/lsp_symbols.ml

let proc_lspsymbol_at_pos ~lspsymbols lsppos =
lspsymbols
|> List.find_opt (fun (sym : symbol) ->
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance improvement available by using Map.split

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's true but this only iterates through toplevel declarations so I'm gonna leave it for now. There's other O(n) work happening anyway.

Comment thread lsp/lsp_symbols.ml Outdated
Comment thread lsp/lsp_state.ml
in
let range = Option.value ~default:default_range range in
[ Lsp.Types.Diagnostic.create ~message ~range ~severity:Error () ]
in
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned previously but including here:

  • do we want to use algebraic effects to pass errors through
effect Warning of string * Lexing.pos * filename (*display and continue *)
effect Alarm of string * Lexing.pos  * filename (* display and continue *)
exception Error (*give up*)
  • is there a way to do this with a Logs reporter that includes a lexing position tag and displays the output as lines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be cool. I think Logs integration is somewhat easier if we don't need to dynamically choose between continuing or aborting. We can do this later.

Comment thread lsp/lsp_symbols.ml
|> Iter.flat_map (function StmtWithAttrib1 (stmt, _) -> of_stmt stmt))
|> Iter.map (fun ((range, name), context) ->
((range, name), Field, context @ ("within block" :: bcontext)))
in
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I think i would prefer a solution that embeds the neccessary information in bincaml ir with the minimal amount of bookeeping e.g. the token under cursor map. We still have work to do to properly support everything and its inherently lossy so its something to work towards in the future. loadir.ml is a mess and needs some substantial work to make it cleaner and separate its various concerns (esp. type inference, processing concrete ast, and ir building)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah :)

Copy link
Copy Markdown
Owner

@agle agle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(docs changes required from previous review)

@katrinafyi
Copy link
Copy Markdown
Collaborator Author

Go to definition of globals goes to declaration rather than last definition

With this, did you mean like a reassignment to $mem? Is there an example?

@agle
Copy link
Copy Markdown
Owner

agle commented Apr 17, 2026

Any register assignment where the register is a global will go to the declaration in global scope rather than the previous assignment. Not sure if LSP ui has support for multiple and it requires a control flow analysis so maybe ignore.

@katrinafyi
Copy link
Copy Markdown
Collaborator Author

Yeah that's expected atm but it would be really useful. I can definitely look into it after this PR.

Copy link
Copy Markdown
Collaborator Author

@katrinafyi katrinafyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments should be fixed now.

Comment thread lsp/lsp_symbols.ml

let proc_lspsymbol_at_pos ~lspsymbols lsppos =
lspsymbols
|> List.find_opt (fun (sym : symbol) ->
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's true but this only iterates through toplevel declarations so I'm gonna leave it for now. There's other O(n) work happening anyway.

Comment thread lsp/lsp_state.ml
in
let range = Option.value ~default:default_range range in
[ Lsp.Types.Diagnostic.create ~message ~range ~severity:Error () ]
in
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be cool. I think Logs integration is somewhat easier if we don't need to dynamically choose between continuing or aborting. We can do this later.

Comment thread lsp/lsp_symbols.ml
|> Iter.flat_map (function StmtWithAttrib1 (stmt, _) -> of_stmt stmt))
|> Iter.map (fun ((range, name), context) ->
((range, name), Field, context @ ("within block" :: bcontext)))
in
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah :)

@katrinafyi
Copy link
Copy Markdown
Collaborator Author

katrinafyi commented Apr 17, 2026

Has anyone seen this before?

Promoting                              
  _build/default/bincaml-lsp/lib/ocaml/5.4.1/site-lib/bincaml_lsp/.formatted/bincaml_lsp.ml
  to bincaml-lsp/lib/ocaml/5.4.1/site-lib/bincaml_lsp/bincaml_lsp.ml.
Error: failed to promote               
bincaml-lsp/lib/ocaml/5.4.1/site-lib/bincaml_lsp/bincaml_lsp.ml
Permission denied

ohh this happens when you have a nix build . output in the current folder.

Comment thread dune-project
(qcheck-stm :with-test))
qcheck-core
qcheck-alcotest
qcheck-stm)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants