/- Copyright (c) 2019 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Sebastian Ullrich -/ module prelude public import Lean.Data.Name public import Lean.Data.Options public import Lean.Data.Format public section namespace Lean /-! Remark: `MonadQuotation` class is part of the `Init` package and loaded by default since it is used in the builtin command `macro`. -/ structure Unhygienic.Context where ref : Syntax scope : MacroScope /-- Simplistic MonadQuotation that does not guarantee globally fresh names, that is, between different runs of this or other MonadQuotation implementations. It is only safe if the syntax quotations do not introduce bindings around antiquotations, and if references to globals are prefixed with `_root_.` (which is not allowed to refer to a local variable) `Unhygienic` can also be seen as a model implementation of `MonadQuotation` (since it is completely hygienic as long as it is "run" only once and can assume that there are no other implementations in use, as is the case for the elaboration monads that carry their macro scope state through the entire processing of a file). It uses the state monad to query and allocate the next macro scope, and uses the reader monad to store the stack of scopes corresponding to `withFreshMacroScope` calls. -/ abbrev Unhygienic := ReaderT Lean.Unhygienic.Context <| StateM MacroScope namespace Unhygienic instance : MonadQuotation Unhygienic where getRef := return (← read).ref withRef := fun ref => withReader ({ · with ref := ref }) getCurrMacroScope := return (← read).scope getContext := pure `UnhygienicMain withFreshMacroScope := fun x => do let fresh ← modifyGet fun n => (n, n + 1) withReader ({ · with scope := fresh}) x @[inline] protected def run {α : Type} (x : Unhygienic α) : α := (x ⟨Syntax.missing, firstFrontendMacroScope⟩).run' (firstFrontendMacroScope+1) end Unhygienic private def mkInaccessibleUserNameAux (unicode : Bool) (name : Name) (idx : Nat) : Name := if unicode then if idx == 0 then name.appendAfter "✝" else name.appendAfter ("✝" ++ idx.toSuperscriptString) else name ++ Name.num `_inaccessible idx private def mkInaccessibleUserName (unicode : Bool) : Name → Name | .num p@(.str ..) idx => mkInaccessibleUserNameAux unicode p idx | .num .anonymous idx => mkInaccessibleUserNameAux unicode Name.anonymous idx | .num p idx => if unicode then (mkInaccessibleUserName unicode p).appendAfter ("⁻" ++ idx.toSuperscriptString) else Name.mkNum (mkInaccessibleUserName unicode p) idx | n => n register_builtin_option pp.sanitizeNames : Bool := { defValue := true group := "pp" descr := "add suffix to shadowed/inaccessible variables when pretty printing" } def getSanitizeNames (o : Options) : Bool := pp.sanitizeNames.get o structure NameSanitizerState where options : Options /-- `x` ~> 2 if we're already using `x✝`, `x✝¹` -/ nameStem2Idx : NameMap Nat := {} /-- `x._hyg...` ~> `x✝` -/ userName2Sanitized : NameMap Name := {} private partial def mkFreshInaccessibleUserName (userName : Name) (idx : Nat) : StateM NameSanitizerState Name := do let s ← get let userNameNew := mkInaccessibleUserName (Std.Format.getUnicode s.options) (Name.mkNum userName idx) if s.nameStem2Idx.contains userNameNew then mkFreshInaccessibleUserName userName (idx+1) else do modify fun s => { s with nameStem2Idx := s.nameStem2Idx.insert userName (idx+1) } pure userNameNew /-- Erase macro scopes from `userName` and add "tombstone" + superscript (or `._hyg`). -/ def sanitizeName (userName : Name) : StateM NameSanitizerState Name := do let stem := userName.eraseMacroScopes; let idx := (← get).nameStem2Idx.find? stem |>.getD 0 let san ← mkFreshInaccessibleUserName stem idx modify fun s => { s with userName2Sanitized := s.userName2Sanitized.insert userName san } pure san private partial def sanitizeSyntaxAux : Syntax → StateM NameSanitizerState Syntax | stx@(Syntax.ident _ _ n _) => do let n ← match (← get).userName2Sanitized.find? n with | some n' => pure n' | none => if n.hasMacroScopes then sanitizeName n else pure n return mkIdentFrom stx n | Syntax.node info k args => Syntax.node info k <$> args.mapM sanitizeSyntaxAux | stx => pure stx def sanitizeSyntax (stx : Syntax) : StateM NameSanitizerState Syntax := do if getSanitizeNames (← get).options then sanitizeSyntaxAux stx else pure stx end Lean