File: Mathlib/Mathport/Notation.lean

full_name: Mathlib.Notation3.initFn._@.Mathlib.Mathport.Notation._hyg.7
code: initialize registerTraceClass `notation3
goal: IO Unit

full_name: Mathlib.Notation3.«termExpand_binders%(_=>_)_,_»
code: /--
Expands binders into nested combinators.
For example, the familiar exists is given by:
`expand_binders% (p => Exists p) x y : Nat, x < y`
which expands to the same expression as
`∃ x y : Nat, x < y`
-/
syntax "expand_binders% " "(" ident " => " term ")" extBinders ", " term : term
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.«_aux_Mathlib_Mathport_Notation___macroRules_Mathlib_Notation3_termExpand_binders%(_=>_)_,__1»
code: macro_rules
  | `(expand_binders% ($x => $term) $y:extBinder, $res) =>
    `(expand_binders% ($x => $term) ($y:extBinder), $res)
  | `(expand_binders% ($_ => $_), $res) => pure res
goal: Lean.Macro

full_name: Mathlib.Notation3.«_aux_Mathlib_Mathport_Notation___macroRules_Mathlib_Notation3_termExpand_binders%(_=>_)_,__2»
code: macro_rules
  | `(expand_binders% ($x => $term) ($y:ident $[: $ty]?) $binders*, $res) => do
    let ty := ty.getD (← `(_))
    term.replaceM fun x' ↦ do
      unless x == x' do return none
      `(fun $y:ident : $ty ↦ expand_binders% ($x => $term) $[$binders]*, $res)
  | `(expand_binders% ($x => $term) (_%$ph $[: $ty]?) $binders*, $res) => do
    let ty := ty.getD (← `(_))
    term.replaceM fun x' ↦ do
      unless x == x' do return none
      `(fun _%$ph : $ty ↦ expand_binders% ($x => $term) $[$binders]*, $res)
  | `(expand_binders% ($x => $term) ($y:ident $pred:binderPred) $binders*, $res) =>
    term.replaceM fun x' ↦ do
      unless x == x' do return none
      `(fun $y:ident ↦ expand_binders% ($x => $term) (h : satisfies_binder_pred% $y $pred)
        $[$binders]*, $res)
goal: Lean.Macro

full_name: Mathlib.Notation3.expandFoldl
code: macro (name := expandFoldl) "expand_foldl% "
  "(" x:ident ppSpace y:ident " => " term:term ") " init:term:max " [" args:term,* "]" : term =>
  args.getElems.foldlM (init := init) fun res arg ↦ do
    term.replaceM fun e ↦
      return if e == x then some res else if e == y then some arg else none
goal: Lean.ParserDescr

full_name: Mathlib.Notation3._aux_Mathlib_Mathport_Notation___macroRules_Mathlib_Notation3_expandFoldl_1
code: macro (name := expandFoldl) "expand_foldl% "
  "(" x:ident ppSpace y:ident " => " term:term ") " init:term:max " [" args:term,* "]" : term =>
  args.getElems.foldlM (init := init) fun res arg ↦ do
    term.replaceM fun e ↦
      return if e == x then some res else if e == y then some arg else none
goal: Lean.Macro

full_name: Mathlib.Notation3.expandFoldr
code: macro (name := expandFoldr) "expand_foldr% "
  "(" x:ident ppSpace y:ident " => " term:term ") " init:term:max " [" args:term,* "]" : term =>
  args.getElems.foldrM (init := init) fun arg res ↦ do
    term.replaceM fun e ↦
      return if e == x then some arg else if e == y then some res else none
goal: Lean.ParserDescr

full_name: Mathlib.Notation3._aux_Mathlib_Mathport_Notation___macroRules_Mathlib_Notation3_expandFoldr_1
code: macro (name := expandFoldr) "expand_foldr% "
  "(" x:ident ppSpace y:ident " => " term:term ") " init:term:max " [" args:term,* "]" : term =>
  args.getElems.foldrM (init := init) fun arg res ↦ do
    term.replaceM fun e ↦
      return if e == x then some arg else if e == y then some res else none
goal: Lean.Macro

full_name: Mathlib.Notation3.foldKind
code: /-- Keywording indicating whether to use a left- or right-fold. -/
syntax foldKind := &"foldl" <|> &"foldr"
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.bindersItem
code: /-- `notation3` argument matching `extBinders`. -/
syntax bindersItem := atomic("(" "..." ")")
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.foldAction
code: /-- `notation3` argument simulating a Lean 3 fold notation. -/
syntax foldAction := "(" ident ppSpace strLit "*" (precedence)? " => " foldKind
  " (" ident ppSpace ident " => " term ") " term ")"
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.identOptScoped
code: /-- `notation3` argument binding a name. -/
syntax identOptScoped :=
  ident (notFollowedBy(":" "(" "scoped") precedence)? (":" "(" "scoped " ident " => " term ")")?
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.notation3Item
code: /-- `notation3` argument. -/
-- Note: there is deliberately no ppSpace between items
-- so that the space in the literals themselves stands out
syntax notation3Item := strLit <|> bindersItem <|> identOptScoped <|> foldAction
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.MatchState.scopeState
code: /-- The dynamic state of a `Matcher`. -/
structure MatchState where
  /-- This stores the assignments of variables to subexpressions (and their contexts)
  that have been found so far during the course of the matching algorithm.
  We store the contexts since we need to delaborate expressions after we leave
  scoping constructs. -/
  vars : HashMap Name (SubExpr × LocalContext × LocalInstances)
  /-- The binders accumulated while matching a `scoped` expression. -/
  scopeState : Option (Array (TSyntax ``extBinderParenthesized))
  /-- The arrays of delaborated `Term`s accumulated while matching
  `foldl` and `foldr` expressions. For `foldl`, the arrays are stored in reverse order. -/
  foldState : HashMap Name (Array Term)
goal: Option (Array (Lean.TSyntax `Batteries.ExtendedBinder.extBinderParenthesized))

full_name: Mathlib.Notation3.MatchState.foldState
code: /-- The dynamic state of a `Matcher`. -/
structure MatchState where
  /-- This stores the assignments of variables to subexpressions (and their contexts)
  that have been found so far during the course of the matching algorithm.
  We store the contexts since we need to delaborate expressions after we leave
  scoping constructs. -/
  vars : HashMap Name (SubExpr × LocalContext × LocalInstances)
  /-- The binders accumulated while matching a `scoped` expression. -/
  scopeState : Option (Array (TSyntax ``extBinderParenthesized))
  /-- The arrays of delaborated `Term`s accumulated while matching
  `foldl` and `foldr` expressions. For `foldl`, the arrays are stored in reverse order. -/
  foldState : HashMap Name (Array Term)
goal: Lean.HashMap Lean.Name (Array Lean.Term)

full_name: Mathlib.Notation3.MatchState
code: /-- The dynamic state of a `Matcher`. -/
structure MatchState where
  /-- This stores the assignments of variables to subexpressions (and their contexts)
  that have been found so far during the course of the matching algorithm.
  We store the contexts since we need to delaborate expressions after we leave
  scoping constructs. -/
  vars : HashMap Name (SubExpr × LocalContext × LocalInstances)
  /-- The binders accumulated while matching a `scoped` expression. -/
  scopeState : Option (Array (TSyntax ``extBinderParenthesized))
  /-- The arrays of delaborated `Term`s accumulated while matching
  `foldl` and `foldr` expressions. For `foldl`, the arrays are stored in reverse order. -/
  foldState : HashMap Name (Array Term)
goal: Type

full_name: Mathlib.Notation3.MatchState.vars
code: /-- The dynamic state of a `Matcher`. -/
structure MatchState where
  /-- This stores the assignments of variables to subexpressions (and their contexts)
  that have been found so far during the course of the matching algorithm.
  We store the contexts since we need to delaborate expressions after we leave
  scoping constructs. -/
  vars : HashMap Name (SubExpr × LocalContext × LocalInstances)
  /-- The binders accumulated while matching a `scoped` expression. -/
  scopeState : Option (Array (TSyntax ``extBinderParenthesized))
  /-- The arrays of delaborated `Term`s accumulated while matching
  `foldl` and `foldr` expressions. For `foldl`, the arrays are stored in reverse order. -/
  foldState : HashMap Name (Array Term)
goal: Lean.HashMap Lean.Name (Lean.SubExpr × Lean.LocalContext × Lean.LocalInstances)

full_name: Mathlib.Notation3.Matcher
code: /-- A matcher is a delaboration function that transforms `MatchState`s. -/
def Matcher 
goal: Type

full_name: Mathlib.Notation3.MatchState.empty
code: /-- The initial state. -/
def MatchState.empty : MatchState 
goal: Mathlib.Notation3.MatchState

full_name: Mathlib.Notation3.MatchState.withVar
code: /-- Evaluate `f` with the given variable's value as the `SubExpr` and within that subexpression's
saved context. Fails if the variable has no value. -/
def MatchState.withVar {α : Type} (s : MatchState) (name : Name)
    (m : DelabM α) : DelabM α 
goal: Lean.PrettyPrinter.Delaborator.DelabM α

full_name: Mathlib.Notation3.MatchState.delabVar
code: /-- Delaborate the given variable's value. Fails if the variable has no value.
If `checkNot` is provided, then checks that the expression being delaborated is not
the given one (this is used to prevent infinite loops). -/
def MatchState.delabVar (s : MatchState) (name : Name) (checkNot? : Option Expr := none) :
    DelabM Term 
goal: Lean.PrettyPrinter.Delaborator.DelabM Lean.Term

full_name: Mathlib.Notation3.MatchState.captureSubexpr
code: /-- Assign a variable to the current `SubExpr`, capturing the local context. -/
def MatchState.captureSubexpr (s : MatchState) (name : Name) : DelabM MatchState 
goal: Lean.PrettyPrinter.Delaborator.DelabM Mathlib.Notation3.MatchState

full_name: Mathlib.Notation3.MatchState.getFoldArray
code: /-- Get the accumulated array of delaborated terms for a given foldr/foldl.
Returns `#[]` if nothing has been pushed yet. -/
def MatchState.getFoldArray (s : MatchState) (name : Name) : Array Term 
goal: Array Lean.Term

full_name: Mathlib.Notation3.MatchState.getBinders
code: /-- Get the accumulated array of delaborated terms for a given foldr/foldl.
Returns `#[]` if nothing has been pushed yet. -/
def MatchState.getBinders (s : MatchState) : Array (TSyntax ``extBinderParenthesized) 
goal: Array (Lean.TSyntax `Batteries.ExtendedBinder.extBinderParenthesized)

full_name: Mathlib.Notation3.MatchState.pushFold
code: /-- Push a delaborated term onto a foldr/foldl array. -/
def MatchState.pushFold (s : MatchState) (name : Name) (t : Term) : MatchState 
goal: Mathlib.Notation3.MatchState

full_name: Mathlib.Notation3.matchVar
code: /-- Matcher that assigns the current `SubExpr` into the match state;
if a value already exists, then it checks for equality. -/
def matchVar (c : Name) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchExpr
code: /-- Matcher for an expression satisfying a given predicate. -/
def matchExpr (p : Expr → Bool) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchFVar
code: /-- Matcher for `Expr.fvar`.
It checks that the user name agrees and that the type of the expression is matched by `matchTy`. -/
def matchFVar (userName : Name) (matchTy : Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchTypeOf
code: /-- Matcher that checks that the type of the expression is matched by `matchTy`. -/
def matchTypeOf (matchTy : Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.natLitMatcher
code: /-- Matches raw nat lits. -/
def natLitMatcher (n : Nat) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchApp
code: /-- Matches applications. -/
def matchApp (matchFun matchArg : Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchForall
code: /-- Matches pi types. The name `n` should be unique, and `matchBody` should use `n`
as the `userName` of its fvar. -/
def matchForall (matchDom : Matcher) (matchBody : Expr → Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.matchLambda
code: /-- Matches lambdas. The `matchBody` takes the fvar introduced when visiting the body. -/
def matchLambda (matchDom : Matcher) (matchBody : Expr → Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.setupLCtx
code: /-- Adds all the names in `boundNames` to the local context
with types that are fresh metavariables.
This is used for example when initializing `p` in `(scoped p => ...)` when elaborating `...`. -/
def setupLCtx (lctx : LocalContext) (boundNames : Array Name) :
    MetaM (LocalContext × HashMap FVarId Name) 
goal: Lean.MetaM (Lean.LocalContext × Lean.HashMap Lean.FVarId Lean.Name)

full_name: Mathlib.Notation3.exprToMatcher
code: /-- Given an expression, generate a matcher for it.
The `boundFVars` hash map records which state variables certain fvars correspond to.
The `localFVars` hash map records which local variable the matcher should use for an exact
expression match.

If it succeeds generating a matcher, returns
1. a list of keys that should be used for the `delab` attribute
   when defining the elaborator
2. a `Term` that represents a `Matcher` for the given expression `e`. -/
partial def exprToMatcher (boundFVars : HashMap FVarId Name) (localFVars : HashMap FVarId Term)
      (e : Expr) :
    OptionT TermElabM (List Name × Term) 
goal: OptionT Lean.Elab.TermElabM (List Lean.Name × Lean.Term)

full_name: Mathlib.Notation3.mkExprMatcher
code: /-- Returns a `Term` that represents a `Matcher` for the given pattern `stx`.
The `boundNames` set determines which identifiers are variables in the pattern.
Fails in the `OptionT` sense if it comes across something it's unable to handle.

Also returns constant names that could serve as a key for a delaborator.
For example, if it's for a function `f`, then `app.f`. -/
partial def mkExprMatcher (stx : Term) (boundNames : Array Name) :
    OptionT TermElabM (List Name × Term) 
goal: OptionT Lean.Elab.TermElabM (List Lean.Name × Lean.Term)

full_name: Mathlib.Notation3.matchScoped.go
code: /-- Matcher for processing `scoped` syntax. Assumes the expression to be matched
against is in the `lit` variable.

Runs `smatcher`, extracts the resulting `scopeId` variable, processes this value
(which must be a lambda) to produce a binder, and loops. -/
partial def matchScoped (lit scopeId : Name) (smatcher : Matcher) : Matcher 
goal: Mathlib.Notation3.MatchState → Lean.PrettyPrinter.Delaborator.DelabM Mathlib.Notation3.MatchState

full_name: Mathlib.Notation3.matchScoped
code: /-- Matcher for processing `scoped` syntax. Assumes the expression to be matched
against is in the `lit` variable.

Runs `smatcher`, extracts the resulting `scopeId` variable, processes this value
(which must be a lambda) to produce a binder, and loops. -/
partial def matchScoped (lit scopeId : Name) (smatcher : Matcher) : Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.mkScopedMatcher
code: /-- Create a `Term` that represents a matcher for `scoped` notation.
Fails in the `OptionT` sense if a matcher couldn't be constructed.
Also returns a delaborator key like in `mkExprMatcher`.
Reminder: `$lit:ident : (scoped $scopedId:ident => $scopedTerm:Term)` -/
partial def mkScopedMatcher (lit scopeId : Name) (scopedTerm : Term) (boundNames : Array Name) :
    OptionT TermElabM (List Name × Term) 
goal: OptionT Lean.Elab.TermElabM (List Lean.Name × Lean.Term)

full_name: Mathlib.Notation3.matchFoldl
code: /-- Matcher for expressions produced by `foldl`. -/
partial def matchFoldl (lit x y : Name) (smatcher : Matcher) (sinit : Matcher) :
    Matcher 
goal: Mathlib.Notation3.Matcher

full_name: Mathlib.Notation3.mkFoldlMatcher
code: /-- Create a `Term` that represents a matcher for `foldl` notation.
Reminder: `( lit ","* => foldl (x y => scopedTerm) init)` -/
partial def mkFoldlMatcher (lit x y : Name) (scopedTerm init : Term) (boundNames : Array Name) :
    OptionT TermElabM (List Name × Term) 
goal: OptionT Lean.Elab.TermElabM (List Lean.Name × Lean.Term)

full_name: Mathlib.Notation3.mkFoldrMatcher
code: /-- Create a `Term` that represents a matcher for `foldr` notation.
Reminder: `( lit ","* => foldr (x y => scopedTerm) init)` -/
partial def mkFoldrMatcher (lit x y : Name) (scopedTerm init : Term) (boundNames : Array Name) :
    OptionT TermElabM (List Name × Term) 
goal: OptionT Lean.Elab.TermElabM (List Lean.Name × Lean.Term)

full_name: Mathlib.Notation3.mkNameFromSyntax
code: /-- Create a name that we can use for the `syntax` definition, using the
algorithm from `notation`. -/
def mkNameFromSyntax (name? : Option (TSyntax ``namedName))
    (syntaxArgs : Array (TSyntax `stx)) (attrKind : TSyntax ``Term.attrKind) :
    CommandElabM Name 
goal: Lean.Elab.Command.CommandElabM Lean.Name

full_name: Mathlib.Notation3.BoundValueType.normal
code: /-- Used when processing different kinds of variables when building the
final delaborator. -/
inductive BoundValueType
  /-- A normal variable, delaborate its expression. -/
  | normal
  /-- A fold variable, use the fold state (but reverse the array). -/
  | foldl
  /-- A fold variable, use the fold state (do not reverse the array). -/
  | foldr
goal: Mathlib.Notation3.BoundValueType

full_name: Mathlib.Notation3.BoundValueType.foldr
code: /-- Used when processing different kinds of variables when building the
final delaborator. -/
inductive BoundValueType
  /-- A normal variable, delaborate its expression. -/
  | normal
  /-- A fold variable, use the fold state (but reverse the array). -/
  | foldl
  /-- A fold variable, use the fold state (do not reverse the array). -/
  | foldr
goal: Mathlib.Notation3.BoundValueType

full_name: Mathlib.Notation3.BoundValueType
code: /-- Used when processing different kinds of variables when building the
final delaborator. -/
inductive BoundValueType
  /-- A normal variable, delaborate its expression. -/
  | normal
  /-- A fold variable, use the fold state (but reverse the array). -/
  | foldl
  /-- A fold variable, use the fold state (do not reverse the array). -/
  | foldr
goal: Type

full_name: Mathlib.Notation3.BoundValueType.foldl
code: /-- Used when processing different kinds of variables when building the
final delaborator. -/
inductive BoundValueType
  /-- A normal variable, delaborate its expression. -/
  | normal
  /-- A fold variable, use the fold state (but reverse the array). -/
  | foldl
  /-- A fold variable, use the fold state (do not reverse the array). -/
  | foldr
goal: Mathlib.Notation3.BoundValueType

full_name: Mathlib.Notation3.prettyPrintOpt
code: syntax prettyPrintOpt := "(" &"prettyPrint" " := " (&"true" <|> &"false") ")"
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.getPrettyPrintOpt
code: /-- Interpret a `prettyPrintOpt`. The default value is `true`. -/
def getPrettyPrintOpt (opt? : Option (TSyntax ``prettyPrintOpt)) : Bool 
goal: Bool

full_name: Mathlib.Notation3._aux_Mathlib_Mathport_Notation___elabRules_Mathlib_Notation3_notation3_1
code: /--
`notation3` declares notation using Lean-3-style syntax.

Examples:
```
notation3 "∀ᶠ " (...) " in " f ", " r:(scoped p => Filter.eventually p f) => r
notation3 "MyList[" (x", "* => foldr (a b => MyList.cons a b) MyList.nil) "]" => x
```
By default notation is unable to mention any variables defined using `variable`, but
`local notation3` is able to use such local variables.

Use `notation3 (prettyPrint := false)` to keep the command from generating a pretty printer
for the notation.

This command can be used in mathlib4 but it has an uncertain future and was created primarily
for backward compatibility.
-/
elab (name := notation3) doc:(docComment)? attrs?:(Parser.Term.attributes)? attrKind:Term.attrKind
    "notation3" prec?:(precedence)? name?:(namedName)? prio?:(namedPrio)? pp?:(prettyPrintOpt)?
    ppSpace items:(notation3Item)+ " => " val:term : command => do
  -- We use raw `Name`s for variables. This maps variable names back to the
  -- identifiers that appear in `items`
  let mut boundIdents : HashMap Name Ident := {}
  -- Replacements to use for the `macro`
  let mut boundValues : HashMap Name Syntax := {}
  -- The names of the bound names in order, used when constructing patterns for delaboration.
  let mut boundNames : Array Name := #[]
  -- The normal/foldl/foldr type of each variable (for delaborator)
  let mut boundType : HashMap Name BoundValueType := {}
  -- Function to update `syntaxArgs` and `pattArgs` using `macroArg` syntax
  let pushMacro (syntaxArgs : Array (TSyntax `stx)) (pattArgs : Array Syntax)
      (mac : TSyntax ``macroArg) := do
    let (syntaxArg, pattArg) ← expandMacroArg mac
    return (syntaxArgs.push syntaxArg, pattArgs.push pattArg)
  -- Arguments for the `syntax` command
  let mut syntaxArgs := #[]
  -- Arguments for the LHS pattern in the `macro`. Also used to construct the syntax
  -- when delaborating
  let mut pattArgs := #[]
  -- The matchers to assemble into a delaborator
  let mut matchers := #[]
  -- Whether we've seen a `(...)` item
  let mut hasBindersItem := false
  -- Whether we've seen a `scoped` item
  let mut hasScoped := false
  for item in items do
    match item with
    | `(notation3Item| $lit:str) =>
      -- Can't use `pushMacro` since it inserts an extra variable into the pattern for `str`, which
      -- breaks our delaborator
      syntaxArgs := syntaxArgs.push (← `(stx| $lit:str))
      pattArgs := pattArgs.push <| mkAtomFrom lit lit.1.isStrLit?.get!
    | `(notation3Item| $_:bindersItem) =>
      if hasBindersItem then
        throwErrorAt item "Cannot have more than one `(...)` item."
      hasBindersItem := true
      -- HACK: Lean 3 traditionally puts a space after the main binder atom, resulting in
      -- notation3 "∑ "(...)", "r:(scoped f => sum f) => r
      -- but extBinders already has a space before it so we strip the trailing space of "∑ "
      if let `(stx| $lit:str) := syntaxArgs.back then
        syntaxArgs := syntaxArgs.pop.push (← `(stx| $(quote lit.getString.trimRight):str))
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs (← `(macroArg| binders:extBinders))
    | `(notation3Item| ($id:ident $sep:str* $(prec?)? => $kind ($x $y => $scopedTerm) $init)) =>
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <| ←
        `(macroArg| $id:ident:sepBy(term $(prec?)?, $sep:str))
      -- N.B. `Syntax.getId` returns `.anonymous` for non-idents
      let scopedTerm' ← scopedTerm.replaceM fun s => pure (boundValues.find? s.getId)
      let init' ← init.replaceM fun s => pure (boundValues.find? s.getId)
      boundIdents := boundIdents.insert id.getId id
      match kind with
        | `(foldKind| foldl) =>
          boundValues := boundValues.insert id.getId <| ←
            `(expand_foldl% ($x $y => $scopedTerm') $init' [$$(.ofElems $id),*])
          boundNames := boundNames.push id.getId
          boundType := boundType.insert id.getId .foldl
          matchers := matchers.push <|
            mkFoldlMatcher id.getId x.getId y.getId scopedTerm init boundNames
        | `(foldKind| foldr) =>
          boundValues := boundValues.insert id.getId <| ←
            `(expand_foldr% ($x $y => $scopedTerm') $init' [$$(.ofElems $id),*])
          boundNames := boundNames.push id.getId
          boundType := boundType.insert id.getId .foldr
          matchers := matchers.push <|
            mkFoldrMatcher id.getId x.getId y.getId scopedTerm init boundNames
        | _ => throwUnsupportedSyntax
    | `(notation3Item| $lit:ident $(prec?)? : (scoped $scopedId:ident => $scopedTerm)) =>
      hasScoped := true
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <|←
        `(macroArg| $lit:ident:term $(prec?)?)
      matchers := matchers.push <|
        mkScopedMatcher lit.getId scopedId.getId scopedTerm boundNames
      let scopedTerm' ← scopedTerm.replaceM fun s => pure (boundValues.find? s.getId)
      boundIdents := boundIdents.insert lit.getId lit
      boundValues := boundValues.insert lit.getId <| ←
        `(expand_binders% ($scopedId => $scopedTerm') $$binders:extBinders,
          $(⟨lit.1.mkAntiquotNode `term⟩):term)
      boundNames := boundNames.push lit.getId
    | `(notation3Item| $lit:ident $(prec?)?) =>
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <|←
        `(macroArg| $lit:ident:term $(prec?)?)
      boundIdents := boundIdents.insert lit.getId lit
      boundValues := boundValues.insert lit.getId <| lit.1.mkAntiquotNode `term
      boundNames := boundNames.push lit.getId
    | _stx => throwUnsupportedSyntax
  if hasScoped && !hasBindersItem then
    throwError "If there is a `scoped` item then there must be a `(...)` item for binders."

  -- 1. The `syntax` command
  let name ← mkNameFromSyntax name? syntaxArgs attrKind
  elabCommand <| ← `(command|
    $[$doc]? $(attrs?)? $attrKind
    syntax $(prec?)? (name := $(Lean.mkIdent name)) $(prio?)? $[$syntaxArgs]* : term)

  -- 2. The `macro_rules`
  let currNamespace : Name ← getCurrNamespace
  -- The `syntax` command puts definitions into the current namespace; we need this
  -- to make the syntax `pat`.
  let fullName := currNamespace ++ name
  trace[notation3] "syntax declaration has name {fullName}"
  let pat : Term := ⟨mkNode fullName pattArgs⟩
  let val' ← val.replaceM fun s => pure (boundValues.find? s.getId)
  let mut macroDecl ← `(macro_rules | `($pat) => `($val'))
  if isLocalAttrKind attrKind then
    -- For local notation, take section variables into account
    macroDecl ← `(section set_option quotPrecheck.allowSectionVars true $macroDecl end)
  elabCommand macroDecl

  -- 3. Create a delaborator
  if getPrettyPrintOpt pp? then
    matchers := matchers.push <| Mathlib.Notation3.mkExprMatcher val boundNames
    -- The matchers need to run in reverse order, so may as well reverse them here.
    let matchersM? := (matchers.reverse.mapM id).run
    -- We let local notations have access to `variable` declarations
    let matchers? ← if isLocalAttrKind attrKind then
      runTermElabM fun _ => matchersM?
    else
      liftTermElabM matchersM?
    if let some ms := matchers? then
      trace[notation3] "Matcher creation succeeded; assembling delaborator"
      let delabName := name ++ `delab
      let matcher ← ms.foldrM (fun m t => `($(m.2) >=> $t)) (← `(pure))
      trace[notation3] "matcher:{indentD matcher}"
      let mut result ← `(`($pat))
      for (name, id) in boundIdents.toArray do
        match boundType.findD name .normal with
        | .normal => result ← `(MatchState.delabVar s $(quote name) (some e) >>= fun $id => $result)
        | .foldl => result ←
          `(let $id := (MatchState.getFoldArray s $(quote name)).reverse; $result)
        | .foldr => result ←
          `(let $id := MatchState.getFoldArray s $(quote name); $result)
      if hasBindersItem then
        result ← `(`(extBinders| $$(MatchState.getBinders s)*) >>= fun binders => $result)
      elabCommand <| ← `(command|
        /-- Pretty printer defined by `notation3` command. -/
        def $(Lean.mkIdent delabName) : Delab := whenPPOption getPPNotation <|
          getExpr >>= fun e => $matcher MatchState.empty >>= fun s => $result)
      trace[notation3] "Defined delaborator {currNamespace ++ delabName}"
      let delabKeys := ms.foldr (·.1 ++ ·) []
      trace[notation3] "Adding `delab` attribute for keys {delabKeys}"
      for key in delabKeys do
        elabCommand <|
          ← `(command| attribute [$attrKind delab $(mkIdent key)] $(Lean.mkIdent delabName))
    else
      logWarning s!"\
        Was not able to generate a pretty printer for this notation. \
        If you do not expect it to be pretty printable, then you can use \
        `notation3 (prettyPrint := false)`. \
        If the notation expansion refers to section variables, be sure to do `local notation3`. \
        Otherwise, you might be able to adjust the notation expansion to make it matchable; \
        pretty printing relies on deriving an expression matcher from the expansion. \
        (Use `set_option trace.notation3 true` to get some debug information.)"
goal: Lean.Elab.Command.CommandElab

full_name: Mathlib.Notation3.notation3
code: /--
`notation3` declares notation using Lean-3-style syntax.

Examples:
```
notation3 "∀ᶠ " (...) " in " f ", " r:(scoped p => Filter.eventually p f) => r
notation3 "MyList[" (x", "* => foldr (a b => MyList.cons a b) MyList.nil) "]" => x
```
By default notation is unable to mention any variables defined using `variable`, but
`local notation3` is able to use such local variables.

Use `notation3 (prettyPrint := false)` to keep the command from generating a pretty printer
for the notation.

This command can be used in mathlib4 but it has an uncertain future and was created primarily
for backward compatibility.
-/
elab (name := notation3) doc:(docComment)? attrs?:(Parser.Term.attributes)? attrKind:Term.attrKind
    "notation3" prec?:(precedence)? name?:(namedName)? prio?:(namedPrio)? pp?:(prettyPrintOpt)?
    ppSpace items:(notation3Item)+ " => " val:term : command => do
  -- We use raw `Name`s for variables. This maps variable names back to the
  -- identifiers that appear in `items`
  let mut boundIdents : HashMap Name Ident := {}
  -- Replacements to use for the `macro`
  let mut boundValues : HashMap Name Syntax := {}
  -- The names of the bound names in order, used when constructing patterns for delaboration.
  let mut boundNames : Array Name := #[]
  -- The normal/foldl/foldr type of each variable (for delaborator)
  let mut boundType : HashMap Name BoundValueType := {}
  -- Function to update `syntaxArgs` and `pattArgs` using `macroArg` syntax
  let pushMacro (syntaxArgs : Array (TSyntax `stx)) (pattArgs : Array Syntax)
      (mac : TSyntax ``macroArg) := do
    let (syntaxArg, pattArg) ← expandMacroArg mac
    return (syntaxArgs.push syntaxArg, pattArgs.push pattArg)
  -- Arguments for the `syntax` command
  let mut syntaxArgs := #[]
  -- Arguments for the LHS pattern in the `macro`. Also used to construct the syntax
  -- when delaborating
  let mut pattArgs := #[]
  -- The matchers to assemble into a delaborator
  let mut matchers := #[]
  -- Whether we've seen a `(...)` item
  let mut hasBindersItem := false
  -- Whether we've seen a `scoped` item
  let mut hasScoped := false
  for item in items do
    match item with
    | `(notation3Item| $lit:str) =>
      -- Can't use `pushMacro` since it inserts an extra variable into the pattern for `str`, which
      -- breaks our delaborator
      syntaxArgs := syntaxArgs.push (← `(stx| $lit:str))
      pattArgs := pattArgs.push <| mkAtomFrom lit lit.1.isStrLit?.get!
    | `(notation3Item| $_:bindersItem) =>
      if hasBindersItem then
        throwErrorAt item "Cannot have more than one `(...)` item."
      hasBindersItem := true
      -- HACK: Lean 3 traditionally puts a space after the main binder atom, resulting in
      -- notation3 "∑ "(...)", "r:(scoped f => sum f) => r
      -- but extBinders already has a space before it so we strip the trailing space of "∑ "
      if let `(stx| $lit:str) := syntaxArgs.back then
        syntaxArgs := syntaxArgs.pop.push (← `(stx| $(quote lit.getString.trimRight):str))
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs (← `(macroArg| binders:extBinders))
    | `(notation3Item| ($id:ident $sep:str* $(prec?)? => $kind ($x $y => $scopedTerm) $init)) =>
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <| ←
        `(macroArg| $id:ident:sepBy(term $(prec?)?, $sep:str))
      -- N.B. `Syntax.getId` returns `.anonymous` for non-idents
      let scopedTerm' ← scopedTerm.replaceM fun s => pure (boundValues.find? s.getId)
      let init' ← init.replaceM fun s => pure (boundValues.find? s.getId)
      boundIdents := boundIdents.insert id.getId id
      match kind with
        | `(foldKind| foldl) =>
          boundValues := boundValues.insert id.getId <| ←
            `(expand_foldl% ($x $y => $scopedTerm') $init' [$$(.ofElems $id),*])
          boundNames := boundNames.push id.getId
          boundType := boundType.insert id.getId .foldl
          matchers := matchers.push <|
            mkFoldlMatcher id.getId x.getId y.getId scopedTerm init boundNames
        | `(foldKind| foldr) =>
          boundValues := boundValues.insert id.getId <| ←
            `(expand_foldr% ($x $y => $scopedTerm') $init' [$$(.ofElems $id),*])
          boundNames := boundNames.push id.getId
          boundType := boundType.insert id.getId .foldr
          matchers := matchers.push <|
            mkFoldrMatcher id.getId x.getId y.getId scopedTerm init boundNames
        | _ => throwUnsupportedSyntax
    | `(notation3Item| $lit:ident $(prec?)? : (scoped $scopedId:ident => $scopedTerm)) =>
      hasScoped := true
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <|←
        `(macroArg| $lit:ident:term $(prec?)?)
      matchers := matchers.push <|
        mkScopedMatcher lit.getId scopedId.getId scopedTerm boundNames
      let scopedTerm' ← scopedTerm.replaceM fun s => pure (boundValues.find? s.getId)
      boundIdents := boundIdents.insert lit.getId lit
      boundValues := boundValues.insert lit.getId <| ←
        `(expand_binders% ($scopedId => $scopedTerm') $$binders:extBinders,
          $(⟨lit.1.mkAntiquotNode `term⟩):term)
      boundNames := boundNames.push lit.getId
    | `(notation3Item| $lit:ident $(prec?)?) =>
      (syntaxArgs, pattArgs) ← pushMacro syntaxArgs pattArgs <|←
        `(macroArg| $lit:ident:term $(prec?)?)
      boundIdents := boundIdents.insert lit.getId lit
      boundValues := boundValues.insert lit.getId <| lit.1.mkAntiquotNode `term
      boundNames := boundNames.push lit.getId
    | _stx => throwUnsupportedSyntax
  if hasScoped && !hasBindersItem then
    throwError "If there is a `scoped` item then there must be a `(...)` item for binders."

  -- 1. The `syntax` command
  let name ← mkNameFromSyntax name? syntaxArgs attrKind
  elabCommand <| ← `(command|
    $[$doc]? $(attrs?)? $attrKind
    syntax $(prec?)? (name := $(Lean.mkIdent name)) $(prio?)? $[$syntaxArgs]* : term)

  -- 2. The `macro_rules`
  let currNamespace : Name ← getCurrNamespace
  -- The `syntax` command puts definitions into the current namespace; we need this
  -- to make the syntax `pat`.
  let fullName := currNamespace ++ name
  trace[notation3] "syntax declaration has name {fullName}"
  let pat : Term := ⟨mkNode fullName pattArgs⟩
  let val' ← val.replaceM fun s => pure (boundValues.find? s.getId)
  let mut macroDecl ← `(macro_rules | `($pat) => `($val'))
  if isLocalAttrKind attrKind then
    -- For local notation, take section variables into account
    macroDecl ← `(section set_option quotPrecheck.allowSectionVars true $macroDecl end)
  elabCommand macroDecl

  -- 3. Create a delaborator
  if getPrettyPrintOpt pp? then
    matchers := matchers.push <| Mathlib.Notation3.mkExprMatcher val boundNames
    -- The matchers need to run in reverse order, so may as well reverse them here.
    let matchersM? := (matchers.reverse.mapM id).run
    -- We let local notations have access to `variable` declarations
    let matchers? ← if isLocalAttrKind attrKind then
      runTermElabM fun _ => matchersM?
    else
      liftTermElabM matchersM?
    if let some ms := matchers? then
      trace[notation3] "Matcher creation succeeded; assembling delaborator"
      let delabName := name ++ `delab
      let matcher ← ms.foldrM (fun m t => `($(m.2) >=> $t)) (← `(pure))
      trace[notation3] "matcher:{indentD matcher}"
      let mut result ← `(`($pat))
      for (name, id) in boundIdents.toArray do
        match boundType.findD name .normal with
        | .normal => result ← `(MatchState.delabVar s $(quote name) (some e) >>= fun $id => $result)
        | .foldl => result ←
          `(let $id := (MatchState.getFoldArray s $(quote name)).reverse; $result)
        | .foldr => result ←
          `(let $id := MatchState.getFoldArray s $(quote name); $result)
      if hasBindersItem then
        result ← `(`(extBinders| $$(MatchState.getBinders s)*) >>= fun binders => $result)
      elabCommand <| ← `(command|
        /-- Pretty printer defined by `notation3` command. -/
        def $(Lean.mkIdent delabName) : Delab := whenPPOption getPPNotation <|
          getExpr >>= fun e => $matcher MatchState.empty >>= fun s => $result)
      trace[notation3] "Defined delaborator {currNamespace ++ delabName}"
      let delabKeys := ms.foldr (·.1 ++ ·) []
      trace[notation3] "Adding `delab` attribute for keys {delabKeys}"
      for key in delabKeys do
        elabCommand <|
          ← `(command| attribute [$attrKind delab $(mkIdent key)] $(Lean.mkIdent delabName))
    else
      logWarning s!"\
        Was not able to generate a pretty printer for this notation. \
        If you do not expect it to be pretty printable, then you can use \
        `notation3 (prettyPrint := false)`. \
        If the notation expansion refers to section variables, be sure to do `local notation3`. \
        Otherwise, you might be able to adjust the notation expansion to make it matchable; \
        pretty printing relies on deriving an expression matcher from the expansion. \
        (Use `set_option trace.notation3 true` to get some debug information.)"
goal: Lean.ParserDescr

full_name: Mathlib.Notation3.initFn._@.Mathlib.Mathport.Notation._hyg.15913
code: initialize Batteries.Linter.UnreachableTactic.addIgnoreTacticKind ``«notation3»
goal: IO Unit

full_name: Mathlib.Notation3._aux_Mathlib_Mathport_Notation___macroRules_Mathlib_Tactic_scopedNS_1
code: macro_rules
  | `($[$doc]? $(attr)? scoped[$ns] notation3 $(prec)? $(n)? $(prio)? $(pp)? $items* => $t) =>
    `(with_weak_namespace $(mkIdentFrom ns <| rootNamespace ++ ns.getId)
      $[$doc]? $(attr)? scoped notation3 $(prec)? $(n)? $(prio)? $(pp)? $items* => $t)
goal: Lean.Macro