2023-05-21 17:41:39 -07:00
|
|
|
|
import Lean
|
|
|
|
|
|
2023-10-15 17:15:23 -07:00
|
|
|
|
import Pantograph.Symbol
|
2023-05-27 23:10:39 -07:00
|
|
|
|
import Pantograph.Serial
|
2023-10-15 17:15:23 -07:00
|
|
|
|
import Pantograph.Protocol
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-05-24 00:54:48 -07:00
|
|
|
|
def Lean.MessageLog.getErrorMessages (log : MessageLog) : MessageLog :=
|
2023-08-14 17:07:53 -07:00
|
|
|
|
{
|
|
|
|
|
msgs := log.msgs.filter fun m => match m.severity with | MessageSeverity.error => true | _ => false
|
|
|
|
|
}
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
|
|
|
|
|
2023-05-24 00:54:48 -07:00
|
|
|
|
namespace Pantograph
|
|
|
|
|
open Lean
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-08-27 19:53:09 -07:00
|
|
|
|
structure GoalState where
|
2023-05-24 00:54:48 -07:00
|
|
|
|
savedState : Elab.Tactic.SavedState
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-10-15 17:15:23 -07:00
|
|
|
|
-- The root hole which is the search target
|
|
|
|
|
root: MVarId
|
|
|
|
|
-- New metavariables acquired in this state
|
|
|
|
|
newMVars: SSet MVarId
|
|
|
|
|
|
2023-05-24 00:54:48 -07:00
|
|
|
|
abbrev M := Elab.TermElabM
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-10-15 17:15:23 -07:00
|
|
|
|
protected def GoalState.create (expr: Expr): M GoalState := do
|
|
|
|
|
-- May be necessary to immediately synthesise all metavariables if we need to leave the elaboration context.
|
2023-10-15 12:31:22 -07:00
|
|
|
|
-- See https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Unknown.20universe.20metavariable/near/360130070
|
2023-10-15 17:15:23 -07:00
|
|
|
|
|
2023-10-15 12:31:22 -07:00
|
|
|
|
--Elab.Term.synthesizeSyntheticMVarsNoPostponing
|
2023-10-15 17:15:23 -07:00
|
|
|
|
--let expr ← instantiateMVars expr
|
2023-10-15 12:31:22 -07:00
|
|
|
|
let goal := (← Meta.mkFreshExprMVar expr (kind := MetavarKind.synthetic) (userName := .anonymous))
|
2023-05-24 00:54:48 -07:00
|
|
|
|
let savedStateMonad: Elab.Tactic.TacticM Elab.Tactic.SavedState := MonadBacktrack.saveState
|
2023-10-25 16:03:45 -07:00
|
|
|
|
let root := goal.mvarId!
|
|
|
|
|
let savedState ← savedStateMonad { elaborator := .anonymous } |>.run' { goals := [root]}
|
2023-05-23 05:12:46 -07:00
|
|
|
|
return {
|
2023-10-15 12:31:22 -07:00
|
|
|
|
savedState,
|
2023-10-25 16:03:45 -07:00
|
|
|
|
root,
|
|
|
|
|
newMVars := SSet.insert .empty root,
|
2023-05-21 17:41:39 -07:00
|
|
|
|
}
|
2023-10-15 17:15:23 -07:00
|
|
|
|
protected def GoalState.goals (goalState: GoalState): List MVarId := goalState.savedState.tactic.goals
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-10-25 16:03:45 -07:00
|
|
|
|
private def GoalState.mctx (state: GoalState): MetavarContext :=
|
|
|
|
|
state.savedState.term.meta.meta.mctx
|
|
|
|
|
private def GoalState.mvars (state: GoalState): SSet MVarId :=
|
|
|
|
|
state.mctx.decls.foldl (init := .empty) fun acc k _ => acc.insert k
|
|
|
|
|
|
2023-10-15 17:15:23 -07:00
|
|
|
|
def executeTactic (state: Elab.Tactic.SavedState) (goal: MVarId) (tactic: Syntax) :
|
2023-05-24 00:54:48 -07:00
|
|
|
|
M (Except (Array String) (Elab.Tactic.SavedState × List MVarId)):= do
|
|
|
|
|
let tacticM (stx: Syntax): Elab.Tactic.TacticM (Except (Array String) (Elab.Tactic.SavedState × List MVarId)) := do
|
2023-05-23 05:12:46 -07:00
|
|
|
|
state.restore
|
2023-05-24 00:54:48 -07:00
|
|
|
|
Elab.Tactic.setGoals [goal]
|
2023-05-21 17:41:39 -07:00
|
|
|
|
try
|
2023-05-24 00:54:48 -07:00
|
|
|
|
Elab.Tactic.evalTactic stx
|
|
|
|
|
if (← getThe Core.State).messages.hasErrors then
|
|
|
|
|
let messages := (← getThe Core.State).messages.getErrorMessages |>.toList.toArray
|
|
|
|
|
let errors ← (messages.map Message.data).mapM fun md => md.toString
|
2023-05-23 05:12:46 -07:00
|
|
|
|
return .error errors
|
|
|
|
|
else
|
2023-10-15 12:31:22 -07:00
|
|
|
|
let unsolved ← Elab.Tactic.getUnsolvedGoals
|
2023-10-15 17:15:23 -07:00
|
|
|
|
-- The order of evaluation is important here, since `getUnsolvedGoals` prunes the goals set
|
2023-10-15 12:31:22 -07:00
|
|
|
|
return .ok (← MonadBacktrack.saveState, unsolved)
|
2023-05-23 05:12:46 -07:00
|
|
|
|
catch exception =>
|
|
|
|
|
return .error #[← exception.toMessageData.toString]
|
2023-10-15 17:15:23 -07:00
|
|
|
|
tacticM tactic { elaborator := .anonymous } |>.run' state.tactic
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
|
|
|
|
/-- Response for executing a tactic -/
|
|
|
|
|
inductive TacticResult where
|
|
|
|
|
-- Goes to next state
|
2023-10-15 17:15:23 -07:00
|
|
|
|
| success (state: GoalState) (goals: Array Protocol.Goal)
|
|
|
|
|
-- Tactic failed with messages
|
2023-05-21 17:41:39 -07:00
|
|
|
|
| failure (messages: Array String)
|
2023-10-15 17:15:23 -07:00
|
|
|
|
-- Could not parse tactic
|
|
|
|
|
| parseError (message: String)
|
|
|
|
|
-- The goal index is out of bounds
|
|
|
|
|
| indexError (goalId: Nat)
|
2023-08-27 19:53:09 -07:00
|
|
|
|
|
2023-05-21 17:41:39 -07:00
|
|
|
|
/-- Execute tactic on given state -/
|
2023-10-15 17:15:23 -07:00
|
|
|
|
protected def GoalState.execute (state: GoalState) (goalId: Nat) (tactic: String):
|
|
|
|
|
Protocol.OptionsT M TacticResult := do
|
|
|
|
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
|
|
|
|
| .some goal => pure $ goal
|
|
|
|
|
| .none => return .indexError goalId
|
|
|
|
|
let tactic ← match Parser.runParserCategory
|
|
|
|
|
(env := ← MonadEnv.getEnv)
|
|
|
|
|
(catName := `tactic)
|
|
|
|
|
(input := tactic)
|
|
|
|
|
(fileName := "<stdin>") with
|
|
|
|
|
| .ok stx => pure $ stx
|
|
|
|
|
| .error error => return .parseError error
|
2023-08-14 21:43:40 -07:00
|
|
|
|
let options ← read
|
2023-10-15 17:15:23 -07:00
|
|
|
|
match (← executeTactic (state := state.savedState) (goal := goal) (tactic := tactic)) with
|
2023-08-27 19:53:09 -07:00
|
|
|
|
| .error errors =>
|
|
|
|
|
return .failure errors
|
2023-10-15 17:15:23 -07:00
|
|
|
|
| .ok (nextSavedState, nextGoals) =>
|
|
|
|
|
assert! nextSavedState.tactic.goals.length == nextGoals.length
|
|
|
|
|
-- Assert that the definition of metavariables are the same
|
|
|
|
|
let nextMCtx := nextSavedState.term.meta.meta.mctx
|
|
|
|
|
let prevMCtx := state.savedState.term.meta.meta.mctx
|
|
|
|
|
-- Generate a list of mvarIds that exist in the parent state; Also test the
|
|
|
|
|
-- assertion that the types have not changed on any mvars.
|
2023-10-25 16:03:45 -07:00
|
|
|
|
let newMVars ← nextMCtx.decls.foldlM (fun acc mvarId mvarDecl => do
|
2023-10-15 17:15:23 -07:00
|
|
|
|
if let .some prevMVarDecl := prevMCtx.decls.find? mvarId then
|
|
|
|
|
assert! prevMVarDecl.type == mvarDecl.type
|
|
|
|
|
return acc
|
|
|
|
|
else
|
2023-10-25 16:03:45 -07:00
|
|
|
|
return acc.insert mvarId
|
|
|
|
|
) SSet.empty
|
2023-10-15 17:15:23 -07:00
|
|
|
|
let nextState: GoalState := {
|
|
|
|
|
savedState := nextSavedState
|
|
|
|
|
root := state.root,
|
|
|
|
|
newMVars,
|
|
|
|
|
}
|
|
|
|
|
nextSavedState.term.restore
|
|
|
|
|
let parentDecl? := (← MonadMCtx.getMCtx).findDecl? goal
|
|
|
|
|
let goals ← nextGoals.mapM fun nextGoal => do
|
|
|
|
|
match (← MonadMCtx.getMCtx).findDecl? nextGoal with
|
|
|
|
|
| .some mvarDecl =>
|
|
|
|
|
let serializedGoal ← serialize_goal options mvarDecl (parentDecl? := parentDecl?)
|
|
|
|
|
return serializedGoal
|
|
|
|
|
| .none => throwError s!"Parent mvar id does not exist {nextGoal.name}"
|
|
|
|
|
return .success nextState goals.toArray
|
|
|
|
|
|
2023-10-25 16:03:45 -07:00
|
|
|
|
/-- After finishing one branch of a proof (`graftee`), pick up from the point where the proof was left off (`target`) -/
|
|
|
|
|
protected def GoalState.continue (target: GoalState) (graftee: GoalState): Except String GoalState :=
|
|
|
|
|
if target.root != graftee.root then
|
|
|
|
|
.error s!"Roots of two continued goal states do not match: {target.root.name} != {graftee.root.name}"
|
|
|
|
|
-- Ensure goals are not dangling
|
|
|
|
|
else if ¬ (target.goals.all (λ goal => graftee.mvars.contains goal)) then
|
|
|
|
|
.error s!"Some goals in target are not present in the graftee"
|
|
|
|
|
else
|
|
|
|
|
-- Set goals to the goals that have not been assigned yet, similar to the `focus` tactic.
|
|
|
|
|
let unassigned := target.goals.filter (λ goal =>
|
|
|
|
|
let mctx := graftee.mctx
|
|
|
|
|
¬(mctx.eAssignment.contains goal || mctx.dAssignment.contains goal))
|
|
|
|
|
.ok {
|
|
|
|
|
savedState := {
|
|
|
|
|
term := graftee.savedState.term,
|
|
|
|
|
tactic := { goals := unassigned },
|
|
|
|
|
},
|
|
|
|
|
root := target.root,
|
|
|
|
|
newMVars := graftee.newMVars,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected def GoalState.rootExpr (goalState: GoalState): Option Expr :=
|
|
|
|
|
goalState.mctx.eAssignment.find? goalState.root |>.filter (λ e => ¬ e.hasMVar)
|
|
|
|
|
|
2023-10-15 17:15:23 -07:00
|
|
|
|
-- Diagnostics functions
|
|
|
|
|
|
|
|
|
|
/-- Print the metavariables in a readable format -/
|
2023-10-25 16:03:45 -07:00
|
|
|
|
protected def GoalState.print (goalState: GoalState) (options: Protocol.GoalPrint := {}): M Unit := do
|
2023-10-15 17:15:23 -07:00
|
|
|
|
let savedState := goalState.savedState
|
|
|
|
|
savedState.term.restore
|
|
|
|
|
let goals := savedState.tactic.goals
|
|
|
|
|
let mctx ← getMCtx
|
2023-10-25 16:03:45 -07:00
|
|
|
|
let root := goalState.root
|
|
|
|
|
-- Print the root
|
|
|
|
|
match mctx.decls.find? root with
|
|
|
|
|
| .some decl => printMVar ">" root decl
|
|
|
|
|
| .none => IO.println s!">{root.name}: ??"
|
2023-10-15 17:15:23 -07:00
|
|
|
|
goals.forM (fun mvarId => do
|
2023-10-25 16:03:45 -07:00
|
|
|
|
if mvarId != root then
|
|
|
|
|
match mctx.decls.find? mvarId with
|
|
|
|
|
| .some decl => printMVar "⊢" mvarId decl
|
|
|
|
|
| .none => IO.println s!"⊢{mvarId.name}: ??"
|
2023-10-15 17:15:23 -07:00
|
|
|
|
)
|
|
|
|
|
let goals := goals.toSSet
|
|
|
|
|
mctx.decls.forM (fun mvarId decl => do
|
2023-10-25 16:03:45 -07:00
|
|
|
|
if goals.contains mvarId || mvarId == root then
|
2023-10-15 17:15:23 -07:00
|
|
|
|
pure ()
|
2023-10-25 16:03:45 -07:00
|
|
|
|
-- Always print the root goal
|
2023-10-15 17:15:23 -07:00
|
|
|
|
else if mvarId == goalState.root then
|
|
|
|
|
printMVar (pref := ">") mvarId decl
|
2023-10-25 16:03:45 -07:00
|
|
|
|
-- Print the remainig ones that users don't see in Lean
|
2023-10-15 17:15:23 -07:00
|
|
|
|
else if options.printNonVisible then
|
2023-10-25 16:03:45 -07:00
|
|
|
|
let pref := if goalState.newMVars.contains mvarId then "~" else " "
|
|
|
|
|
printMVar pref mvarId decl
|
2023-08-27 19:53:09 -07:00
|
|
|
|
else
|
2023-10-25 16:03:45 -07:00
|
|
|
|
pure ()
|
|
|
|
|
--IO.println s!" {mvarId.name}{userNameToString decl.userName}"
|
2023-10-15 17:15:23 -07:00
|
|
|
|
)
|
|
|
|
|
where
|
|
|
|
|
printMVar (pref: String) (mvarId: MVarId) (decl: MetavarDecl): Elab.TermElabM Unit := do
|
|
|
|
|
if options.printContext then
|
|
|
|
|
decl.lctx.fvarIdToDecl.forM printFVar
|
2023-10-25 16:03:45 -07:00
|
|
|
|
let type_sexp ← serialize_expression_ast (← instantiateMVars decl.type) (sanitize := false)
|
|
|
|
|
IO.println s!"{pref}{mvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type} {type_sexp}"
|
2023-10-15 17:15:23 -07:00
|
|
|
|
if options.printValue then
|
|
|
|
|
if let Option.some value := (← getMCtx).eAssignment.find? mvarId then
|
|
|
|
|
IO.println s!" = {← Meta.ppExpr value}"
|
|
|
|
|
printFVar (fvarId: FVarId) (decl: LocalDecl): Elab.TermElabM Unit := do
|
|
|
|
|
IO.println s!" | {fvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type}"
|
|
|
|
|
userNameToString : Name → String
|
|
|
|
|
| .anonymous => ""
|
|
|
|
|
| other => s!"[{other}]"
|
2023-05-21 17:41:39 -07:00
|
|
|
|
|
2023-05-24 00:54:48 -07:00
|
|
|
|
end Pantograph
|