Skip to content

Commit bed2fff

Browse files
committed
fix: actually rewind the scope for *parsing* when in a hole context
1 parent 01fa4a4 commit bed2fff

File tree

6 files changed

+107
-38
lines changed

6 files changed

+107
-38
lines changed

lib/parser/command.ml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ module Parse = struct
240240
let* number, wsin, wsnumber, tm =
241241
(let* wsin = token In in
242242
let* number, wsnumber = integer in
243+
(* Go back in time for parsing, to use the notations in scope at that hole. *)
244+
let (Found_hole { instant; _ }) = Global.find_hole number in
245+
let* () = set (Instant instant) in
243246
let* tm = C.term [] in
244247
return (Some number, wsin, wsnumber, tm))
245248
</> let* tm = C.term [] in
@@ -550,6 +553,9 @@ module Parse = struct
550553
let* number, wsnumber = integer in
551554
let* column, wscolumn = integer </> return (0, []) in
552555
let* wscoloneq = token Coloneq in
556+
(* Go back in time for parsing, to use the notations in scope at that hole. *)
557+
let (Found_hole { instant; _ }) = Global.find_hole number in
558+
let* () = set (Instant instant) in
553559
let* tm = C.term [] in
554560
return
555561
(Solve { wssolve; number; wsnumber; column; wscolumn; wscoloneq; tm; parenthesized = false })
@@ -558,6 +564,9 @@ module Parse = struct
558564
let* wssplit = token Split in
559565
let* number, wsnumber = integer in
560566
let* wscoloneq = token Coloneq in
567+
(* Go back in time for parsing, to use the notations in scope at that hole. *)
568+
let (Found_hole { instant; _ }) = Global.find_hole number in
569+
let* () = set (Instant instant) in
561570
let* tm = C.term [] in
562571
let* tms =
563572
zero_or_more
@@ -710,7 +719,8 @@ module Parse = struct
710719
Range.run ~env @@ fun () ->
711720
let p =
712721
C.Lex_and_parse.make Lexer.Parser.start
713-
(C.Basic.make_partial () (if or_echo then command_or_echo () else command ())) in
722+
(C.Basic.make_partial (Origin.current ())
723+
(if or_echo then command_or_echo () else command ())) in
714724
let out, p = run p in
715725
(C.ensure_success p, (env, out))
716726

@@ -726,7 +736,8 @@ module Parse = struct
726736
Range.run ~env @@ fun () ->
727737
let p =
728738
C.Lex_and_parse.make_next p
729-
(C.Basic.make_partial () (if or_echo then command_or_echo () else command ())) in
739+
(C.Basic.make_partial (Origin.current ())
740+
(if or_echo then command_or_echo () else command ())) in
730741
let out, p = run p in
731742
(C.ensure_success p, (env, out))
732743

lib/parser/parse.ml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
open Util
22
open Core.Reporter
3+
open Core.Origin
34
open Fmlib_parse
45
open Notation
56
module TokMap = Map.Make (Token)
@@ -21,7 +22,8 @@ end
2122

2223
(* The functor that defines all the term-parsing combinators. *)
2324
module Combinators (Final : Fmlib_std.Interfaces.ANY) = struct
24-
module Basic = Token_parser.Make (Unit) (Lexer.Token_whitespace) (Final) (SemanticError)
25+
(* The "state" is an Origin. Normally the origin is stored globally with a State effect, but we sometimes need to change the origin temporarily during parsing (e.g. to go back in time and parse a term using the notations that were in scope in the past) and doing that with effects doesn't interact well with fmlib's continuation-based parser monad. So we store the origin used for parsing in fmlib's built-in state parameter instead, and initialize it with the overall current origin when beginning parsing. *)
26+
module Basic = Token_parser.Make (Origin) (Lexer.Token_whitespace) (Final) (SemanticError)
2527
open Basic
2628

2729
(* We aren't using Fmlib's error reporting, so there's no point in supplying it nonempty "expect" strings. *)
@@ -108,7 +110,8 @@ module Combinators (Final : Fmlib_std.Interfaces.ANY) = struct
108110
(lt, ls) No.iinterval -> (rt, rs) tokmap -> (lt, ls) right_wrapped_parse t =
109111
fun tight stop ->
110112
let* res =
111-
(let* inner_loc, (inner, notn) = located (entry (Scope.Situation.left_closeds ())) in
113+
(let* origin = get in
114+
let* inner_loc, (inner, notn) = located (entry (Scope.Situation.left_closeds_at origin)) in
112115
match notn with
113116
| Open_in_interval (lt, _) -> No.plusomega_nlt lt (* This case is impossible *)
114117
| Closed_in_interval notn -> (
@@ -219,18 +222,20 @@ module Combinators (Final : Fmlib_std.Interfaces.ANY) = struct
219222
| None -> succeed first_arg
220223
| Some nontrivial ->
221224
(* Now we start by looking ahead one token. If we see one of the specified ending ops, or the initial op of a left-open tree with looser tightness than the lower endpoint of the current interval (with strictness determined by the tree in question), we return the result argument without parsing any more. Note that the order matters, in case the next token could have more than one role. Ending ops are tested first, which means that if a certain operator could end an "inner term" in an outer containing notation, it always does, even if it could also be interpreted as some infix notation inside that inner term. If a certain token could be the initial op of more than one left-open, we stop here if *any* of those is looser; we don't backtrack and try other possibilities. So the rule is that if multiple notations start with the same token, the looser one is used preferentially in cases when it matters. (In cases where it doesn't matter, i.e. they would both be allowed at the same grouping relative to other notations, we can proceed to parse a merged tree containing both of them and decide later which one it is.) *)
225+
let* origin = get in
222226
followed_by
223227
(step (fun state _ (tok, _) ->
224228
if TokMap.mem tok stop then Some (first_arg, state)
225229
else
226230
let open Monad.Ops (Monad.Maybe) in
227-
let* (No.Interval ivl) = Scope.Situation.left_opens tok in
231+
let* (No.Interval ivl) = Scope.Situation.left_opens_at origin tok in
228232
let t = tight.endpoint in
229233
let* _ = No.Interval.contains ivl t in
230234
return (first_arg, state)))
231235
(* Otherwise, we parse either an arbitrary left-closed tree (applying the given result to it as a function) or an arbitrary left-open tree with tightness in the given interval (passing the given result as the starting open argument). Interior terms are treated as in "lclosed". *)
232236
</> (let* res =
233-
(let* inner_loc, (inner, notn) = located (entry (Scope.Situation.tighters tight)) in
237+
(let* inner_loc, (inner, notn) =
238+
located (entry (Scope.Situation.tighters_at origin tight)) in
234239
match notn with
235240
| Open_in_interval (left_ok, notn) -> (
236241
match (first_arg.get (interval_left notn), right notn) with
@@ -397,7 +402,7 @@ module Combinators (Final : Fmlib_std.Interfaces.ANY) = struct
397402
| Error e -> fatal (Anomaly ("Outer term failed: " ^ e))
398403

399404
module Lex_and_parse =
400-
Parse_with_lexer.Make_utf8 (Unit) (Lexer.Token_whitespace) (Final) (SemanticError)
405+
Parse_with_lexer.Make_utf8 (Origin) (Lexer.Token_whitespace) (Final) (SemanticError)
401406
(Lexer.Parser)
402407
(Basic.Parser)
403408

@@ -438,7 +443,9 @@ module Term = struct
438443
( { source = `File name; length = In_channel.length ic },
439444
fun p -> C.Lex_and_parse.run_on_channel ic p ) in
440445
Range.run ~env @@ fun () ->
441-
let p = C.Lex_and_parse.make Lexer.Parser.start (C.Basic.make () (C.term_only ?li ?ri ())) in
446+
let p =
447+
C.Lex_and_parse.make Lexer.Parser.start
448+
(C.Basic.make (Origin.current ()) (C.term_only ?li ?ri ())) in
442449
let p = run p in
443450
C.ensure_success p
444451

lib/parser/scope.ml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,35 @@ module Situation = struct
9090

9191
let get () = M.exclusively @@ fun () -> (Versioned.get scopes).inner.situation
9292

93+
let get_at origin =
94+
M.exclusively @@ fun () ->
95+
match Versioned.get_at scopes origin with
96+
| Some s -> s.inner.situation
97+
| None -> fatal (Anomaly "invalid origin in Scope.Situation.get_at")
98+
9399
let modify f =
94100
M.exclusively @@ fun () ->
95101
let s = Versioned.get scopes in
96102
let x, situation = f s.inner.situation in
97103
Versioned.set scopes { s with inner = { s.inner with situation } };
98104
x
99105

106+
let left_closeds_at : Origin.t -> (No.plus_omega, No.strict) Notation.entry =
107+
fun origin -> left_closeds (get_at origin)
108+
100109
let left_closeds : unit -> (No.plus_omega, No.strict) Notation.entry =
101110
fun () -> left_closeds (get ())
102111

112+
let tighters_at : type strict tight.
113+
Origin.t -> (tight, strict) No.iinterval -> (tight, strict) Notation.entry =
114+
fun origin i -> tighters (get_at origin) i
115+
103116
let tighters : type strict tight. (tight, strict) No.iinterval -> (tight, strict) Notation.entry =
104117
fun i -> tighters (get ()) i
105118

119+
let left_opens_at : Origin.t -> Token.t -> No.interval option =
120+
fun origin tok -> left_opens (get_at origin) tok
121+
106122
let left_opens : Token.t -> No.interval option = fun tok -> left_opens (get ()) tok
107123

108124
let unparse : Situation.PrintKey.t -> User.notation option =

lib/parser/scope.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ val check_name : Trie.path -> Asai.Range.t option -> unit
9090

9191
module Situation : sig
9292
val get : unit -> Situation.t
93+
val left_closeds_at : Origin.t -> (No.plus_omega, No.strict) Notation.entry
9394
val left_closeds : unit -> (No.plus_omega, No.strict) Notation.entry
95+
val tighters_at : Origin.t -> ('tight, 'strict) No.iinterval -> ('tight, 'strict) Notation.entry
9496
val tighters : ('tight, 'strict) No.iinterval -> ('tight, 'strict) Notation.entry
97+
val left_opens_at : Origin.t -> Token.t -> No.interval option
9598
val left_opens : Token.t -> No.interval option
9699
val unparse : Situation.PrintKey.t -> User.notation option
97100
val add : ('left, 'tight, 'right) Notation.notation -> unit

test/black/holes.t/holes.ny

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ axiom a_very_long_function : A → A → A → A → A → A → A → B
2020

2121
def f' : A → B ≔ ?
2222

23+
{` Check whether notations that were in scope at the time of a hole are still available when solving the hole even if they're no longer in scope at the current time, while notations defined later are not in scope for the hole. `}
24+
section sec ≔
25+
26+
notation "&" ≔ b
27+
28+
def f' : A → B ≔ ¿_ ↦ &ʔ
29+
30+
end
31+
32+
notation "$" ≔ b
33+
2334
def ℕ : Type ≔ data [ zero. : ℕ | suc. : ℕ → ℕ ]
2435

2536
def plus (m n : ℕ) : ℕ ≔ match n [ zero. ↦ ? | suc. n ↦ ? ]

0 commit comments

Comments
 (0)