| import Lean.Parser |
| import Lean.Elab.Frontend |
|
|
| open Lean |
|
|
| namespace Lean.FileMap |
|
|
| / |
| @[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 |
|
|
| / |
| 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) |
|
|
| / |
| @[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) |
|
|
| / |
| 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) |
| (fileName : String := defaultFileName) |
| (env? : Option Lean.Environment := .none) |
| (opts : Options := {}) |
| : IO (Elab.Frontend.Context Γ Elab.Frontend.State) := unsafe do |
| |
| 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 => |
| |
| 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) |
|
|
| / |
| def collectEndState : FrontendM Elab.Command.State := do |
| executeFrontend Ξ» _ => pure () |
| let state β get |
| return state.commandState |
|
|