File size: 7,117 Bytes
0115dcf | 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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | import Lean.Parser
import Lean.Elab.Frontend
open Lean
namespace Lean.FileMap
/-- Extract the range of a `Syntax` expressed as lines and columns. -/
@[export pantograph_frontend_stx_range]
protected def stxRange (fileMap : FileMap) (stx : Syntax) : Position Γ Position :=
let pos := stx.getPos?.getD 0
let endPos := stx.getTailPos?.getD pos
(fileMap.toPosition pos, fileMap.toPosition endPos)
end Lean.FileMap
namespace Lean.PersistentArray
/--
Drop the first `n` elements of a `PersistentArray`, returning the results as a `List`.
We can't remove the `[Inhabited Ξ±]` hypotheses here until
`PersistentArray`'s `GetElem` instance also does.
-/
protected def drop [Inhabited Ξ±] (t : PersistentArray Ξ±) (n : Nat) : List Ξ± :=
List.range (t.size - n) |>.map fun i => t.get! (n + i)
end Lean.PersistentArray
namespace Pantograph.Frontend
@[export pantograph_frontend_stx_byte_range]
def stxByteRange (stx : Syntax) : String.Pos Γ String.Pos :=
let pos := stx.getPos?.getD 0
let endPos := stx.getTailPos?.getD 0
(pos, endPos)
structure Context where
cancelTk? : Option IO.CancelToken := .none
/-- This `FrontendM` comes with more options. -/
abbrev FrontendM := ReaderT Context Elab.Frontend.FrontendM
structure CompilationStep where
scope : Elab.Command.Scope
fileName : String
fileMap : FileMap
src : Substring
stx : Syntax
before : Environment
after : Environment
msgs : List Message
trees : List Elab.InfoTree
@[export pantograph_frontend_compilation_step_defined_constants_m]
protected def CompilationStep.newConstants (step : CompilationStep) : IO NameSet := do
step.after.constants.mapβ.foldlM (init := .empty) Ξ» acc name _ => do
if step.before.contains name then
return acc
let coreM : CoreM Bool := Option.isSome <$> findDeclarationRanges? name
let hasRange β coreM.run'
{ fileName := step.fileName, fileMap := step.fileMap }
{ env := step.after }
|>.toBaseIO
match hasRange with
| .ok true => return acc.insert name
| .ok false => return acc
| .error e => throw $ IO.userError (β e.toMessageData.toString)
/-- Like `Elab.Frontend.runCommandElabM`, but taking `cancelTk?` into account. -/
@[inline] def runCommandElabM (x : Elab.Command.CommandElabM Ξ±) : FrontendM Ξ± := do
let config β read
let ctx β readThe Elab.Frontend.Context
let s β get
let cmdCtx : Elab.Command.Context := {
cmdPos := s.cmdPos
fileName := ctx.inputCtx.fileName
fileMap := ctx.inputCtx.fileMap
snap? := none
cancelTk? := config.cancelTk?
}
match (β liftM <| EIO.toIO' <| (x cmdCtx).run s.commandState) with
| Except.error e => throw <| IO.Error.userError s!"unexpected internal error: {β e.toMessageData.toString}"
| Except.ok (a, sNew) => Elab.Frontend.setCommandState sNew; return a
def elabCommandAtFrontend (stx : Syntax) : FrontendM Unit := do
runCommandElabM do
let initMsgs β modifyGet Ξ» st =>
(st.messages, { st with messages := {} })
Elab.Command.elabCommandTopLevel stx
modify Ξ» state => { state with
messages := initMsgs ++ state.messages }
open Elab.Frontend in
def processCommand : FrontendM Bool := do
updateCmdPos
let cmdState β getCommandState
let ictx β getInputContext
let pstate β getParserState
let scope := cmdState.scopes.head!
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
match profileit "parsing" scope.opts fun _ => Parser.parseCommand ictx pmctx pstate cmdState.messages with
| (cmd, ps, messages) =>
modify fun s => { s with commands := s.commands.push cmd }
setParserState ps
setMessages messages
elabCommandAtFrontend cmd
pure (Parser.isTerminalCommand cmd)
/--
Process one command, returning a `CompilationStep` and
`done : Bool`, indicating whether this was the last command.
-/
@[export pantograph_frontend_process_one_command_m]
def processOneCommand: FrontendM (CompilationStep Γ Bool) := do
let s := (β get).commandState
let before := s.env
let done β processCommand
let stx := (β get).commands.back!
let src := (β readThe Elab.Frontend.Context).inputCtx.substring
(β get).cmdPos
(β get).parserState.pos
let s' := (β get).commandState
let after := s'.env
let msgs := s'.messages.toList.drop s.messages.toList.length
let trees := s'.infoState.trees.drop s.infoState.trees.size
let { fileName, fileMap, .. } := (β readThe Elab.Frontend.Context).inputCtx
return ({ scope := s.scopes.head!, fileName, fileMap, src, stx, before, after, msgs, trees }, done)
/-- Executes a `FrontendM`-based monad until completion -/
partial def executeFrontend { m } [Monad m] [MonadLiftT FrontendM m]
(f : CompilationStep β m Unit) : m Unit := do
let (cmd, done) β processOneCommand
if done then
if cmd.src.isEmpty then
return ()
else
f cmd
else
f cmd
executeFrontend f
def mapCompilationSteps { m Ξ± }
[Monad m] [MonadLiftT FrontendM m] [MonadLiftT (ST IO.RealWorld) m]
(f : CompilationStep β m Ξ±) : m (List Ξ±) := do
let f' (step : CompilationStep) : StateRefT' IO.RealWorld (List Ξ±) m Unit := do
let a β f step
modify (a :: Β·)
let (_, li) β executeFrontend f' |>.run []
return li.reverse
@[export pantograph_frontend_find_source_path_m]
def findSourcePath (module : Name) : IO System.FilePath := do
let olean β findOLean module
return System.FilePath.mk (olean.toString.replace ".lake/build/lib/" "") |>.withExtension "lean"
def defaultFileName := "<anonymous>"
/--
Use with
```lean
let m: FrontendM Ξ± := ...
let (context, state) β createContextStateFromFile ...
m.run context |>.run' state
```
-/
@[export pantograph_frontend_create_context_state_from_file_m]
def createContextStateFromFile
(file : String) -- Content of the file
(fileName : String := defaultFileName)
(env? : Option Lean.Environment := .none) -- If set to true, assume there's no header.
(opts : Options := {})
: IO (Elab.Frontend.Context Γ Elab.Frontend.State) := unsafe do
--let file β IO.FS.readFile (β findSourcePath module)
let inputCtx := Parser.mkInputContext file fileName
let (header, parserState, messages) β Parser.parseHeader inputCtx
let (env, parserState, messages) β match env? with
| .some env => pure (env, {}, .empty)
| .none =>
-- Only process the header if we don't have an environment.
let (env, messages) β Elab.processHeader header opts messages inputCtx
pure (env, parserState, messages)
let commandState := Elab.Command.mkState env messages opts
let context: Elab.Frontend.Context := { inputCtx }
let state: Elab.Frontend.State := {
commandState := { commandState with infoState.enabled := true },
parserState,
cmdPos := parserState.pos
}
return (context, state)
/-- Returns the command state at the end of execution -/
def collectEndState : FrontendM Elab.Command.State := do
executeFrontend Ξ» _ => pure ()
let state β get
return state.commandState
|