/- 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.Meta import Init.Lean.Parser.Module namespace Lean namespace Elab /- structure ElabContext := (fileName : String) (fileMap : FileMap) structure ElabScope := (cmd : String) (header : Name) (options : Options := {}) (ns : Name := Name.anonymous) -- current namespace (openDecls : List OpenDecl := []) (univs : List Name := []) (lctx : LocalContext := {}) (nextInstIdx : Nat := 1) (inPattern : Bool := false) namespace ElabScope instance : Inhabited ElabScope := ⟨{ cmd := "", header := arbitrary _ }⟩ end ElabScope structure ElabState := (env : Environment) (messages : MessageLog := {}) (cmdPos : String.Pos := 0) (ngen : NameGenerator := {}) (mctx : MetavarContext := {}) (scopes : List ElabScope := [{ cmd := "root", header := Name.anonymous }]) inductive ElabException | io : IO.Error → ElabException | msg : Message → ElabException | kernel : KernelException → ElabException | other : String → ElabException /- ElabException.silent is used when we log an error in `messages`, and then want to interrupt the elaborator execution. We use it to make sure the top-level handler does not record it again in `messages`. See `logErrorAndThrow` -/ | silent : ElabException namespace ElabException instance : Inhabited ElabException := ⟨other "error"⟩ end ElabException abbrev Elab := ReaderT ElabContext (EStateM ElabException ElabState) instance str2ElabException : HasCoe String ElabException := ⟨ElabException.other⟩ abbrev TermElab := SyntaxNode Expr → Option Expr → Elab (Syntax Expr) abbrev CommandElab := SyntaxNode → Elab Unit abbrev TermElabTable : Type := SMap SyntaxNodeKind TermElab abbrev CommandElabTable : Type := SMap SyntaxNodeKind CommandElab def mkBuiltinTermElabTable : IO (IO.Ref TermElabTable) := IO.mkRef {} def mkBuiltinCommandElabTable : IO (IO.Ref CommandElabTable) := IO.mkRef {} @[init mkBuiltinTermElabTable] constant builtinTermElabTable : IO.Ref TermElabTable := arbitrary _ @[init mkBuiltinCommandElabTable] constant builtinCommandElabTable : IO.Ref CommandElabTable := arbitrary _ 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 := mkApp (mkConst `IO) (mkConst `Unit); let val := mkAppN (mkConst 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 -- TODO: pretty print error | Except.error _ => throw (IO.userError ("failed to emit registration code for builtin term elaborator '" ++ toString declName ++ "'")) | Except.ok 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 := arbitrary _, ext := arbitrary _, 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 _ => #[], -- 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 := arbitrary _ abbrev CommandElabAttribute := ElabAttribute CommandElabTable def mkCommandElabAttribute : IO CommandElabAttribute := mkElabAttribute `commandTerm "command" builtinCommandElabTable @[init mkCommandElabAttribute] constant commandElabAttribute : CommandElabAttribute := arbitrary _ namespace Elab def logMessage (msg : Message) : Elab Unit := modify $ fun s => { messages := s.messages.add msg, .. s } def getPosition (pos : Option String.Pos := none) : Elab Position := do ctx ← read; s ← get; pure $ ctx.fileMap.toPosition (pos.getD s.cmdPos) def mkMessage (msg : String) (pos : Option String.Pos := none) : Elab Message := do ctx ← read; s ← get; let pos := ctx.fileMap.toPosition (pos.getD s.cmdPos); pure { fileName := ctx.fileName, pos := pos, data := msg } def logErrorAt (pos : String.Pos) (errorMsg : String) : Elab Unit := mkMessage errorMsg pos >>= logMessage 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 logElabException (e : ElabException) : Elab Unit := let log (msg : Message) : Elab Unit := modify $ fun s => { messages := s.messages.add msg, .. s }; match e with | ElabException.silent => pure () -- do nothing since message was already logged | ElabException.msg m => log m | ElabException.io e => mkMessage (toString e) >>= log | ElabException.other e => mkMessage e >>= log | ElabException.kernel e => match e with | KernelException.other msg => mkMessage msg >>= log | _ => mkMessage "kernel exception" >>= log -- TODO(pretty print them) def logErrorAndThrow {α β : Type} (stx : Syntax β) (errorMsg : String) : Elab α := do logError stx errorMsg; throw ElabException.silent def logUnknownDecl {α} (stx : Syntax α) (declName : Name) : Elab Unit := logError stx ("unknown declaration '" ++ toString declName ++ "'") def getEnv : Elab Environment := do s ← get; pure s.env def setEnv (env : Environment) : Elab Unit := modify $ fun s => { env := env, .. s } 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 ElabContext := (fileName : String) (fileMap : FileMap) inductive ElabException | io : IO.Error → ElabException | msg : Message → ElabException | kernel : KernelException → ElabException structure ElabState := (dummy : Unit := ()) structure FrontendState := (elabState : ElabState) (parserState : Parser.ModuleParserState) abbrev Frontend := ReaderT Parser.ParserContextCore (EStateM 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 $ EStateM.adaptState (fun (s : FrontendState) => (s.elabState, s.parserState)) (fun es ps => { elabState := es, parserState := ps }) (x c) 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 () def testFrontend (input : String) (fileName : Option String := none) : IO (Environment × MessageLog) := do env ← mkEmptyEnvironment; let fileName := fileName.getD ""; let ctx := Parser.mkParserContextCore env input fileName; match Parser.parseHeader env ctx with | (header, parserState, messages) => do (env, messages) ← processHeader header messages ctx; let elabState := { ElabState . env := env, messages := messages }; match (processCommands ctx).run { elabState := elabState, parserState := parserState } with | EStateM.Result.ok _ s => pure (s.elabState.env, s.elabState.messages) | EStateM.Result.error _ s => pure (s.elabState.env, s.elabState.messages) instance {α} : Inhabited (Elab α) := ⟨fun _ => arbitrary _⟩ def mkFreshName : Elab Name := modifyGet $ fun s => (s.ngen.curr, { ngen := s.ngen.next, .. s }) def getScope : Elab ElabScope := do s ← get; pure s.scopes.head! def getOpenDecls : Elab (List OpenDecl) := ElabScope.openDecls <$> getScope def getUniverses : Elab (List Name) := ElabScope.univs <$> getScope def getNamespace : Elab Name := do s ← get; match s.scopes with | [] => pure Name.anonymous | (sc::_) => pure sc.ns @[specialize] def modifyScope (f : ElabScope → ElabScope) : Elab Unit := modify $ fun s => { scopes := match s.scopes with | h::t => f h :: t | [] => [], -- unreachable .. s } @[specialize] def modifyGetScope {α} [Inhabited α] (f : ElabScope → α × ElabScope) : Elab α := modifyGet $ fun s => match s with | { scopes := h::t, .. } => let (a, h) := f h; (a, { scopes := h :: t, .. s }) | _ => (arbitrary _, s) def localContext : Elab LocalContext := do scope ← getScope; pure scope.lctx def mkLocalDecl (userName : Name) (type : Expr) (bi : BinderInfo := BinderInfo.default) : Elab Expr := do idx ← mkFreshName; modifyScope $ fun scope => { lctx := scope.lctx.mkLocalDecl idx userName type bi, .. scope }; pure (mkFVar idx) def mkLambda (xs : Array Expr) (b : Expr) : Elab Expr := do lctx ← localContext; pure $ lctx.mkLambda xs b def mkForall (xs : Array Expr) (b : Expr) : Elab Expr := do lctx ← localContext; pure $ lctx.mkForall xs b def anonymousInstNamePrefix := `_inst def mkAnonymousInstName : Elab Name := do scope ← getScope; let n := anonymousInstNamePrefix.appendIndexAfter scope.nextInstIdx; modifyScope $ fun scope => { nextInstIdx := scope.nextInstIdx + 1, .. scope }; pure n 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 ++ "'")) @[inline] def withNewScope {α} (x : Elab α) : Elab α := do modify $ fun s => { scopes := s.scopes.head! :: s.scopes, .. s }; a ← x; modify $ fun s => { scopes := s.scopes.tail!, .. s}; pure a @[inline] def withInPattern {α} (x : Elab α) : Elab α := withNewScope $ do modifyScope $ fun scope => { inPattern := true, .. scope }; x def inPattern : Elab Bool := do scope ← getScope; pure $ scope.inPattern /- 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 α := arbitrary _ end Elab -/ end Elab end Lean