diff --git a/src/Init/Lean/Elab/Command.lean b/src/Init/Lean/Elab/Command.lean
index e58f05f1a8..613441b5bc 100644
--- a/src/Init/Lean/Elab/Command.lean
+++ b/src/Init/Lean/Elab/Command.lean
@@ -14,10 +14,6 @@ namespace Lean
namespace Elab
namespace Command
-structure Context :=
-(fileName : String)
-(fileMap : FileMap)
-
structure Scope :=
(kind : String)
(header : String)
@@ -32,7 +28,6 @@ instance Scope.inhabited : Inhabited Scope := ⟨{ kind := "", header := "" }⟩
structure State :=
(env : Environment)
(messages : MessageLog := {})
-(cmdPos : String.Pos := 0)
(scopes : List Scope := [{ kind := "root", header := "" }])
instance State.inhabited : Inhabited State := ⟨{ env := arbitrary _ }⟩
@@ -40,11 +35,44 @@ instance State.inhabited : Inhabited State := ⟨{ env := arbitrary _ }⟩
def mkState (env : Environment) (messages : MessageLog := {}) (opts : Options := {}) : State :=
{ env := env, messages := messages, scopes := [{ kind := "root", header := "", opts := opts }] }
-abbrev CommandElabM := ReaderT Context (EStateM Exception State)
+structure Context :=
+(fileName : String)
+(fileMap : FileMap)
+(stateRef : IO.Ref State)
+(cmdPos : String.Pos := 0)
+
+abbrev CommandElabCoreM (ε) := ReaderT Context (EIO ε)
+abbrev CommandElabM := CommandElabCoreM Exception
abbrev CommandElab := SyntaxNode → CommandElabM Unit
+def mkMessageAux (ctx : Context) (ref : Syntax) (msgData : MessageData) (severity : MessageSeverity) : Message :=
+mkMessageCore ctx.fileName ctx.fileMap msgData severity (ref.getPos.getD ctx.cmdPos)
+
+private def ioErrorToMessage (ctx : Context) (ref : Syntax) (err : IO.Error) : Message :=
+mkMessageAux ctx ref ("IO error, " ++ toString err) MessageSeverity.error
+
+@[inline] def liftIOCore {α} (ctx : Context) (ref : Syntax) (x : IO α) : EIO Exception α :=
+EIO.adaptExcept (ioErrorToMessage ctx ref) x
+
+@[inline] def liftIO {α} (ref : Syntax) (x : IO α) : CommandElabM α :=
+fun ctx => liftIOCore ctx ref x
+
+private def getState : CommandElabM State :=
+fun ctx => liftIOCore ctx Syntax.missing $ ctx.stateRef.get
+
+private def setState (s : State) : CommandElabM Unit :=
+fun ctx => liftIOCore ctx Syntax.missing $ ctx.stateRef.set s
+
+@[inline] private def modifyGetState {α} (f : State → α × State) : CommandElabM α := do
+s ← getState; let (a, s) := f s; setState s; pure a
+
+instance CommandElabCoreM.monadState : MonadState State CommandElabM :=
+{ get := getState,
+ set := setState,
+ modifyGet := @modifyGetState }
+
instance CommandElabM.monadLog : MonadLog CommandElabM :=
-{ getCmdPos := do s ← get; pure s.cmdPos,
+{ getCmdPos := do ctx ← read; pure ctx.cmdPos,
getFileMap := do ctx ← read; pure ctx.fileMap,
getFileName := do ctx ← read; pure ctx.fileName,
logMessage := fun msg => modify $ fun s => { messages := s.messages.add msg, .. s } }
@@ -107,7 +135,7 @@ let scope := s.scopes.head!;
{ config := { opts := scope.opts, foApprox := true, ctxApprox := true, quasiPatternApprox := true, isDefEqStuckEx := true },
fileName := ctx.fileName,
fileMap := ctx.fileMap,
- cmdPos := s.cmdPos,
+ cmdPos := ctx.cmdPos,
currNamespace := scope.currNamespace,
univNames := scope.univNames,
openDecls := scope.openDecls }
@@ -125,8 +153,22 @@ match result with
| EStateM.Result.error (Term.Exception.error ex) newS => EStateM.Result.error ex { env := newS.env, messages := newS.messages, .. s }
| EStateM.Result.error Term.Exception.postpone newS => unreachable!
-@[inline] def runTermElabM {α} (x : TermElabM α) : CommandElabM α :=
-fun ctx s => toCommandResult ctx s $ (Term.elabBinders (getVarDecls s) (fun _ => x)) (mkTermContext ctx s) (mkTermState s)
+instance CommandElabM.inhabited {α} : Inhabited (CommandElabM α) :=
+⟨throw $ arbitrary _⟩
+
+@[inline] def runTermElabM {α} (x : TermElabM α) : CommandElabM α := do
+ctx ← read;
+s ← get;
+match ((Term.elabBinders (getVarDecls s) (fun _ => x)) (mkTermContext ctx s)).run (mkTermState s) with
+| EStateM.Result.ok a newS => do modify $ fun s => { env := newS.env, messages := newS.messages, .. s }; pure a
+| EStateM.Result.error (Term.Exception.error ex) newS => do modify $ fun s => { env := newS.env, messages := newS.messages, .. s }; throw ex
+| EStateM.Result.error Term.Exception.postpone newS => unreachable!
+
+@[inline] def withLogging (x : CommandElabM Unit) : CommandElabM Unit :=
+catch x (fun ex => do logMessage ex; pure ())
+
+@[inline] def catchExceptions (x : CommandElabM Unit) : CommandElabCoreM Empty Unit :=
+fun ctx => EIO.catchExceptions (withLogging x ctx) (fun _ => pure ())
def dbgTrace {α} [HasToString α] (a : α) : CommandElabM Unit :=
_root_.dbgTrace (toString a) $ fun _ => pure ()
diff --git a/src/Init/Lean/Elab/Frontend.lean b/src/Init/Lean/Elab/Frontend.lean
index 9bdca702fa..cd8db204fd 100644
--- a/src/Init/Lean/Elab/Frontend.lean
+++ b/src/Init/Lean/Elab/Frontend.lean
@@ -11,40 +11,52 @@ namespace Lean
namespace Elab
namespace Frontend
-structure State :=
-(commandState : Command.State)
-(parserState : Parser.ModuleParserState)
+structure Context :=
+(commandStateRef : IO.Ref Command.State)
+(parserStateRef : IO.Ref Parser.ModuleParserState)
+(cmdPosRef : IO.Ref String.Pos)
+(parserCtx : Parser.ParserContextCore)
-abbrev FrontendM := ReaderT Parser.ParserContextCore (StateM State)
+abbrev FrontendM := ReaderT Context (EIO Empty)
-private def getCmdContext : FrontendM Command.Context := do
-c ← read;
-pure { fileName := c.fileName, fileMap := c.fileMap }
+@[inline] def liftIOCore! {α} [Inhabited α] (x : IO α) : EIO Empty α :=
+EIO.catchExceptions x (fun _ => unreachable!)
-@[inline] def runCommandElabM (x : Command.CommandElabM Unit) : FrontendM Unit := do
-fun ctx s =>
- let parserState := s.parserState;
- match x { fileName := ctx.fileName, fileMap := ctx.fileMap } s.commandState with
- | EStateM.Result.ok _ newCmdState => ((), { commandState := newCmdState, parserState := parserState })
- | EStateM.Result.error ex newCmdState =>
- let newCmdState := { messages := newCmdState.messages.add ex, .. newCmdState };
- ((), { commandState := newCmdState, parserState := parserState })
+@[inline] def runCommandElabM (x : Command.CommandElabM Unit) : FrontendM Unit :=
+fun ctx => do
+ cmdPos ← liftIOCore! $ ctx.cmdPosRef.get;
+ let cmdCtx : Command.Context := { cmdPos := cmdPos, stateRef := ctx.commandStateRef, fileName := ctx.parserCtx.fileName, fileMap := ctx.parserCtx.fileMap };
+ EIO.catchExceptions (x cmdCtx) (fun _ => pure ())
def elabCommandAtFrontend (stx : Syntax) : FrontendM Unit :=
-runCommandElabM (Command.elabCommand stx)
+runCommandElabM (Command.withLogging $ Command.elabCommand stx)
def updateCmdPos : FrontendM Unit :=
-modify $ fun s => { commandState := { cmdPos := s.parserState.pos, .. s.commandState }, .. s }
+fun ctx => do
+ parserState ← liftIOCore! $ ctx.parserStateRef.get;
+ liftIOCore! $ ctx.cmdPosRef.set parserState.pos
+
+def getParserState : FrontendM Parser.ModuleParserState :=
+fun ctx => liftIOCore! $ ctx.parserStateRef.get
+
+def getCommandState : FrontendM Command.State :=
+fun ctx => liftIOCore! $ ctx.commandStateRef.get
+
+def setParserState (ps : Parser.ModuleParserState) : FrontendM Unit :=
+fun ctx => liftIOCore! $ ctx.parserStateRef.set ps
+
+def setMessages (msgs : MessageLog) : FrontendM Unit :=
+fun ctx => liftIOCore! $ ctx.commandStateRef.modify $ fun s => { messages := msgs, .. s }
def processCommand : FrontendM Bool := do
updateCmdPos;
-s ← get;
-let cs := s.commandState;
-let ps := s.parserState;
+cs ← getCommandState;
+ps ← getParserState;
c ← read;
-match Parser.parseCommand cs.env c ps cs.messages with
+match Parser.parseCommand cs.env c.parserCtx ps cs.messages with
| (cmd, ps, messages) => do
- set { commandState := { messages := messages, .. cs }, parserState := ps };
+ setParserState ps;
+ setMessages messages;
if Parser.isEOI cmd || Parser.isExitCommand cmd then do
pure true -- Done
else do
@@ -64,23 +76,33 @@ end Frontend
open Frontend
-def process (input : String) (env : Environment) (opts : Options) (fileName : Option String := none) : Environment × MessageLog :=
-let fileName := fileName.getD "";
-let ctx := Parser.mkParserContextCore env input fileName;
-let cmdState := Command.mkState env {} opts;
-match (processCommands ctx).run { commandState := cmdState, parserState := {} } with
-| (_, { commandState := { env := env, messages := messages, .. }, .. }) => (env, messages)
+def IO.processCommands (parserCtx : Parser.ParserContextCore) (parserStateRef : IO.Ref Parser.ModuleParserState) (cmdStateRef : IO.Ref Command.State) : IO Unit := do
+ps ← parserStateRef.get;
+cmdPosRef ← IO.mkRef ps.pos;
+EIO.adaptExcept (fun (ex : Empty) => unreachable!) $ -- TODO: compiler support for Empty.rec is missing
+ processCommands { commandStateRef := cmdStateRef, parserStateRef := parserStateRef, cmdPosRef := cmdPosRef, parserCtx := parserCtx }
+
+def process (input : String) (env : Environment) (opts : Options) (fileName : Option String := none) : IO (Environment × MessageLog) := do
+let fileName := fileName.getD "";
+let parserCtx := Parser.mkParserContextCore env input fileName;
+parserStateRef ← IO.mkRef { Parser.ModuleParserState . };
+cmdStateRef ← IO.mkRef $ Command.mkState env {} opts;
+IO.processCommands parserCtx parserStateRef cmdStateRef;
+cmdState ← cmdStateRef.get;
+pure (cmdState.env, cmdState.messages)
def testFrontend (input : String) (opts : Options := {}) (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
+let parserCtx := Parser.mkParserContextCore env input fileName;
+match Parser.parseHeader env parserCtx with
| (header, parserState, messages) => do
- (env, messages) ← processHeader header messages ctx;
- let cmdState := Command.mkState env messages opts;
- match (processCommands ctx).run { commandState := cmdState, parserState := parserState } with
- | (_, { commandState := { env := env, messages := messages, .. }, .. }) => pure (env, messages)
+ (env, messages) ← processHeader header messages parserCtx;
+ parserStateRef ← IO.mkRef parserState;
+ cmdStateRef ← IO.mkRef $ Command.mkState env messages opts;
+ IO.processCommands parserCtx parserStateRef cmdStateRef;
+ cmdState ← cmdStateRef.get;
+ pure (cmdState.env, cmdState.messages)
end Elab
end Lean
diff --git a/src/Init/System/IO.lean b/src/Init/System/IO.lean
index 9e6d0a627a..14ef4b2ce0 100644
--- a/src/Init/System/IO.lean
+++ b/src/Init/System/IO.lean
@@ -26,9 +26,9 @@ def EIO (ε : Type) : Type → Type := EStateM ε IO.RealWorld
@[inline] def EIO.adaptExcept {α ε ε'} (f : ε → ε') (x : EIO ε α) : EIO ε' α :=
EStateM.adaptExcept f x
-@[inline] def EIO.catchExceptions {ε} (x : EIO ε Unit) (h : ε → EIO Empty Unit) : EIO Empty Unit :=
+@[inline] def EIO.catchExceptions {α ε} (x : EIO ε α) (h : ε → EIO Empty α) : EIO Empty α :=
fun s => match x s with
-| EStateM.Result.ok _ s => EStateM.Result.ok () s
+| EStateM.Result.ok a s => EStateM.Result.ok a s
| EStateM.Result.error ex s => h ex s
instance (ε : Type) : Monad (EIO ε) := inferInstanceAs (Monad (EStateM ε IO.RealWorld))
diff --git a/tests/lean/run/frontend1.lean b/tests/lean/run/frontend1.lean
index ef0785b473..37042a2dce 100644
--- a/tests/lean/run/frontend1.lean
+++ b/tests/lean/run/frontend1.lean
@@ -5,7 +5,7 @@ open Lean.Elab
def run (input : String) (failIff : Bool := true) : MetaIO Unit :=
do env ← MetaIO.getEnv;
opts ← MetaIO.getOptions;
- let (env, messages) := process input env opts;
+ (env, messages) ← liftM $ process input env opts;
messages.forM $ fun msg => IO.println msg;
when (failIff && messages.hasErrors) $ throw (IO.userError "errors have been found");
when (!failIff && !messages.hasErrors) $ throw (IO.userError "there are no errors");
diff --git a/tests/lean/run/termParserAttr.lean b/tests/lean/run/termParserAttr.lean
index 0d509672c5..b6342aba1f 100644
--- a/tests/lean/run/termParserAttr.lean
+++ b/tests/lean/run/termParserAttr.lean
@@ -5,7 +5,7 @@ open Lean.Elab
def run (input : String) (failIff : Bool := true) : MetaIO Unit :=
do env ← MetaIO.getEnv;
opts ← MetaIO.getOptions;
- let (env, messages) := process input env opts;
+ (env, messages) ← liftM $ process input env opts;
messages.toList.forM $ fun msg => IO.println msg;
when (failIff && messages.hasErrors) $ throw (IO.userError "errors have been found");
when (!failIff && !messages.hasErrors) $ throw (IO.userError "there are no errors");