lean4-htt/src/Lean/PrettyPrinter/Delaborator/Basic.lean
2021-08-01 09:58:44 +02:00

437 lines
17 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
/-!
The delaborator is the first stage of the pretty printer, and the inverse of the
elaborator: it turns fully elaborated `Expr` core terms back into surface-level
`Syntax`, omitting some implicit information again and using higher-level syntax
abstractions like notations where possible. The exact behavior can be customized
using pretty printer options; activating `pp.all` should guarantee that the
delaborator is injective and that re-elaborating the resulting `Syntax`
round-trips.
Pretty printer options can be given not only for the whole term, but also
specific subterms. This is used both when automatically refining pp options
until round-trip and when interactively selecting pp options for a subterm (both
TBD). The association of options to subterms is done by assigning a unique,
synthetic Nat position to each subterm derived from its position in the full
term. This position is added to the corresponding Syntax object so that
elaboration errors and interactions with the pretty printer output can be traced
back to the subterm.
The delaborator is extensible via the `[delab]` attribute.
-/
import Lean.KeyedDeclsAttribute
import Lean.ProjFns
import Lean.Syntax
import Lean.Meta.Match
import Lean.Elab.Term
namespace Lean
register_builtin_option pp.all : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display coercions, implicit parameters, proof terms, fully qualified names, universe, " ++
"and disable beta reduction and notations during pretty printing"
}
register_builtin_option pp.notation : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) disable/enable notation (infix, mixfix, postfix operators and unicode characters)"
}
register_builtin_option pp.coercions : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) hide coercion applications"
}
register_builtin_option pp.universes : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display universe"
}
register_builtin_option pp.full_names : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display fully qualified names"
}
register_builtin_option pp.private_names : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display internal names assigned to private declarations"
}
register_builtin_option pp.binder_types : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display types of lambda and Pi parameters"
}
register_builtin_option pp.structure_instances : Bool := {
defValue := true
group := "pp"
-- TODO: implement second part
descr := "(pretty printer) display structure instances using the '{ fieldName := fieldValue, ... }' notation " ++
"or '⟨fieldValue, ... ⟩' if structure is tagged with [pp_using_anonymous_constructor] attribute"
}
register_builtin_option pp.structure_projections : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) display structure projections using field notation"
}
register_builtin_option pp.explicit : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display implicit arguments"
}
register_builtin_option pp.structure_instance_type : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display type of structure instances"
}
register_builtin_option pp.safe_shadowing : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) allow variable shadowing if there is no collision"
}
register_builtin_option pp.proofs : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) if set to false, replace proofs appearing as an argument to a function with a placeholder"
}
register_builtin_option pp.proofs.withType : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) when eliding a proof (see `pp.proofs`), show its type instead"
}
register_builtin_option pp.motives.pi : Bool := {
defValue := true
group := "pp"
descr := "(pretty printer) print all motives that return pi types"
}
register_builtin_option pp.motives.nonConst : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) print all motives that are not constant functions"
}
register_builtin_option pp.motives.all : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) print all motives"
}
-- TODO:
/-
register_builtin_option g_pp_max_depth : Nat := {
defValue := false
group := "pp"
descr := "(pretty printer) maximum expression depth, after that it will use ellipsis"
}
register_builtin_option g_pp_max_steps : Nat := {
defValue := false
group := "pp"
descr := "(pretty printer) maximum number of visited expressions, after that it will use ellipsis"
}
register_builtin_option g_pp_locals_full_names : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) show full names of locals"
}
register_builtin_option g_pp_beta : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) apply beta-reduction when pretty printing"
}
register_builtin_option g_pp_goal_compact : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) try to display goal in a single line when possible"
}
register_builtin_option g_pp_goal_max_hyps : Nat := {
defValue := false
group := "pp"
descr := "(pretty printer) maximum number of hypotheses to be displayed"
}
register_builtin_option g_pp_instantiate_mvars : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) instantiate assigned metavariables before pretty printing terms and goals"
}
register_builtin_option g_pp_annotations : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) display internal annotations (for debugging purposes only)"
}
register_builtin_option g_pp_compact_let : Bool := {
defValue := false
group := "pp"
descr := "(pretty printer) minimal indentation at `let`-declarations"
}
-/
def getPPAll (o : Options) : Bool := o.get `pp.all false
def getPPBinderTypes (o : Options) : Bool := o.get `pp.binder_types true
def getPPCoercions (o : Options) : Bool := o.get `pp.coercions (!getPPAll o)
def getPPExplicit (o : Options) : Bool := o.get `pp.explicit (getPPAll o)
def getPPNotation (o : Options) : Bool := o.get `pp.notation (!getPPAll o)
def getPPStructureProjections (o : Options) : Bool := o.get `pp.structure_projections (!getPPAll o)
def getPPStructureInstances (o : Options) : Bool := o.get `pp.structure_instances (!getPPAll o)
def getPPStructureInstanceType (o : Options) : Bool := o.get `pp.structure_instance_type (getPPAll o)
def getPPUniverses (o : Options) : Bool := o.get `pp.universes (getPPAll o)
def getPPFullNames (o : Options) : Bool := o.get `pp.full_names (getPPAll o)
def getPPPrivateNames (o : Options) : Bool := o.get `pp.private_names (getPPAll o)
def getPPUnicode (o : Options) : Bool := o.get `pp.unicode true
def getPPSafeShadowing (o : Options) : Bool := o.get `pp.safe_shadowing true
def getPPProofs (o : Options) : Bool := o.get pp.proofs.name (getPPAll o)
def getPPProofsWithType (o : Options) : Bool := o.get pp.proofs.withType.name true
def getPPMotivesPi (o : Options) : Bool := o.get pp.motives.pi.name pp.motives.pi.defValue
def getPPMotivesNonConst (o : Options) : Bool := o.get pp.motives.nonConst.name pp.motives.nonConst.defValue
def getPPMotivesAll (o : Options) : Bool := o.get pp.motives.all.name pp.motives.all.defValue
/-- Associate pretty printer options to a specific subterm using a synthetic position. -/
abbrev OptionsPerPos := Std.RBMap Nat Options compare
namespace PrettyPrinter
namespace Delaborator
open Lean.Meta
structure Context where
-- In contrast to other systems like the elaborator, we do not pass the current term explicitly as a
-- parameter, but store it in the monad so that we can keep it in sync with `pos`.
expr : Expr
pos : Nat := 1
defaultOptions : Options
optionsPerPos : OptionsPerPos
currNamespace : Name
openDecls : List OpenDecl
inPattern : Bool := false -- true whe delaborating `match` patterns
-- Exceptions from delaborators are not expected. We use an internal exception to signal whether
-- the delaborator was able to produce a Syntax object.
builtin_initialize delabFailureId : InternalExceptionId ← registerInternalExceptionId `delabFailure
abbrev DelabM := ReaderT Context MetaM
abbrev Delab := DelabM Syntax
instance {α} : Inhabited (DelabM α) where
default := throw arbitrary
@[inline] protected def orElse {α} (d₁ d₂ : DelabM α) : DelabM α := do
catchInternalId delabFailureId d₁ (fun _ => d₂)
protected def failure {α} : DelabM α := throw $ Exception.internal delabFailureId
instance : Alternative DelabM := {
orElse := Delaborator.orElse,
failure := Delaborator.failure
}
-- HACK: necessary since it would otherwise prefer the instance from MonadExcept
instance {α} : OrElse (DelabM α) := ⟨Delaborator.orElse⟩
-- Macro scopes in the delaborator output are ultimately ignored by the pretty printer,
-- so give a trivial implementation.
instance : MonadQuotation DelabM := {
getCurrMacroScope := pure arbitrary,
getMainModule := pure arbitrary,
withFreshMacroScope := fun x => x
}
unsafe def mkDelabAttribute : IO (KeyedDeclsAttribute Delab) :=
KeyedDeclsAttribute.init {
builtinName := `builtinDelab,
name := `delab,
descr := "Register a delaborator.
[delab k] registers a declaration of type `Lean.PrettyPrinter.Delaborator.Delab` for the `Lean.Expr`
constructor `k`. Multiple delaborators for a single constructor are tried in turn until
the first success. If the term to be delaborated is an application of a constant `c`,
elaborators for `app.c` are tried first; this is also done for `Expr.const`s (\"nullary applications\")
to reduce special casing. If the term is an `Expr.mdata` with a single key `k`, `mdata.k`
is tried first.",
valueTypeName := `Lean.PrettyPrinter.Delaborator.Delab
} `Lean.PrettyPrinter.Delaborator.delabAttribute
@[builtinInit mkDelabAttribute] constant delabAttribute : KeyedDeclsAttribute Delab
def getExpr : DelabM Expr := do
let ctx ← read
pure ctx.expr
def getExprKind : DelabM Name := do
let e ← getExpr
pure $ match e with
| Expr.bvar _ _ => `bvar
| Expr.fvar _ _ => `fvar
| Expr.mvar _ _ => `mvar
| Expr.sort _ _ => `sort
| Expr.const c _ _ =>
-- we identify constants as "nullary applications" to reduce special casing
`app ++ c
| Expr.app fn _ _ => match fn.getAppFn with
| Expr.const c _ _ => `app ++ c
| _ => `app
| Expr.lam _ _ _ _ => `lam
| Expr.forallE _ _ _ _ => `forallE
| Expr.letE _ _ _ _ _ => `letE
| Expr.lit _ _ => `lit
| Expr.mdata m _ _ => match m.entries with
| [(key, _)] => `mdata ++ key
| _ => `mdata
| Expr.proj _ _ _ _ => `proj
/-- Evaluate option accessor, using subterm-specific options if set. -/
def getPPOption (opt : Options → Bool) : DelabM Bool := do
let ctx ← read
let mut opts := ctx.defaultOptions
if let some opts' ← ctx.optionsPerPos.find? ctx.pos then
for (k, v) in opts' do
opts := opts.insert k v
return opt opts
def whenPPOption (opt : Options → Bool) (d : Delab) : Delab := do
let b ← getPPOption opt
if b then d else failure
def whenNotPPOption (opt : Options → Bool) (d : Delab) : Delab := do
let b ← getPPOption opt
if b then failure else d
/--
Descend into `child`, the `childIdx`-th subterm of the current term, and update position.
Because `childIdx < 3` in the case of `Expr`, we can injectively map a path
`childIdxs` to a natural number by computing the value of the 3-ary representation
`1 :: childIdxs`, since n-ary representations without leading zeros are unique.
Note that `pos` is initialized to `1` (case `childIdxs == []`).
-/
def descend {α} (child : Expr) (childIdx : Nat) (d : DelabM α) : DelabM α :=
withReader (fun cfg => { cfg with expr := child, pos := cfg.pos * 3 + childIdx }) d
def withAppFn {α} (d : DelabM α) : DelabM α := do
let Expr.app fn _ _ ← getExpr | unreachable!
descend fn 0 d
def withAppArg {α} (d : DelabM α) : DelabM α := do
let Expr.app _ arg _ ← getExpr | unreachable!
descend arg 1 d
partial def withAppFnArgs {α} : DelabM α → (α → DelabM α) → DelabM α
| fnD, argD => do
let Expr.app fn arg _ ← getExpr | fnD
let a ← withAppFn (withAppFnArgs fnD argD)
withAppArg (argD a)
def withBindingDomain {α} (d : DelabM α) : DelabM α := do
let e ← getExpr
descend e.bindingDomain! 0 d
def withBindingBody {α} (n : Name) (d : DelabM α) : DelabM α := do
let e ← getExpr
withLocalDecl n e.binderInfo e.bindingDomain! fun fvar =>
let b := e.bindingBody!.instantiate1 fvar
descend b 1 d
def withProj {α} (d : DelabM α) : DelabM α := do
let Expr.proj _ _ e _ ← getExpr | unreachable!
descend e 0 d
def withMDataExpr {α} (d : DelabM α) : DelabM α := do
let Expr.mdata _ e _ ← getExpr | unreachable!
-- do not change position so that options on an mdata are automatically forwarded to the child
withReader ({ · with expr := e }) d
partial def annotatePos (pos : Nat) : Syntax → Syntax
| stx@(Syntax.ident _ _ _ _) => stx.setInfo (SourceInfo.synthetic pos pos)
-- app => annotate function
| stx@(Syntax.node `Lean.Parser.Term.app args) => stx.modifyArg 0 (annotatePos pos)
-- otherwise, annotate first direct child token if any
| stx => match stx.getArgs.findIdx? Syntax.isAtom with
| some idx => stx.modifyArg idx (Syntax.setInfo (SourceInfo.synthetic pos pos))
| none => stx
def annotateCurPos (stx : Syntax) : Delab := do
let ctx ← read
pure $ annotatePos ctx.pos stx
def getUnusedName (suggestion : Name) (body : Expr) : DelabM Name := do
-- Use a nicer binder name than `[anonymous]`. We probably shouldn't do this in all LocalContext use cases, so do it here.
let suggestion := if suggestion.isAnonymous then `a else suggestion;
let suggestion := suggestion.eraseMacroScopes
let lctx ← getLCtx
if !lctx.usesUserName suggestion then
return suggestion
else if (← getPPOption getPPSafeShadowing) && !bodyUsesSuggestion lctx suggestion then
return suggestion
else
return lctx.getUnusedName suggestion
where
bodyUsesSuggestion (lctx : LocalContext) (suggestion' : Name) : Bool :=
Option.isSome <| body.find? fun
| Expr.fvar fvarId _ =>
match lctx.find? fvarId with
| none => false
| some decl => decl.userName == suggestion'
| _ => false
def withBindingBodyUnusedName {α} (d : Syntax → DelabM α) : DelabM α := do
let n ← getUnusedName (← getExpr).bindingName! (← getExpr).bindingBody!
let stxN ← annotateCurPos (mkIdent n)
withBindingBody n $ d stxN
@[inline] def liftMetaM {α} (x : MetaM α) : DelabM α :=
liftM x
partial def delabFor : Name → Delab
| Name.anonymous => failure
| k => do
(delabAttribute.getValues (← getEnv) k).firstM id >>= annotateCurPos <|>
-- have `app.Option.some` fall back to `app` etc.
delabFor k.getRoot
partial def delab : Delab := do
unless (← getPPOption getPPProofs) do
let e ← getExpr
-- no need to hide atomic proofs
unless e.isAtomic do
try
let ty ← Meta.inferType (← getExpr)
if ← Meta.isProp ty then
if ← getPPOption getPPProofsWithType then
return ← ``((_ : $(← descend ty 0 delab)))
else
return ← ``(_)
catch _ => pure ()
let k ← getExprKind
delabFor k <|> (liftM $ show MetaM Syntax from throwError "don't know how to delaborate '{k}'")
unsafe def mkAppUnexpanderAttribute : IO (KeyedDeclsAttribute Unexpander) :=
KeyedDeclsAttribute.init {
name := `appUnexpander,
descr := "Register an unexpander for applications of a given constant.
[appUnexpander c] registers a `Lean.PrettyPrinter.Unexpander` for applications of the constant `c`. The unexpander is
passed the result of pre-pretty printing the application *without* implicitly passed arguments. If `pp.explicit` is set
to true or `pp.notation` is set to false, it will not be called at all.",
valueTypeName := `Lean.PrettyPrinter.Unexpander
} `Lean.PrettyPrinter.Delaborator.appUnexpanderAttribute
@[builtinInit mkAppUnexpanderAttribute] constant appUnexpanderAttribute : KeyedDeclsAttribute Unexpander
end Delaborator
/-- "Delaborate" the given term into surface-level syntax using the default and given subterm-specific options. -/
def delab (currNamespace : Name) (openDecls : List OpenDecl) (e : Expr) (optionsPerPos : OptionsPerPos := {}) : MetaM Syntax := do
trace[PrettyPrinter.delab.input] "{Std.format e}"
let mut opts ← MonadOptions.getOptions
-- default `pp.proofs` to `true` if `e` is a proof
if pp.proofs.get? opts == none then
try
let ty ← Meta.inferType e
if ← Meta.isProp ty then
opts := pp.proofs.set opts true
catch _ => pure ()
catchInternalId Delaborator.delabFailureId
(Delaborator.delab.run { expr := e, defaultOptions := opts, optionsPerPos := optionsPerPos, currNamespace := currNamespace, openDecls := openDecls })
(fun _ => unreachable!)
builtin_initialize registerTraceClass `PrettyPrinter.delab
end PrettyPrinter
end Lean