@@ -97,8 +97,8 @@ structure MatchState where
97
97
We store the contexts since we need to delaborate expressions after we leave
98
98
scoping constructs. -/
99
99
vars : HashMap Name (SubExpr × LocalContext × LocalInstances)
100
- /-- The binders accumulated when matching a `scoped` expression. -/
101
- scopeState : Array (TSyntax ``extBinderParenthesized)
100
+ /-- The binders accumulated while matching a `scoped` expression. -/
101
+ scopeState : Option ( Array (TSyntax ``extBinderParenthesized) )
102
102
/-- The arrays of delaborated `Term`s accumulated while matching
103
103
`foldl` and `foldr` expressions. For `foldl`, the arrays are stored in reverse order. -/
104
104
foldState : HashMap Name (Array Term)
@@ -110,7 +110,7 @@ def Matcher := MatchState → DelabM MatchState
110
110
/-- The initial state. -/
111
111
def MatchState.empty : MatchState where
112
112
vars := {}
113
- scopeState := #[]
113
+ scopeState := none
114
114
foldState := {}
115
115
116
116
/-- Evaluate `f` with the given variable's value as the `SubExpr` and within that subexpression's
@@ -134,19 +134,16 @@ def MatchState.delabVar (s : MatchState) (name : Name) (checkNot? : Option Expr
134
134
def MatchState.captureSubexpr (s : MatchState) (name : Name) : DelabM MatchState := do
135
135
return {s with vars := s.vars.insert name (← readThe SubExpr, ← getLCtx, ← getLocalInstances)}
136
136
137
- /-- Push a binder onto the binder array. For `scoped`. -/
138
- def MatchState.pushBinder (s : MatchState) (b : TSyntax ``extBinderParenthesized) :
139
- DelabM MatchState := do
140
- let binders := s.scopeState
141
- -- TODO merge binders as an inverse to `satisfies_binder_pred%`
142
- let binders := binders.push b
143
- return {s with scopeState := binders}
144
-
145
137
/-- Get the accumulated array of delaborated terms for a given foldr/foldl.
146
138
Returns `#[]` if nothing has been pushed yet. -/
147
139
def MatchState.getFoldArray (s : MatchState) (name : Name) : Array Term :=
148
140
(s.foldState.find? name).getD #[]
149
141
142
+ /-- Get the accumulated array of delaborated terms for a given foldr/foldl.
143
+ Returns `#[]` if nothing has been pushed yet. -/
144
+ def MatchState.getBinders (s : MatchState) : Array (TSyntax ``extBinderParenthesized) :=
145
+ s.scopeState.getD #[]
146
+
150
147
/-- Push a delaborated term onto a foldr/foldl array. -/
151
148
def MatchState.pushFold (s : MatchState) (name : Name) (t : Term) : MatchState :=
152
149
let ts := (s.getFoldArray name).push t
@@ -268,46 +265,44 @@ where
268
265
against is in the `lit` variable.
269
266
270
267
Runs `smatcher`, extracts the resulting `scopeId` variable, processes this value
271
- (which must be a lambda) to produce a binder, and loops.
272
-
273
- Succeeds even if it matches nothing, so it is up to the caller to decide if the
274
- empty scope state is ok. -/
275
- partial def matchScoped (lit scopeId : Name) (smatcher : Matcher) : Matcher := fun s => do
276
- -- `lit` is bound to the SubExpr that the `scoped` syntax produced
277
- s.withVar lit do
278
- try
279
- -- Run `smatcher` at `lit`, clearing the `scopeId` variable so that it can get a fresh value
280
- let s ← smatcher {s with vars := s.vars.erase scopeId}
281
- s.withVar scopeId do
282
- guard (← getExpr).isLambda
283
- let prop ← try Meta.isProp (← getExpr).bindingDomain! catch _ => pure false
284
- let isDep := (← getExpr).bindingBody!.hasLooseBVar 0
285
- let ppTypes ← getPPOption getPPPiBinderTypes -- the same option controlling ∀
286
- let dom ← withBindingDomain delab
287
- withBindingBodyUnusedName <| fun x => do
288
- let x : Ident := ⟨x⟩
289
- let binder ←
290
- if prop && !isDep then
291
- -- this underscore is used to support binder predicates, since it indicates
292
- -- the variable is unused and this binder is safe to merge into another
293
- `(extBinderParenthesized|(_ : $dom))
294
- else if prop || ppTypes then
295
- `(extBinderParenthesized|($x:ident : $dom))
296
- else
297
- `(extBinderParenthesized|($x:ident))
298
- -- Now use the body of the lambda for `lit` for the next iteration
299
- let s ← s.captureSubexpr lit
300
- let s ← s.pushBinder binder
301
- matchScoped lit scopeId smatcher s
302
- catch _ =>
303
- return s
304
-
305
- /-- Like `matchScoped` but ensures that it matches at least one binder. -/
306
- partial def matchScoped' (lit scopeId : Name) (smatcher : Matcher) : Matcher := fun s => do
307
- guard <| s.scopeState.isEmpty
308
- let s ← matchScoped lit scopeId smatcher s
309
- guard <| !s.scopeState.isEmpty
310
- return s
268
+ (which must be a lambda) to produce a binder, and loops. -/
269
+ partial def matchScoped (lit scopeId : Name) (smatcher : Matcher) : Matcher := go #[] where
270
+ /-- Variant of `matchScoped` after some number of `binders` have already been captured. -/
271
+ go (binders : Array (TSyntax ``extBinderParenthesized)) : Matcher := fun s => do
272
+ -- `lit` is bound to the SubExpr that the `scoped` syntax produced
273
+ s.withVar lit do
274
+ try
275
+ -- Run `smatcher` at `lit`, clearing the `scopeId` variable so that it can get a fresh value
276
+ let s ← smatcher {s with vars := s.vars.erase scopeId}
277
+ s.withVar scopeId do
278
+ guard (← getExpr).isLambda
279
+ let prop ← try Meta.isProp (← getExpr).bindingDomain! catch _ => pure false
280
+ let isDep := (← getExpr).bindingBody!.hasLooseBVar 0
281
+ let ppTypes ← getPPOption getPPPiBinderTypes -- the same option controlling ∀
282
+ let dom ← withBindingDomain delab
283
+ withBindingBodyUnusedName <| fun x => do
284
+ let x : Ident := ⟨x⟩
285
+ let binder ←
286
+ if prop && !isDep then
287
+ -- this underscore is used to support binder predicates, since it indicates
288
+ -- the variable is unused and this binder is safe to merge into another
289
+ `(extBinderParenthesized|(_ : $dom))
290
+ else if prop || ppTypes then
291
+ `(extBinderParenthesized|($x:ident : $dom))
292
+ else
293
+ `(extBinderParenthesized|($x:ident))
294
+ -- Now use the body of the lambda for `lit` for the next iteration
295
+ let s ← s.captureSubexpr lit
296
+ -- TODO merge binders as an inverse to `satisfies_binder_pred%`
297
+ let binders := binders.push binder
298
+ go binders s
299
+ catch _ =>
300
+ guard <| !binders.isEmpty
301
+ if let some binders₂ := s.scopeState then
302
+ guard <| binders == binders₂ -- TODO: this might be a bit too strict, but it seems to work
303
+ return s
304
+ else
305
+ return {s with scopeState := binders}
311
306
312
307
/- Create a `Term` that represents a matcher for `scoped` notation.
313
308
Fails in the `OptionT` sense if a matcher couldn't be constructed.
@@ -317,7 +312,7 @@ partial def mkScopedMatcher (lit scopeId : Name) (scopedTerm : Term) (boundNames
317
312
OptionT TermElabM (List Name × Term) := do
318
313
-- Build the matcher for `scopedTerm` with `scopeId` as an additional variable
319
314
let (keys, smatcher) ← mkExprMatcher scopedTerm (boundNames.insert scopeId)
320
- return (keys, ← ``(matchScoped' $(quote lit) $(quote scopeId) $smatcher))
315
+ return (keys, ← ``(matchScoped $(quote lit) $(quote scopeId) $smatcher))
321
316
322
317
/-- Matcher for expressions produced by `foldl`. -/
323
318
partial def matchFoldl (lit x y : Name) (smatcher : Matcher) (sinit : Matcher) :
@@ -482,8 +477,6 @@ elab doc:(docComment)? attrs?:(Parser.Term.attributes)? attrKind:Term.attrKind
482
477
mkFoldrMatcher id.getId x.getId y.getId scopedTerm init (getBoundNames boundValues)
483
478
| _ => throwUnsupportedSyntax
484
479
| `(notation3Item| $lit:ident $(prec?)? : (scoped $scopedId:ident => $scopedTerm)) =>
485
- if hasScoped then
486
- throwErrorAt item "Cannot have more than one `scoped` item."
487
480
hasScoped := true
488
481
(syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <|←
489
482
`(macroArg| $lit:ident:term $(prec?)?)
@@ -547,7 +540,7 @@ elab doc:(docComment)? attrs?:(Parser.Term.attributes)? attrKind:Term.attrKind
547
540
| .foldr => result ←
548
541
`(let $id := MatchState.getFoldArray s $(quote name); $result)
549
542
if hasBindersItem then
550
- result ← `(`(extBinders| $$(MatchState.scopeState s)*) >>= fun binders => $result)
543
+ result ← `(`(extBinders| $$(MatchState.getBinders s)*) >>= fun binders => $result)
551
544
elabCommand <| ← `(command|
552
545
def $(Lean.mkIdent delabName) : Delab := whenPPOption getPPNotation <|
553
546
getExpr >>= fun e => $matcher MatchState.empty >>= fun s => $result)
0 commit comments