Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] 🐕‍🦺 Cooltt Server v2.0 #289

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1bab8ff
Add socket communication code for a cooltt display server
TOTBWF Aug 10, 2021
856f19b
Add syntax for "Probe Holes"
TOTBWF Aug 10, 2021
bc5b903
Use a 'ref' to hold the cooltt server handle
TOTBWF Aug 10, 2021
331d422
Add 'probe_goal' family of tactics
TOTBWF Aug 10, 2021
2eb241f
Add server communication inside elaborator
TOTBWF Aug 10, 2021
bba9713
Server initialization + command line args
TOTBWF Aug 10, 2021
e42d970
Fix issue with emacs mode + extra args
TOTBWF Aug 10, 2021
115b838
Update protocol to perform more heavy lifting on the cooltt side
TOTBWF Aug 26, 2021
f2b3c71
Update zsh completion
favonia Aug 27, 2021
d79782d
Merge remote-tracking branch 'origin/main' into cooltt-server
TOTBWF Sep 2, 2021
5a8aeb1
Replace ProbeHole with Visualize
TOTBWF Sep 2, 2021
c30a940
Update syntax highlighting
TOTBWF Sep 2, 2021
10e4fff
Add ability to pass in server hostname, rework options handling a bit
TOTBWF Sep 2, 2021
a568129
Update message format to track changes to coolttviz
TOTBWF Sep 22, 2021
36717bd
Fix Test Suite
TOTBWF Sep 23, 2021
3d8480a
Remove old server code
TOTBWF Nov 24, 2021
2777db2
Add a message queue for emitting metadata from the refiner
TOTBWF Nov 24, 2021
2beea3a
Thread through a null job queue into the refiner
TOTBWF Nov 24, 2021
b03d146
Start implementing LSP server
TOTBWF Nov 25, 2021
af99c13
Fix some issues with the core lsp impl
TOTBWF Nov 25, 2021
3435bfa
Add 'Executor.ml' for running server actions
TOTBWF Nov 25, 2021
0b24b1c
Add emacs config for lsp integration
TOTBWF Nov 25, 2021
10e5aaf
Pull 'elaborate_typed_term' into the elaborator
TOTBWF Nov 25, 2021
c5f6839
Remove job queue in favor of metadata list
TOTBWF Nov 25, 2021
1306aac
Update main.ml with server command
TOTBWF Nov 25, 2021
a72fd57
Add type hovers
TOTBWF Nov 25, 2021
f018ff6
Tweak printing for subtypes with trivial cofibrations
TOTBWF Nov 25, 2021
f2b74ff
[WIP] Start working on holes/commands
TOTBWF Nov 28, 2021
26c8989
Add segment trees
favonia Nov 29, 2021
ea21988
Remove stupid parentheses
favonia Nov 29, 2021
a9e2309
Refactor and clean up code
favonia Nov 29, 2021
425b0f0
Add a README
favonia Nov 29, 2021
6ad5196
Add visualize code action
TOTBWF Nov 30, 2021
75cb430
Wire up code actions
TOTBWF Nov 30, 2021
45042be
Fix minor bug in SegmentTree
TOTBWF Nov 30, 2021
16ddd81
Add some extra stuff to the segment tree API
TOTBWF Nov 30, 2021
75e8550
Add pp_range
TOTBWF Nov 30, 2021
34fcefa
Rework how metadata is stored
TOTBWF Nov 30, 2021
0b0db06
Remove IntervalTree
TOTBWF Nov 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cooltt.opam
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ depends: [
"menhir" {>= "20180703"}
"uuseg" {>= "12.0.0"}
"uutf" {>= "1.0.2"}
"lsp" {>= "1.8.3"}
"odoc" {with-doc}
"bantorra"
"yuujinchou"
Expand Down
19 changes: 19 additions & 0 deletions emacs/cooltt-lsp.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
;;; cooltt-lsp --- -*- lexical-binding: t; -*-

;;; Commentary:
(require 'cooltt)
(require 'lsp-mode)

(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection (list cooltt-command "--mode" "server"))
:major-modes '(cooltt-mode)
:server-id 'cooltt))

(lsp-consistency-check cooltt-lsp)
(add-to-list 'lsp-language-id-configuration '(cooltt-mode . "cooltt"))

;;; Code:

(provide 'cooltt-lsp)
;;; cooltt-lsp.el ends here
4 changes: 2 additions & 2 deletions emacs/cooltt.el
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"Declaration keywords.")

(defconst cooltt-command-keywords
'("#fail" "#normalize" "#print" "#quit")
'("#fail" "#normalize" "#print" "#quit" "#viz")
"Command keywords.")

(defconst cooltt-expression-keywords
Expand Down Expand Up @@ -186,7 +186,7 @@ See `compilation-error-regexp-alist' for semantics.")

(defun cooltt-compile-options ()
"Compute the options to provide to cooltt."
(let (opts cooltt-options)
(let ((opts cooltt-options))
(when cooltt-debug
(push "--debug" opts))
opts))
Expand Down
73 changes: 73 additions & 0 deletions src/basis/SegmentTree.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module type POSITION =
sig
include Map.OrderedType
type range = { start : t; stop: t }
val cut_range_after : t -> range -> range
val cut_range_before : t -> range -> range
end

module type S = functor (Pos: POSITION) ->
sig
type !+'a t
val lookup : Pos.t -> 'a t -> 'a option
val containing : Pos.t -> 'a t -> 'a list
val of_list : (Pos.range * 'a) list -> 'a t
val empty : 'a t

val pp : (Format.formatter -> Pos.range -> unit) -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
end

module Make : S = functor (Pos : POSITION) ->
struct

module Range = struct
type t = Pos.range
let compare (x : t) (y : t) =
CCOrd.(Pos.compare x.start y.start <?> (opp Pos.compare, x.stop, y.stop))
end

module SegTree = CCMap.Make(Range)

type !+'a t = 'a SegTree.t

let lookup pos t =
match SegTree.find_last_opt (fun k -> Range.compare k {start = pos; stop = pos} <= 0) t with
| None -> None
| Some (r, v) ->
if Pos.compare r.stop pos >= 0 then Some v else None

let containing pos t =
(* FIXME: This is suboptimal *)
CCList.of_iter @@ SegTree.values @@ SegTree.filter (fun range _ -> range.start <= pos && pos <= range.stop) t
Comment on lines +39 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

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

This might not be correct, because the "same" span can show up as multiple segments, and "shadowed" spans will not even be in the tree.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would suggest eventually removing containing completely---or we have to rethink the entire framework.


let of_sorted_list l =
let rec loop tree stack l =
match stack, l with
| _, [] -> SegTree.add_list tree stack
| [], x :: l -> loop tree [x] l
| ((xk, xv) as x) :: stack, ((yk, _) as y :: l) ->
if Pos.compare yk.stop xk.start < 0 then
loop tree (y :: x :: stack) l
else if Pos.compare xk.stop yk.start < 0 then
loop (SegTree.add xk xv tree) stack (y :: l)
else
let tree =
if Pos.compare xk.start yk.start < 0 then
SegTree.add (Pos.cut_range_after yk.start xk) xv tree
else
tree
in
if Pos.compare xk.stop yk.stop > 0 then
loop tree (y :: (Pos.cut_range_before yk.stop xk, xv) :: stack) l
else
loop tree stack (y :: l)
in
loop SegTree.empty [] l

let of_list l =
of_sorted_list (CCList.sort (CCOrd.(>|=) Range.compare fst) l)

let empty = SegTree.empty

let pp pp_range pp_elem : Format.formatter -> 'a t -> unit = SegTree.pp pp_range pp_elem
end
20 changes: 20 additions & 0 deletions src/basis/SegmentTree.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module type POSITION =
sig
include Map.OrderedType
type range = { start : t; stop: t }
val cut_range_after : t -> range -> range
val cut_range_before : t -> range -> range
end

module type S = functor (Pos: POSITION) ->
sig
type !+'a t
val lookup : Pos.t -> 'a t -> 'a option
val containing : Pos.t -> 'a t -> 'a list
val of_list : (Pos.range * 'a) list -> 'a t
val empty : 'a t

val pp : (Format.formatter -> Pos.range -> unit) -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
end

module Make : S
2 changes: 1 addition & 1 deletion src/bin/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

(executables
(names main)
(libraries cooltt.frontend cmdliner))
(libraries cooltt.frontend cooltt.server cmdliner))

(install
(section bin)
Expand Down
64 changes: 41 additions & 23 deletions src/bin/main.ml
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
open Server
open Frontend
open Cmdliner

let _ =
Printexc.record_backtrace false;
()

type mode =
[ `Interactive
| `Scripting of [`Stdin | `File of string]
| `Server
]

type options =
{ mode : [`Interactive | `Scripting of [`Stdin | `File of string]];
{ mode : mode;
as_file : string option;
width : int;
debug_mode : bool }
debug_mode : bool;
}

let options mode as_file width debug_mode =
{ mode; as_file; width; debug_mode }

let main {mode; as_file; width; debug_mode} =
let main {mode; as_file; width; debug_mode; _} =
Format.set_margin width;
match
match mode with
| `Interactive -> Driver.do_repl ~as_file ~debug_mode
| `Scripting input -> Driver.load_file ~as_file ~debug_mode input
| `Interactive -> Driver.do_repl {as_file; debug_mode}
| `Scripting input -> Driver.load_file {as_file; debug_mode;} input
| `Server -> LanguageServer.run ()
with
| Ok () -> `Ok ()
| Error () -> `Error (false, "encountered one or more errors")
Expand All @@ -25,7 +37,7 @@ let opt_mode =
let doc =
"Set the interaction mode. "^
"The value $(docv) must be one of "^
"$(b,scripting) (default) or $(b,interactive)." in
"$(b,scripting) (default), $(b,interactive), or $(b,server)." in
Arg.(value & opt (some string) None & info ["m"; "mode"] ~doc ~docv:"MODE")

let opt_interactive =
Expand Down Expand Up @@ -63,28 +75,34 @@ let parse_mode =
function
| "interactive" -> `Interactive
| "scripting" -> `Scripting
| "server" -> `Server
| s -> `Nonexistent s

let quote s = "`" ^ s ^ "'"

let consolidate_options mode interactive width input_file as_file debug_mode : options Term.ret =
match Option.map parse_mode mode, interactive, width, input_file with
| (Some `Scripting | None), false, width, Some input_file ->
`Ok {mode = `Scripting input_file; as_file; width; debug_mode}
| (Some `Scripting | None), false, _, None ->
`Error (true, "scripting mode expects an input file")
| Some `Interactive, _, width, None | None, true, width, None ->
`Ok {mode = `Interactive; as_file; width; debug_mode}
| Some `Interactive, _, _, Some _ | None, true, _, _ ->
`Error (true, "interactive mode expects no input files")
| Some `Scripting, true, _, _ ->
`Error (true, "inconsistent mode assignment")
| Some (`Nonexistent s), _, _, _ ->
`Error (true, "no mode named " ^ quote s)
let consolidate_input_options mode interactive input_file : (mode, [`Msg of string]) result =
match Option.map parse_mode mode, interactive, input_file with
| (Some `Scripting | None), false, Some input_file ->
Ok (`Scripting input_file)
| (Some `Scripting | None), false, None ->
Error (`Msg "scripting mode expects an input file")
| Some `Interactive, _, None | None, true, None ->
Ok `Interactive
| Some `Server, false, None ->
Ok `Server
| Some `Interactive, _, Some _ | None, true, _ ->
Error (`Msg "interactive mode expects no input files")
| Some `Server, _, Some _ ->
Error (`Msg "server mode expects no input files")
| Some `Scripting, true, _ ->
Error (`Msg "inconsistent mode assignment")
| Some `Server, true, _ ->
Error (`Msg "inconsistent mode assignment")
| Some (`Nonexistent s), _, _ ->
Error (`Msg ("no mode named " ^ quote s))

let () =
let options : options Term.t =
Term.(ret (const consolidate_options $ opt_mode $ opt_interactive $ opt_width $ opt_input_file $ opt_as_file $ opt_debug))
in
let opts_input = Term.(term_result ~usage:true (const consolidate_input_options $ opt_mode $ opt_interactive $ opt_input_file)) in
let options : options Term.t = Term.(const options $ opts_input $ opt_as_file $ opt_width $ opt_debug) in
let t = Term.ret @@ Term.(const main $ options) in
Term.exit @@ Term.eval ~catch:true ~err:Format.std_formatter (t, myinfo)
18 changes: 18 additions & 0 deletions src/core/RefineMonad.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module S = Syntax
module St = RefineState
module Env = RefineEnv
module Err = RefineError
module Metadata = RefineMetadata
module Qu = Quote
module Conv = Conversion

Expand Down Expand Up @@ -167,3 +168,20 @@ let push_problem lbl =

let update_span loc =
scope @@ Env.set_location loc

let emit_tp loc tp =
match loc with
| Some loc ->
let* env = read in
let* qtp = quote_tp tp in
modify (St.add_type_at loc (Env.pp_env env) qtp)
| None ->
ret ()

let emit_hole ctx tp =
let* env = read in
match Env.location env with
| Some loc ->
let hole = St.Metadata.Hole { ctx; tp } in
modify (St.add_hole loc hole)
| None -> ret ()
3 changes: 3 additions & 0 deletions src/core/RefineMonad.mli
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ val with_pp : (Pp.env -> 'a m) -> 'a m
val expected_connective : RefineError.connective -> D.tp -> 'a m
val expected_field : D.sign -> S.t -> string list -> 'a m
val field_names_mismatch : expected:string list list -> actual:string list list -> 'a m

val emit_tp : LexingUtil.span option -> D.tp -> unit m
val emit_hole : (Ident.t * S.tp) list -> S.tp -> unit m
42 changes: 39 additions & 3 deletions src/core/RefineState.ml
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
open Basis
open CodeUnit

module IDMap = Map.Make (CodeUnitID)
module D = Domain
module S = Syntax

module Metadata = struct
type hole = Hole of { ctx : (Ident.t * S.tp) list; tp : S.tp }

type t = { holes : (LexingUtil.span * hole) list;
type_spans : (LexingUtil.span * Pp.env * S.tp) list
}

let init : t = { holes = []; type_spans = [] }

let holes metadata = metadata.holes

let type_spans metadata = metadata.type_spans

let add_hole span hole t = { t with holes = (span, hole) :: t.holes }

let add_type_at span env tp t = { t with type_spans = (span, env, tp) :: t.type_spans }
end


type t = { code_units : CodeUnit.t IDMap.t;
(** The binding namespace for each code unit. *)
namespaces : (Global.t Namespace.t) IDMap.t;
(** The import namespace for each code unit. *)
import_namespaces : (Global.t Namespace.t) IDMap.t }
import_namespaces : (Global.t Namespace.t) IDMap.t;
metadata : Metadata.t
}

let init =
{ code_units = IDMap.empty;
namespaces = IDMap.empty;
import_namespaces = IDMap.empty }
import_namespaces = IDMap.empty;
metadata = Metadata.init
}

let get_unit id st =
IDMap.find id st.code_units
Expand Down Expand Up @@ -57,10 +82,21 @@ let add_import id modifier import_id st =
let init_unit id st =
{ code_units = IDMap.add id (CodeUnit.create id) st.code_units;
namespaces = IDMap.add id Namespace.empty st.namespaces;
import_namespaces = IDMap.add id Namespace.empty st.import_namespaces }
import_namespaces = IDMap.add id Namespace.empty st.import_namespaces;
metadata = st.metadata
}

let get_import path st =
IDMap.find_opt path st.code_units

let is_imported path st =
IDMap.mem path st.code_units

let add_hole span hole st =
{ st with metadata = Metadata.add_hole span hole st.metadata }

let add_type_at span env tp st =
{ st with metadata = Metadata.add_type_at span env tp st.metadata }

let get_metadata st =
st.metadata
16 changes: 16 additions & 0 deletions src/core/RefineState.mli
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
open Basis
open CodeUnit
module D = Domain
module S = Syntax

type t

Expand All @@ -23,3 +25,17 @@ val is_imported : id -> t -> bool

(** Create and add a new code unit. *)
val init_unit : id -> t -> t


module Metadata : sig
type hole = Hole of { ctx : (Ident.t * S.tp) list; tp : S.tp }

type t

val holes : t -> (LexingUtil.span * hole) list
val type_spans : t -> (LexingUtil.span * Pp.env * S.tp) list
end

val add_hole : LexingUtil.span -> Metadata.hole -> t -> t
val add_type_at : LexingUtil.span -> Pp.env -> S.tp -> t -> t
val get_metadata : t -> Metadata.t
Loading