···100100 lexbuf ->
101101 (Code.t, Reporter.Message.t Asai.Diagnostic.t) Result.t
102102 = fun ?(stop_on_err = true) lexbuf ->
103103-104104- (* a checkpoint is an intermediate or final state of the parser *)
105103 let initial_checkpoint = (Grammar.Incremental.main lexbuf.lex_curr_p) in
106106-107107- (* The idea is simple: push opening delimiters onto the stack, pop them
108108- once they are closed. Upon encountering an error, the reported range
109109- includes the last unclosed delimiter.
110110- *)
111104 let delim_stack = Stack.create () in
112112-113113- (* The recursive loop.*)
114114- let fail
115115- : _ I.checkpoint ->
116116- (Code.t, Reporter.Message.t Asai.Diagnostic.t) Result.t
117117- = fun checkpoint ->
118118- match checkpoint with
119119- (*
120120- Consider the source file
121121-122122- ```
123123- \def\foo{
124124-125125- \p{
126126- lorem ipsum dolor sit amet
127127- }
128128- ```
129129-130130- Before this patch, the error reported when encountering this was
131131- undecipherable, since it just hit EOF and then reported unexpected ``.
132132-133133- The diagnostic constructed below enlarges the range of the reported
134134- error to the last unclosed delimiter. This makes the error much more
135135- understandable
136136-137137- ```
138138- ■ ./trees/comment.tree
139139- 1 | \def\foo{
140140- ^ This delimiter is never closed
141141- 2 |
142142- 3 | \p{
143143- 4 | lorem ipsum dolor sit amet
144144- 5 | }
145145- 6 |
146146- ^ Did you forget to close it?
147147- ```
148148-149149- However, for implementing an LSP this is insufficient. When
150150- encountering an error, we produce no value. We need an
151151- error-resilient parser. We want the parse tree to look something like
152152- this:
153153-154154- 1 TREE
155155- 2 FUN_SPEC
156156- 3 NAME "foo"
157157- 4 ARG_LIST []
158158- 5 BODY ERROR
159159- 6 PRIM PARAGRAPH
160160- 7 TEXT "lorem ipsum dolor sit amet"
161161-162162- The tricky bit here is that we only know that there is a parse error
163163- when encountering EOF. How can we derive the fact that the erroneous
164164- construct is the function definition, and that the error should be
165165- localised at the unclosed brace?
166166-167167- We would need to inspect the parser state and recognize that we are
168168- currently parsing a function whose body never gets closed, produce a
169169- value like at line 5, and resume:
170170- https://gallium.inria.fr/%7Efpottier/menhir/manual.html#sec%3Aincremental%3Aupdating
171171-172172- This illustrates the problem with having such strictly typed parse
173173- tree. Code.t was not able to represent such a failed parse. (I have
174174- sinced added an error node, so it is now technically possible for
175175- this specific usecase, but so far it is unused. Furhtermore, the code
176176- can't represent, for example, trie paths that failed to parse).
177177-178178- Aside: Inspired by rust-analyzer and rowan, I am playing around
179179- with github.com/kentookura/orochi. In rust-analyzer, they use such
180180- an untyped API and layer a typed AST over it. I haven't gotten
181181- around to that part yet.
182182-183183- Now the use case of the incremental API becomes apparent. It allows
184184- us to inspect the parser state and travel in time, look back, look
185185- forward, anything we want.
186186-187187- Here are the action items:
188188-189189- - Understand what kinds of syntax errors can occur when typing. These
190190- are the erroneous inputs we are most likely to encounter, as we want
191191- to make an LSP server. This is stuff like unclosed delimiters, etc.
192192-193193- - Understand how we should best recover from erroneous input, for
194194- example:
195195-196196- ```
197197- \ul{
198198- \li{
199199- asdf
200200- }
201201- {
202202- }
203203- ```
204204-205205- Should report "unexpected {", rather than "unclosed {", and produce
206206- something like:
207207-208208- 1 TREE
209209- 2 PRIM OL
210210- 3 PRIM LI
211211- 4 TEXT "asdf"
212212- 5 ERROR "{"
213213-214214- - Study the menhir API so that we know how to implement this kind of
215215- recovery.
216216- *)
217217- | I.HandlingError _ -> Reporter.fatalf Parse_error "TODO"
218218- | _ -> Reporter.fatalf Parse_error "TODO"
219219- in
220105 let rec run
221106 : _ I.checkpoint ->
222107 _ ->
···226111 | I.InputNeeded _env ->
227112 (* In this phase we push and pop delimiters onto the stack.*)
228113 let token, start, end_ = supplier () in
114114+ (* NOTE: Should we be storing the checkpoints?
115115+ The reason would be that we might want to resume parsing from an
116116+ unclosed delimiter, and putting an error node where the unclosed
117117+ delimiter was found, and we might only find out that the delimiter
118118+ was not closed upon encountering EOF
119119+ *)
229120 let start_position = lexbuf.lex_start_p in
230121 let end_position = lexbuf.lex_curr_p in
231122 if is_opening_delim token then
···303194 parse lexbuf
304195 with
305196 | Grammar.Error ->
306306- Reporter.fatalf ~loc: (Range.of_lexbuf lexbuf) Parse_error "failed to parse"
307307- | exn -> raise exn
197197+ Error (Asai.Diagnostic.of_text ~loc: (Range.of_lexbuf lexbuf) Error Forester_core.Reporter.Message.Parse_error (Asai.Diagnostic.text "failed to parse"))
198198+(* | exn -> *)
199199+(* Error (Asai.Diagnostic.of_text ~loc: (Range.of_lexbuf lexbuf) Error Forester_core.Reporter.Message.Parse_error (Asai.Diagnostic.text "failed to parse")) *)
308200309201let parse_channel filename ch =
310202 let lexbuf = Lexing.from_channel ch in
+1
lib/compiler/Resolver.ml
···17171818module Scope = struct
1919 include Yuujinchou.Scope.Make(P)
2020+ type data = P.data
20212122 let import_singleton x v =
2223 import_singleton (x, (v, ()))
+20-6
lib/core/Reporter.ml
···44 * SPDX-License-Identifier: GPL-3.0-or-later
55 *)
6677+open Base
88+79module Message = struct
810 type t =
99- | Duplicate_tree
1111+ | Tree_not_found of iri
1212+ | Duplicate_tree of iri
1013 | Parse_error
1114 | Type_error
1215 | Type_warning
1316 | Resolution_error
1417 | Resolution_warning
1515- | Expansion_error
1618 | Duplicate_attribute
1717- | Frontmatter_in_body
1819 | Unhandled_case
1920 | Transclusion_loop
2021 | Internal_error
···2930 [@@deriving show]
30313132 let default_severity : t -> Asai.Diagnostic.severity = function
3232- | Duplicate_tree -> Error
3333+ | Duplicate_tree _ -> Error
3434+ | Tree_not_found _ -> Error
3335 | Parse_error -> Error
3436 | Type_error -> Error
3537 | Type_warning -> Warning
3638 | Resolution_error -> Error
3739 | Resolution_warning -> Warning
3838- | Expansion_error -> Error
3940 | Duplicate_attribute -> Error
4040- | Frontmatter_in_body -> Error
4141 | Unhandled_case -> Bug
4242 | Transclusion_loop -> Error
4343 | Internal_error -> Bug
···7979 in
8080 let emit _diagnostics = () in
8181 run ~emit ~fatal k
8282+8383+let lsp_run ?init_loc ?init_backtrace publish path k =
8484+ let diagnostics = ref [] in
8585+ let push_diagnostic d =
8686+ diagnostics := d :: !diagnostics
8787+ in
8888+ run
8989+ ~emit: push_diagnostic
9090+ ~fatal: push_diagnostic
9191+ ?init_loc
9292+ ?init_backtrace @@
9393+ fun () ->
9494+ k ();
9595+ publish path !diagnostics
+11-10
lib/frontend/Forest_reader.ml
···6060module Job_map_reduce = Map_reduce.Make(Job_runner)
6161module Eval_map_reduce = Map_reduce.Make(Eval_runner)
62626363-let read_trees ~(env : env) ~host (trees : Code.tree list) : T.content T.article list * Job.publication list =
6363+let expand ~host trees =
6464 let unexpanded_trees = organise_trees ~host trees in
6565- let _, expanded_trees =
6666- let task addr (units, trees) =
6767- match String_map.find_opt addr unexpanded_trees with
6868- | None -> units, trees
6969- | Some tree ->
7070- let units, syn = Expand.expand_tree units tree in
7171- units, (addr, tree.source_path, syn) :: trees
7272- in
7373- Import_graph.topo_fold task (Import_graph.build trees) (Expand.Env.empty, [])
6565+ let task addr (units, trees) =
6666+ match String_map.find_opt addr unexpanded_trees with
6767+ | None -> units, trees
6868+ | Some tree ->
6969+ let units, syn = Expand.expand_tree units tree in
7070+ units, (addr, tree.source_path, syn) :: trees
7471 in
7272+ Import_graph.topo_fold task (Import_graph.build trees) (Expand.Env.empty, [])
7373+7474+let read_trees ~(env : env) ~host (trees : Code.tree list) : T.content T.article list * Job.publication list =
7575+ let _, expanded_trees = expand ~host trees in
7576 let Eval_runner.{ articles; jobs } = Eval_map_reduce.reduce ~env: { host } expanded_trees in
7677 let job_results =
7778 Job_map_reduce.reduce