PhysLean/HepLean/Meta/TransverseTactics.lean
2024-09-04 08:20:10 -04:00

119 lines
5 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) 2024 Joseph Tooby-Smith. All rights reserved.
Released under Apache 2.0 license.
Authors: Joseph Tooby-Smith
-/
import Batteries.Lean.HashSet
import Lean
import HepLean.Meta.AllFilePaths
/-!
This file enables us to transverse tactics and test for conditions.
## References
The content of this file is based on the following sources (released under the Apache 2.0 license):
- https://github.com/dwrensha/tryAtEachStep/blob/main/tryAtEachStep.lean
- https://github.com/lean-dojo/LeanDojo/blob/main/src/lean_dojo/data_extraction/ExtractData.lean
Modifications have been made to the original content of these files here.
See also:
- https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Memory.20increase.20in.20loops.2E
-/
open Lean Elab System
namespace transverseTactics
partial def processCommands : Frontend.FrontendM (List (Environment × InfoState)) := do
/- Very roughly, `Frontend.FrontendM (List (Environment × InfoState))` is equivalent
to `Frontend.Context → Frontend.state → List (Environment × InfoState)`.
The `←get` here is returning the inputted value of `Frontend.state`,
from which we get the enviroment.
-/
let env := (←get).commandState.env
/- Processes a single command, adding it to `env`. This is done using
`modify fun s => { s with commands := s.commands.push cmd }` as part of
`Frontend.processCommand`. -/
let done ← Frontend.processCommand
/- Gets the updated `Frontend.state`. -/
let st := ← get
/- Gets the infostate associated with the single command. -/
let infoState := st.commandState.infoState
set {st with commandState := {st.commandState with infoState := {}}}
if done
then return [(env, infoState)]
else
/- For each command, we return the enviroment before the command is processed,
and the `infoState` associated with that command. -/
return (env, infoState) :: (←processCommands)
def visitInfo (file : FilePath) (env : Environment)
(visitTacticInfo : FilePath → ContextInfo → TacticInfo → MetaM Unit) (ci : ContextInfo)
(info : Info) (acc : List (IO Unit)) : List (IO Unit) :=
match info with
| .ofTacticInfo ti =>
(ci.runMetaM default
(do setEnv env
try visitTacticInfo file ci ti
catch e =>
println! "caught: {←e.toMessageData.toString}")) :: acc
| _ => acc
def traverseForest (file : FilePath)
(visitTacticInfo : FilePath → ContextInfo → TacticInfo → MetaM Unit)
(steps : List (Environment × InfoState)) : List (IO Unit) :=
let t := steps.map fun (env, infoState) ↦
(infoState.trees.toList.map fun t ↦
(Lean.Elab.InfoTree.foldInfo (visitInfo file env visitTacticInfo) [] t).reverse)
t.join.join
end transverseTactics
open transverseTactics in
unsafe def transverseTactics (file : FilePath)
(visitTacticInfo : FilePath → ContextInfo → TacticInfo → MetaM Unit) : IO Unit := do
searchPathRef.set compile_time_search_path%
/- This is equivalent to `(IO.FS.readFile file).bind (fun fileContent => do ...)`. -/
let fileContent ← IO.FS.readFile file
enableInitializersExecution
/- Get `Parser.InputContext` from file. -/
let inputCtx := Parser.mkInputContext fileContent file.toString -- The input content of the file
/- We parse the header. Recall that the parser is takes a string and
outputs a Lean syntax object. -/
let (header, parserState, messages) ← Parser.parseHeader inputCtx
/- Recall that the elborator turns a Lean syntax object into a Lean Expr object.
In the below, we process the header, creating an enviroment with the relevent imports.
This can be thought of as creating an import only file. -/
if messages.hasErrors then
for msg in messages.toList do
if msg.severity == .error then
println! "ERROR: {← msg.toString}"
throw $ IO.userError "Errors during import; aborting"
for msg in messages.toList do
println! "{← msg.toString}"
let (env, messages) ← processHeader header {} messages inputCtx
if messages.hasErrors then
for msg in messages.toList do
if msg.severity == .error then
println! "ERROR: {← msg.toString}"
throw $ IO.userError "Errors during import; aborting"
/- As part of the enviroment header is the module name. This is not included
in our current `env`. So we include it now. -/
let env1 := env.setMainModule (← moduleNameOfFileName file none)
/- From the enviroment, we create a state of the `Command` monad. -/
let commandState := {Command.mkState env1 messages {} with infoState.enabled := true}
/- We create a state of the `Frontend` monad-/
/- Runs the `processCommands` function on the context defined by `inputCtx`, and the
state defined by `frontendState`. -/
let (steps, _frontendState) ← (processCommands.run { inputCtx := inputCtx }).run
{ commandState := commandState, parserState := parserState, cmdPos := parserState.pos }
/- Note that for us, each `infoState.trees` is actually of length 1. -/
for t in traverseForest file visitTacticInfo steps do
try t
catch e =>
println! "caught top level: {e}"
pure ()