mikeljl's picture
use pypantograph for checking submitted statements
0115dcf
import Pantograph.Goal
import Pantograph.Environment
import Lean.Environment
import Lean.Replay
import Lean.Util.ShareCommon
import Std.Data.HashMap
open Lean
/-!
Input/Output functions borrowed from REPL
# Pickling and unpickling objects
By abusing `saveModuleData` and `readModuleData` we can pickle and unpickle objects to disk.
-/
namespace Pantograph
/--
Save an object to disk.
If you need to write multiple objects from within a single declaration,
you will need to provide a unique `key` for each.
-/
def pickle {α : Type} (path : System.FilePath) (x : α) (key : Name := by exact decl_name%) : IO Unit :=
saveModuleData path key (unsafe unsafeCast x)
/--
Load an object from disk.
Note: The returned `CompactedRegion` can be used to free the memory behind the value
of type `α`, using `CompactedRegion.free` (which is only safe once all references to the `α` are
released). Ignoring the `CompactedRegion` results in the data being leaked.
Use `withUnpickle` to call `CompactedRegion.free` automatically.
This function is unsafe because the data being loaded may not actually have type `α`, and this
may cause crashes or other bad behavior.
-/
unsafe def unpickle (α : Type) (path : System.FilePath) : IO (α × CompactedRegion) := do
let (x, region) ← readModuleData path
pure (unsafeCast x, region)
/-- Load an object from disk and run some continuation on it, freeing memory afterwards. -/
unsafe def withUnpickle [Monad m] [MonadLiftT IO m] {α β : Type}
(path : System.FilePath) (f : α → m β) : m β := do
let (x, region) ← unpickle α path
let r ← f x
region.free
pure r
/--
Pickle an `Environment` to disk relative to a background.
We only store:
* the list of imports
* the new constants from `Environment.constants`
and when unpickling, we build a fresh `Environment` from the imports,
and then add the new constants.
-/
@[export pantograph_env_pickle_m]
def environmentPickle (env : Environment) (path : System.FilePath) (background? : Option Environment := .none)
: IO Unit :=
let distilled := distilEnvironment env background?
pickle path (ShareCommon.shareCommon distilled)
def resurrectEnvironment
(distilled : DistilledEnvironment)
(background? : Option Environment := .none)
: IO Environment := do
let (imports, constArray) := distilled
let env ← match background? with
| .none => importModules imports {} 0 (loadExts := true)
| .some env =>
assert! env.imports == imports
pure env
env.replay (Std.HashMap.ofList constArray.toList)
/--
Unpickle an `Environment` from disk.
We construct a fresh `Environment` with the relevant imports,
and then replace the new constants.
The `CompactedRegion` must be free'd after using the environment. Otherwise
there could be memory leaks.
-/
@[export pantograph_env_unpickle_m]
def environmentUnpickle (path : System.FilePath) (background? : Option Environment := .none)
: IO (Environment × CompactedRegion) := unsafe do
let (distilled, region) ← unpickle (Array Import × ConstArray) path
return (← resurrectEnvironment distilled background?, region)
/-- `CoreM`'s state, with information irrelevant to pickling masked out -/
structure CompactCoreState where
-- env : Environment
nextMacroScope : MacroScope := firstFrontendMacroScope + 1
ngen : NameGenerator := {}
auxDeclNGen : DeclNameGenerator := {}
-- traceState : TraceState := {}
-- cache : Cache := {}
-- messages : MessageLog := {}
-- infoState : Elab.InfoState := {}
structure CompactGoalState where
env : DistilledEnvironment
core : CompactCoreState
«meta» : Meta.State
«elab» : Elab.Term.State
tactic : Elab.Tactic.State
root : MVarId
parentMVars : List MVarId
fragments : FragmentMap
/-- Pickles a goal state by taking its diff relative to a background
environment. This function eliminates all `MessageData` from synthetic
metavariables, because these `MessageData` objects frequently carry closures,
which cannot be pickled. If there is no synthetic metavariable, this would not
cause a difference. -/
@[export pantograph_goal_state_pickle_m]
def goalStatePickle (goalState : GoalState) (path : System.FilePath) (background? : Option Environment := .none)
: IO Unit :=
let {
savedState := {
term := {
«meta» := {
core := {
env, nextMacroScope, ngen, auxDeclNGen, ..
},
«meta»,
}
«elab» := «elab»@{
syntheticMVars, ..
},
},
tactic
}
root,
parentMVars,
fragments,
} := goalState
-- Delete `MessageData`s
let syntheticMVars : MVarIdMap _ := syntheticMVars.foldl (init := .empty) λ acc key val =>
let kind := match val.kind with
| .typeClass _ => .typeClass .none
| .coe header? expectedType e f? _ => .coe header? expectedType e f? .none
| k => k
acc.insert key { val with kind }
let compacted : CompactGoalState := {
env := distilEnvironment env background?,
core := { nextMacroScope, ngen, auxDeclNGen },
«meta»,
«elab» := { «elab» with syntheticMVars },
tactic,
root,
parentMVars,
fragments,
}
pickle path (ShareCommon.shareCommon compacted)
/--
Loads a goal state from disk. The background is the environment which the goal
state was cached relative to.
The `CompactedRegion` must be free'd after using the goal state. Otherwise there
will be memory leaks.
-/
@[export pantograph_goal_state_unpickle_m]
def goalStateUnpickle (path : System.FilePath) (background? : Option Environment := .none)
: IO (GoalState × CompactedRegion) := unsafe do
let ({
env,
core,
«meta»,
«elab»,
tactic,
root,
parentMVars,
fragments,
}, region) ← Pantograph.unpickle CompactGoalState path
let env ← resurrectEnvironment env background?
let goalState := {
savedState := {
term := {
«meta» := {
core := {
core with
passedHeartbeats := 0,
env,
},
«meta»,
},
«elab»,
},
tactic,
},
root,
parentMVars,
fragments,
}
return (goalState, region)
end Pantograph