lean4-htt/library/init/lean/elaborator/basic.lean

435 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) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
prelude
import init.control.reader
import init.lean.namegenerator
import init.lean.scopes
import init.lean.parser.module
namespace Lean
inductive OpenDecl
| simple (ns : Name)
| explicit (ns : Name) (ids : List Name)
| «hiding» (ns : Name) (ex : List Name)
| «renaming» (ns : Name) (renames : List (Name × Name))
structure ElabContext :=
(fileName : String)
(fileMap : FileMap)
structure ElabScope :=
(cmd : String)
(header : Name)
(options : Options := {})
(ns : Name := Name.anonymous) -- current namespace
(openDecls : List OpenDecl := [])
namespace ElabScope
instance : Inhabited ElabScope := ⟨{ cmd := "", header := default _ }⟩
end ElabScope
structure ElabState :=
(env : Environment)
(messages : MessageLog := {})
(cmdPos : String.Pos := 0)
(ngen : NameGenerator := {})
(scopes : List ElabScope := [{ cmd := "root", header := Name.anonymous }])
inductive ElabException
| io : IO.Error → ElabException
| msg : Message → ElabException
| other : String → ElabException
namespace ElabException
instance : Inhabited ElabException := ⟨other "error"⟩
end ElabException
abbrev Elab := ReaderT ElabContext (EState ElabException ElabState)
instance str2ElabException : HasCoe String ElabException := ⟨ElabException.other⟩
abbrev TermElab := SyntaxNode → Elab Expr
abbrev CommandElab := SyntaxNode → Elab Unit
abbrev TermElabTable : Type := SMap SyntaxNodeKind TermElab Name.quickLt
abbrev CommandElabTable : Type := SMap SyntaxNodeKind CommandElab Name.quickLt
def mkBuiltinTermElabTable : IO (IO.Ref TermElabTable) := IO.mkRef {}
def mkBuiltinCommandElabTable : IO (IO.Ref CommandElabTable) := IO.mkRef {}
@[init mkBuiltinTermElabTable]
constant builtinTermElabTable : IO.Ref TermElabTable := default _
@[init mkBuiltinCommandElabTable]
constant builtinCommandElabTable : IO.Ref CommandElabTable := default _
def addBuiltinTermElab (k : SyntaxNodeKind) (declName : Name) (elab : TermElab) : IO Unit :=
do m ← builtinTermElabTable.get;
when (m.contains k) $
throw (IO.userError ("invalid builtin term elaborator, elaborator for '" ++ toString k ++ "' has already been defined"));
builtinTermElabTable.modify $ fun m => m.insert k elab
def addBuiltinCommandElab (k : SyntaxNodeKind) (declName : Name) (elab : CommandElab) : IO Unit :=
do m ← builtinCommandElabTable.get;
when (m.contains k) $
throw (IO.userError ("invalid builtin command elaborator, elaborator for '" ++ toString k ++ "' has already been defined"));
builtinCommandElabTable.modify $ fun m => m.insert k elab
def checkSyntaxNodeKind (k : Name) : IO Name :=
do b ← Parser.isValidSyntaxNodeKind k;
if b then pure k
else throw (IO.userError "failed")
def checkSyntaxNodeKindAtNamespaces (k : Name) : List Name → IO Name
| [] := throw (IO.userError "failed")
| (n::ns) := checkSyntaxNodeKind (n ++ k) <|> checkSyntaxNodeKindAtNamespaces ns
def syntaxNodeKindOfAttrParam (env : Environment) (parserNamespace : Name) (arg : Syntax) : IO SyntaxNodeKind :=
match attrParamSyntaxToIdentifier arg with
| some k =>
checkSyntaxNodeKind k
<|>
checkSyntaxNodeKindAtNamespaces k env.getNamespaces
<|>
checkSyntaxNodeKind (parserNamespace ++ k)
<|>
throw (IO.userError ("invalid syntax node kind '" ++ toString k ++ "'"))
| none => throw (IO.userError ("syntax node kind is missing"))
def declareBuiltinElab (env : Environment) (addFn : Name) (kind : SyntaxNodeKind) (declName : Name) : IO Environment :=
let name := `_regBuiltinTermElab ++ declName;
let type := Expr.app (mkConst `IO) (mkConst `Unit);
let val := mkCApp addFn [toExpr kind, toExpr declName, mkConst declName];
let decl := Declaration.defnDecl { name := name, lparams := [], type := type, value := val, hints := ReducibilityHints.opaque, isUnsafe := false };
match env.addAndCompile {} decl with
| none => throw (IO.userError ("failed to emit registration code for builtin term elaborator '" ++ toString declName ++ "'"))
| some env => IO.ofExcept (setInitAttr env name)
def declareBuiltinTermElab (env : Environment) (kind : SyntaxNodeKind) (declName : Name) : IO Environment :=
declareBuiltinElab env `Lean.addBuiltinTermElab kind declName
def declareBuiltinCommandElab (env : Environment) (kind : SyntaxNodeKind) (declName : Name) : IO Environment :=
declareBuiltinElab env `Lean.addBuiltinCommandElab kind declName
@[init] def registerBuiltinTermElabAttr : IO Unit :=
registerAttribute {
name := `builtinTermElab,
descr := "Builtin term elaborator",
add := fun env declName arg persistent => do {
unless persistent $ throw (IO.userError ("invalid attribute 'builtinTermElab', must be persistent"));
kind ← syntaxNodeKindOfAttrParam env `Lean.Parser.Term arg;
match env.find declName with
| none => throw "unknown declaration"
| some decl =>
match decl.type with
| Expr.const `Lean.TermElab _ => declareBuiltinTermElab env kind declName
| _ => throw (IO.userError ("unexpected term elaborator type at '" ++ toString declName ++ "' `TermElab` expected"))
},
applicationTime := AttributeApplicationTime.afterCompilation
}
@[init] def registerBuiltinCommandElabAttr : IO Unit :=
registerAttribute {
name := `builtinCommandElab,
descr := "Builtin command elaborator",
add := fun env declName arg persistent => do {
unless persistent $ throw (IO.userError ("invalid attribute 'builtinCommandElab', must be persistent"));
kind ← syntaxNodeKindOfAttrParam env `Lean.Parser.Command arg;
match env.find declName with
| none => throw "unknown declaration"
| some decl =>
match decl.type with
| Expr.const `Lean.CommandElab _ => declareBuiltinCommandElab env kind declName
| _ => throw (IO.userError ("unexpected command elaborator type at '" ++ toString declName ++ "' `CommandElab` expected"))
},
applicationTime := AttributeApplicationTime.afterCompilation
}
structure ElabAttributeEntry :=
(kind : SyntaxNodeKind)
(declName : Name)
structure ElabAttribute (σ : Type) :=
(attr : AttributeImpl)
(ext : PersistentEnvExtension ElabAttributeEntry σ)
(kind : String)
namespace ElabAttribute
instance {σ} [Inhabited σ] : Inhabited (ElabAttribute σ) := ⟨{ attr := default _, ext := default _, kind := "" }⟩
end ElabAttribute
/-
This is just the basic skeleton for the `[termElab]` attribute and environment extension.
The state is initialized using `builtinTermElabTable`.
The current implementation just uses the bultin elaborators.
-/
def mkElabAttribute {σ} [Inhabited σ] (attrName : Name) (kind : String) (builtinTable : IO.Ref σ) : IO (ElabAttribute σ) :=
do
ext : PersistentEnvExtension ElabAttributeEntry σ ← registerPersistentEnvExtension {
name := attrName,
addImportedFn := fun es => do
table ← builtinTable.get;
-- TODO: populate table with `es`
pure table,
addEntryFn := fun (s : σ) _ => s, -- TODO
exportEntriesFn := fun _ => Array.empty, -- TODO
statsFn := fun _ => fmt (kind ++ " elaborator attribute") -- TODO
};
let attrImpl : AttributeImpl := {
name := attrName,
descr := kind ++ " elaborator",
add := fun env decl args persistent => pure env -- TODO
};
pure { ext := ext, attr := attrImpl, kind := kind }
abbrev TermElabAttribute := ElabAttribute TermElabTable
def mkTermElabAttribute : IO TermElabAttribute :=
mkElabAttribute `elabTerm "term" builtinTermElabTable
@[init mkTermElabAttribute]
constant termElabAttribute : TermElabAttribute := default _
abbrev CommandElabAttribute := ElabAttribute CommandElabTable
def mkCommandElabAttribute : IO CommandElabAttribute :=
mkElabAttribute `commandTerm "command" builtinCommandElabTable
@[init mkCommandElabAttribute]
constant commandElabAttribute : CommandElabAttribute := default _
def mkMessage (msg : String) (pos : Option String.Pos := none) : Elab Message :=
do ctx ← read;
s ← get;
let pos := ctx.fileMap.toPosition (pos.getOrElse s.cmdPos);
pure { filename := ctx.fileName, pos := pos, text := msg }
def logErrorAt (pos : String.Pos) (errorMsg : String) : Elab Unit :=
do msg ← mkMessage errorMsg pos;
modify (fun s => { messages := s.messages.add msg, .. s })
def logErrorUsingCmdPos (errorMsg : String) : Elab Unit :=
do s ← get;
logErrorAt s.cmdPos errorMsg
def getPos (stx : Syntax) : Elab String.Pos :=
match stx.getPos with
| some p => pure p
| none => do s ← get; pure s.cmdPos
def logError (stx : Syntax) (errorMsg : String) : Elab Unit :=
do pos ← getPos stx;
logErrorAt pos errorMsg
def toMessage : ElabException → Elab Message
| (ElabException.msg m) := pure m
| (ElabException.io e) := mkMessage (toString e)
| (ElabException.other e) := mkMessage e
def logElabException (e : ElabException) : Elab Unit :=
do msg ← toMessage e;
modify (fun s => { messages := s.messages.add msg, .. s })
def logErrorAndThrow {α : Type} (stx : Syntax) (errorMsg : String) : Elab α :=
do logError stx errorMsg;
throw errorMsg
def elabTerm (stx : Syntax) : Elab Expr :=
stx.ifNode
(fun n => do
s ← get;
let tables := termElabAttribute.ext.getState s.env;
let k := n.getKind;
match tables.find k with
| some elab => elab n
| none => logErrorAndThrow stx ("term elaborator failed, no support for syntax '" ++ toString k ++ "'"))
(fun _ => throw "term elaborator failed, unexpected syntax")
def elabCommand (stx : Syntax) : Elab Unit :=
stx.ifNode
(fun n => do
s ← get;
let tables := commandElabAttribute.ext.getState s.env;
let k := n.getKind;
match tables.find k with
| some elab => elab n
| none => logError stx ("command '" ++ toString k ++ "' has not been implemented"))
(fun _ => logErrorUsingCmdPos ("unexpected command"))
structure FrontendState :=
(elabState : ElabState)
(parserState : Parser.ModuleParserState)
abbrev Frontend := ReaderT Parser.ParserContextCore (EState ElabException FrontendState)
def getElabContext : Frontend ElabContext :=
do c ← read;
pure { fileName := c.filename, fileMap := c.fileMap }
@[specialize] def runElab {α} (x : Elab α) : Frontend α :=
do c ← getElabContext;
monadLift $ EState.adaptState (x c)
(fun (s : FrontendState) => (s.elabState, s.parserState))
(fun es ps => { elabState := es, parserState := ps })
def elabCommandAtFrontend (stx : Syntax) : Frontend Unit :=
runElab (elabCommand stx)
def updateCmdPos : Frontend Unit :=
modify $ fun s => { elabState := { cmdPos := s.parserState.pos, .. s.elabState }, .. s }
def processCommand : Frontend Bool :=
do updateCmdPos;
s ← get;
let es := s.elabState;
let ps := s.parserState;
c ← read;
match Parser.parseCommand es.env c ps es.messages with
| (cmd, ps, messages) => do
set { elabState := { messages := messages, .. es }, parserState := ps };
if Parser.isEOI cmd || Parser.isExitCommand cmd then do
pure true -- Done
else do
catch (elabCommandAtFrontend cmd) $ fun e => runElab (logElabException e);
pure false
partial def processCommandsAux : Unit → Frontend Unit
| () := do
done ← processCommand;
if done then pure ()
else processCommandsAux ()
def processCommands : Frontend Unit :=
processCommandsAux ()
@[export lean.absolutize_module_name_core]
def absolutizeModuleName (baseDir : Option String) (m : Name) (k : Option Nat) : IO Name :=
match k, baseDir with
| none, _ => pure m
| some k, none => throw (IO.userError ("invalid use of relative import, file name of main file is not available"))
| some k, some baseDir => do
let dir := addRel baseDir k;
let pathSep := toString System.FilePath.pathSeparator;
let oleanFName := dir ++ pathSep ++ modNameToFileName m ++ toString System.FilePath.extSeparator ++ "olean";
moduleNameOfFileName oleanFName
def processHeaderAux (baseDir : Option String) (header : Syntax) (trustLevel : UInt32) : IO Environment :=
do let header := header.asNode;
let imports := if (header.getArg 0).isNone then [`init.default] else [];
let modImports := (header.getArg 1).getArgs;
imports ← modImports.mfoldl (fun imports stx =>
-- `stx` is of the form `(Module.import "import" (null ...))
let importPaths := (stx.getArg 1).getArgs; -- .asNode.getArgs;
importPaths.mfoldl (fun imports stx => do
-- `stx` is of the form `(Module.importPath (null "*"*) <id>)
let stx := stx.asNode;
let rel := stx.getArg 0;
let k := if rel.isNone then none else some (rel.getNumArgs - 1);
let id := (stx.getArg 1).getIdentVal;
m ← absolutizeModuleName baseDir id k;
pure (m::imports))
imports)
imports;
let imports := imports.reverse;
importModules imports trustLevel
def processHeader (baseDir : Option String) (header : Syntax) (messages : MessageLog) (ctx : Parser.ParserContextCore) (trustLevel : UInt32 := 0) : IO (Environment × MessageLog) :=
catch
(do env ← processHeaderAux baseDir header trustLevel;
pure (env, messages))
(fun e => do
env ← mkEmptyEnvironment;
let spos := header.getPos.getOrElse 0;
let pos := ctx.fileMap.toPosition spos;
pure (env, messages.add { filename := ctx.filename, text := toString e, pos := pos }))
def toBaseDir (fileName : Option String) : IO (Option String) :=
match fileName with
| none => pure none
| some fileName => do
fileName ← IO.realPath fileName;
pure $ some (System.FilePath.dirName fileName)
def testFrontend (input : String) (fileName : Option String := none) : IO (Environment × MessageLog) :=
do env ← mkEmptyEnvironment;
baseDir ← toBaseDir fileName;
let fileName := fileName.getOrElse "<input>";
let ctx := Parser.mkParserContextCore env input fileName;
match Parser.parseHeader env ctx with
| (header, parserState, messages) => do
(env, messages) ← processHeader baseDir header messages ctx;
let elabState := { ElabState . env := env, messages := messages };
match (processCommands ctx).run { elabState := elabState, parserState := parserState } with
| EState.Result.ok _ s => pure (s.elabState.env, s.elabState.messages)
| EState.Result.error _ s => pure (s.elabState.env, s.elabState.messages)
namespace Elab
instance {α} : Inhabited (Elab α) :=
⟨fun _ => default _⟩
def getOpenDecls : Elab (List OpenDecl) :=
do s ← get;
pure s.scopes.head.openDecls
def getNamespace : Elab Name :=
do s ← get;
match s.scopes with
| [] => pure Name.anonymous
| (sc::_) => pure sc.ns
def rootNamespace := `_root_
def removeRoot (n : Name) : Name :=
n.replacePrefix rootNamespace Name.anonymous
def resolveNamespaceUsingScopes (env : Environment) (n : Name) : List ElabScope → Option Name
| [] := none
| ({ ns := ns, .. } :: scopes) := if isNamespace env (ns ++ n) then some (ns ++ n) else resolveNamespaceUsingScopes scopes
def resolveNamespaceUsingOpenDecls (env : Environment) (n : Name) : List OpenDecl → Option Name
| [] := none
| (OpenDecl.simple ns :: ds) := if isNamespace env (ns ++ n) then some (ns ++ n) else resolveNamespaceUsingOpenDecls ds
| (_ :: ds) := resolveNamespaceUsingOpenDecls ds
/-
Given a name `n` try to find namespace it refers to. The resolution procedure works as follows
1- If `n` is the extact name of an existing namespace, then return `n`
2- If `n` is in the scope of `namespace` commands declaring namespace headers `h_1`, ..., `h_n`,
then return `h_1 ++ ... ++ h_i ++ n` if it is the name of an existing namespace. We search "backwards".
3- Finally, for each command `open N`, return `N ++ n` if it is the name of an existing namespace.
We search "backwards" again. That is, we try the most recent `open` command first.
We only consider simple `open` commands.
-/
def resolveNamespace (n : Name) : Elab Name :=
do s ← get;
if isNamespace s.env n then pure n
else match resolveNamespaceUsingScopes s.env n s.scopes with
| some n => pure n
| none => do
openDecls ← getOpenDecls;
match resolveNamespaceUsingOpenDecls s.env n openDecls with
| some n => pure n
| none => throw (ElabException.other ("unknown namespace '" ++ toString n ++ "'"))
/- Remark: in an ideal world where performance doesn't matter, we would define `Elab` as
```
ExceptT ElabException (StateT ElabException IO)
```
and we would not need unsafe features for implementing `runIO`.
We say `Elab` is "morally" built on top of `IO`. -/
unsafe def runIOUnsafe {α : Type} (x : IO α) : Elab α :=
match unsafeIO x with
| Except.ok a => pure a
| Except.error e => throw (ElabException.io e)
@[implementedBy runIOUnsafe]
constant runIO {α : Type} (x : IO α) : Elab α := default _
end Elab
end Lean