-
Notifications
You must be signed in to change notification settings - Fork 437
/
Hygiene.lean
115 lines (101 loc) · 4.51 KB
/
Hygiene.lean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
prelude
import Lean.Data.Name
import Lean.Data.Options
import Lean.Data.Format
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
getMainModule := 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