Compare commits
No commits in common. "io/async" and "dev" have entirely different histories.
|
@ -1,54 +0,0 @@
|
||||||
name: deploy-docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
# This job installs dependencies, builds the book, and pushes it to `gh-pages`
|
|
||||||
jobs:
|
|
||||||
deploy-book:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: 3.11
|
|
||||||
|
|
||||||
- name: Install elan
|
|
||||||
run: |
|
|
||||||
set -o pipefail
|
|
||||||
curl -sSfL https://github.com/leanprover/elan/releases/download/v3.1.1/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz
|
|
||||||
./elan-init -y --default-toolchain none
|
|
||||||
echo "$HOME/.elan/bin" >> "${GITHUB_PATH}"
|
|
||||||
|
|
||||||
- name: Install Lean
|
|
||||||
run: |
|
|
||||||
elan toolchain install $(<src/lean-toolchain)
|
|
||||||
|
|
||||||
- name: Install poetry
|
|
||||||
run: |
|
|
||||||
pip install poetry
|
|
||||||
poetry install --only doc
|
|
||||||
|
|
||||||
- name: Build documentations
|
|
||||||
run: |
|
|
||||||
poetry run jupyter-book build docs
|
|
||||||
|
|
||||||
# Upload the book's HTML as an artifact
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v2
|
|
||||||
with:
|
|
||||||
path: "docs/_build/html"
|
|
||||||
|
|
||||||
# Deploy the book's HTML to GitHub Pages
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v2
|
|
|
@ -1,11 +1,4 @@
|
||||||
.*
|
.*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!.github
|
*.[io]lean
|
||||||
|
/result
|
||||||
# Python
|
|
||||||
*.py[cod]
|
|
||||||
*.egg-info
|
|
||||||
|
|
||||||
# Output
|
|
||||||
/dist
|
|
||||||
/venv
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "src"]
|
|
||||||
path = src
|
|
||||||
url = https://github.com/lenianiva/Pantograph.git
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import Lean.Data.Json
|
||||||
|
import Lean.Environment
|
||||||
|
|
||||||
|
import Pantograph
|
||||||
|
import Repl
|
||||||
|
|
||||||
|
-- Main IO functions
|
||||||
|
open Pantograph.Repl
|
||||||
|
open Pantograph.Protocol
|
||||||
|
|
||||||
|
/-- Parse a command either in `{ "cmd": ..., "payload": ... }` form or `cmd { ... }` form. -/
|
||||||
|
def parseCommand (s: String): Except String Command := do
|
||||||
|
let s := s.trim
|
||||||
|
match s.get? 0 with
|
||||||
|
| .some '{' => -- Parse in Json mode
|
||||||
|
Lean.fromJson? (← Lean.Json.parse s)
|
||||||
|
| .some _ => -- Parse in line mode
|
||||||
|
let offset := s.posOf ' ' |> s.offsetOfPos
|
||||||
|
if offset = s.length then
|
||||||
|
return { cmd := s.take offset, payload := Lean.Json.null }
|
||||||
|
else
|
||||||
|
let payload ← s.drop offset |> Lean.Json.parse
|
||||||
|
return { cmd := s.take offset, payload := payload }
|
||||||
|
| .none => throw "Command is empty"
|
||||||
|
|
||||||
|
partial def loop : MainM Unit := do
|
||||||
|
let state ← get
|
||||||
|
let command ← (← IO.getStdin).getLine
|
||||||
|
if command.trim.length = 0 then return ()
|
||||||
|
match parseCommand command with
|
||||||
|
| .error error =>
|
||||||
|
let error := Lean.toJson ({ error := "command", desc := error }: InteractionError)
|
||||||
|
-- Using `Lean.Json.compress` here to prevent newline
|
||||||
|
IO.println error.compress
|
||||||
|
| .ok command =>
|
||||||
|
try
|
||||||
|
let ret ← execute command
|
||||||
|
let str := match state.options.printJsonPretty with
|
||||||
|
| true => ret.pretty
|
||||||
|
| false => ret.compress
|
||||||
|
IO.println str
|
||||||
|
catch e =>
|
||||||
|
let message ← e.toMessageData.toString
|
||||||
|
let error := Lean.toJson ({ error := "main", desc := message }: InteractionError)
|
||||||
|
IO.println error.compress
|
||||||
|
loop
|
||||||
|
|
||||||
|
|
||||||
|
unsafe def main (args: List String): IO Unit := do
|
||||||
|
-- NOTE: A more sophisticated scheme of command line argument handling is needed.
|
||||||
|
-- Separate imports and options
|
||||||
|
if args == ["--version"] then do
|
||||||
|
IO.println s!"{Pantograph.version}"
|
||||||
|
return
|
||||||
|
|
||||||
|
Pantograph.initSearch ""
|
||||||
|
|
||||||
|
let coreContext ← args.filterMap (λ s => if s.startsWith "--" then .some <| s.drop 2 else .none)
|
||||||
|
|>.toArray |> Pantograph.createCoreContext
|
||||||
|
let imports:= args.filter (λ s => ¬ (s.startsWith "--"))
|
||||||
|
let coreState ← Pantograph.createCoreState imports.toArray
|
||||||
|
let context: Context := {
|
||||||
|
imports
|
||||||
|
}
|
||||||
|
try
|
||||||
|
let coreM := loop.run context |>.run' {}
|
||||||
|
IO.println "ready."
|
||||||
|
discard <| coreM.toIO coreContext coreState
|
||||||
|
catch ex =>
|
||||||
|
let message := ex.toString
|
||||||
|
let error := Lean.toJson ({ error := "io", desc := message }: InteractionError)
|
||||||
|
IO.println error.compress
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Pantograph.Elab
|
||||||
|
import Pantograph.Environment
|
||||||
|
import Pantograph.Frontend
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Library
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Pantograph.Version
|
|
@ -0,0 +1,561 @@
|
||||||
|
/-
|
||||||
|
This file handles "Delation": The conversion of Kernel view into Search view.
|
||||||
|
-/
|
||||||
|
import Lean
|
||||||
|
import Std.Data.HashMap
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Protocol
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
-- Symbol processing functions --
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
structure ProjectionApplication where
|
||||||
|
projector: Name
|
||||||
|
numParams: Nat
|
||||||
|
inner: Expr
|
||||||
|
|
||||||
|
@[export pantograph_expr_proj_to_app]
|
||||||
|
def exprProjToApp (env: Environment) (e: Expr): ProjectionApplication :=
|
||||||
|
let (typeName, idx, inner) := match e with
|
||||||
|
| .proj typeName idx inner => (typeName, idx, inner)
|
||||||
|
| _ => panic! "Argument must be proj"
|
||||||
|
let ctor := getStructureCtor env typeName
|
||||||
|
let fieldName := getStructureFields env typeName |>.get! idx
|
||||||
|
let projector := getProjFnForField? env typeName fieldName |>.get!
|
||||||
|
{
|
||||||
|
projector,
|
||||||
|
numParams := ctor.numParams,
|
||||||
|
inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _root_.Lean.Name.isAuxLemma (n : Lean.Name) : Bool := n matches .num (.str _ "_auxLemma") _
|
||||||
|
|
||||||
|
/-- Unfold all lemmas created by `Lean.Meta.mkAuxLemma`. These end in `_auxLemma.nn` where `nn` is a number. -/
|
||||||
|
@[export pantograph_unfold_aux_lemmas]
|
||||||
|
def unfoldAuxLemmas (e : Expr) : CoreM Expr := do
|
||||||
|
Lean.Meta.deltaExpand e Lean.Name.isAuxLemma
|
||||||
|
|
||||||
|
/--
|
||||||
|
Force the instantiation of delayed metavariables even if they cannot be fully
|
||||||
|
instantiated. This is used during resumption to provide diagnostic data about
|
||||||
|
the current goal.
|
||||||
|
|
||||||
|
Since Lean 4 does not have an `Expr` constructor corresponding to delayed
|
||||||
|
metavariables, any delayed metavariables must be recursively handled by this
|
||||||
|
function to ensure that nested delayed metavariables can be properly processed.
|
||||||
|
The caveat is this recursive call will lead to infinite recursion if a loop
|
||||||
|
between metavariable assignment exists.
|
||||||
|
|
||||||
|
This function ensures any metavariable in the result is either
|
||||||
|
1. Delayed assigned with its pending mvar not assigned in any form
|
||||||
|
2. Not assigned (delay or not)
|
||||||
|
-/
|
||||||
|
partial def instantiateDelayedMVars (eOrig: Expr) : MetaM Expr := do
|
||||||
|
--let padding := String.join $ List.replicate level "│ "
|
||||||
|
--IO.println s!"{padding}Starting {toString eOrig}"
|
||||||
|
let mut result ← Meta.transform (← instantiateMVars eOrig)
|
||||||
|
(pre := fun e => e.withApp fun f args => do
|
||||||
|
let .mvar mvarId := f | return .continue
|
||||||
|
--IO.println s!"{padding}├V {e}"
|
||||||
|
let mvarDecl ← mvarId.getDecl
|
||||||
|
|
||||||
|
-- This is critical to maintaining the interdependency of metavariables.
|
||||||
|
-- Without setting `.syntheticOpaque`, Lean's metavariable elimination
|
||||||
|
-- system will not make the necessary delayed assigned mvars in case of
|
||||||
|
-- nested mvars.
|
||||||
|
mvarId.setKind .syntheticOpaque
|
||||||
|
|
||||||
|
mvarId.withContext do
|
||||||
|
let lctx ← MonadLCtx.getLCtx
|
||||||
|
if mvarDecl.lctx.any (λ decl => !lctx.contains decl.fvarId) then
|
||||||
|
let violations := mvarDecl.lctx.decls.foldl (λ acc decl? => match decl? with
|
||||||
|
| .some decl => if lctx.contains decl.fvarId then acc else acc ++ [decl.fvarId.name]
|
||||||
|
| .none => acc) []
|
||||||
|
panic! s!"In the context of {mvarId.name}, there are local context variable violations: {violations}"
|
||||||
|
|
||||||
|
if let .some assign ← getExprMVarAssignment? mvarId then
|
||||||
|
--IO.println s!"{padding}├A ?{mvarId.name}"
|
||||||
|
assert! !(← mvarId.isDelayedAssigned)
|
||||||
|
return .visit (mkAppN assign args)
|
||||||
|
else if let some { fvars, mvarIdPending } ← getDelayedMVarAssignment? mvarId then
|
||||||
|
--let substTableStr := String.intercalate ", " $ Array.zipWith fvars args (λ fvar assign => s!"{fvar.fvarId!.name} := {assign}") |>.toList
|
||||||
|
--IO.println s!"{padding}├MD ?{mvarId.name} := ?{mvarIdPending.name} [{substTableStr}]"
|
||||||
|
|
||||||
|
if args.size < fvars.size then
|
||||||
|
throwError "Not enough arguments to instantiate a delay assigned mvar. This is due to bad implementations of a tactic: {args.size} < {fvars.size}. Expr: {toString e}; Origin: {toString eOrig}"
|
||||||
|
--if !args.isEmpty then
|
||||||
|
--IO.println s!"{padding}├── Arguments Begin"
|
||||||
|
let args ← args.mapM self
|
||||||
|
--if !args.isEmpty then
|
||||||
|
--IO.println s!"{padding}├── Arguments End"
|
||||||
|
if !(← mvarIdPending.isAssignedOrDelayedAssigned) then
|
||||||
|
--IO.println s!"{padding}├T1"
|
||||||
|
let result := mkAppN f args
|
||||||
|
return .done result
|
||||||
|
|
||||||
|
let pending ← mvarIdPending.withContext do
|
||||||
|
let inner ← instantiateDelayedMVars (.mvar mvarIdPending) --(level := level + 1)
|
||||||
|
--IO.println s!"{padding}├Pre: {inner}"
|
||||||
|
pure <| (← inner.abstractM fvars).instantiateRev args
|
||||||
|
|
||||||
|
-- Tail arguments
|
||||||
|
let result := mkAppRange pending fvars.size args.size args
|
||||||
|
--IO.println s!"{padding}├MD {result}"
|
||||||
|
return .done result
|
||||||
|
else
|
||||||
|
assert! !(← mvarId.isAssigned)
|
||||||
|
assert! !(← mvarId.isDelayedAssigned)
|
||||||
|
--if !args.isEmpty then
|
||||||
|
-- IO.println s!"{padding}├── Arguments Begin"
|
||||||
|
let args ← args.mapM self
|
||||||
|
--if !args.isEmpty then
|
||||||
|
-- IO.println s!"{padding}├── Arguments End"
|
||||||
|
|
||||||
|
--IO.println s!"{padding}├M ?{mvarId.name}"
|
||||||
|
return .done (mkAppN f args))
|
||||||
|
--IO.println s!"{padding}└Result {result}"
|
||||||
|
return result
|
||||||
|
where
|
||||||
|
self e := instantiateDelayedMVars e --(level := level + 1)
|
||||||
|
|
||||||
|
/--
|
||||||
|
Convert an expression to an equiavlent form with
|
||||||
|
1. No nested delayed assigned mvars
|
||||||
|
2. No aux lemmas
|
||||||
|
3. No assigned mvars
|
||||||
|
-/
|
||||||
|
@[export pantograph_instantiate_all_m]
|
||||||
|
def instantiateAll (e: Expr): MetaM Expr := do
|
||||||
|
let e ← instantiateDelayedMVars e
|
||||||
|
let e ← unfoldAuxLemmas e
|
||||||
|
return e
|
||||||
|
|
||||||
|
structure DelayedMVarInvocation where
|
||||||
|
mvarIdPending: MVarId
|
||||||
|
args: Array (FVarId × (Option Expr))
|
||||||
|
-- Extra arguments applied to the result of this substitution
|
||||||
|
tail: Array Expr
|
||||||
|
|
||||||
|
-- The pending mvar of any delayed assigned mvar must not be assigned in any way.
|
||||||
|
@[export pantograph_to_delayed_mvar_invocation_m]
|
||||||
|
def toDelayedMVarInvocation (e: Expr): MetaM (Option DelayedMVarInvocation) := do
|
||||||
|
let .mvar mvarId := e.getAppFn | return .none
|
||||||
|
let .some decl ← getDelayedMVarAssignment? mvarId | return .none
|
||||||
|
let mvarIdPending := decl.mvarIdPending
|
||||||
|
let mvarDecl ← mvarIdPending.getDecl
|
||||||
|
-- Print the function application e. See Lean's `withOverApp`
|
||||||
|
let args := e.getAppArgs
|
||||||
|
|
||||||
|
assert! args.size ≥ decl.fvars.size
|
||||||
|
assert! !(← mvarIdPending.isAssigned)
|
||||||
|
assert! !(← mvarIdPending.isDelayedAssigned)
|
||||||
|
let fvarArgMap: Std.HashMap FVarId Expr := Std.HashMap.ofList $ (decl.fvars.map (·.fvarId!) |>.zip args).toList
|
||||||
|
let subst ← mvarDecl.lctx.foldlM (init := []) λ acc localDecl => do
|
||||||
|
let fvarId := localDecl.fvarId
|
||||||
|
let a := fvarArgMap[fvarId]?
|
||||||
|
return acc ++ [(fvarId, a)]
|
||||||
|
|
||||||
|
assert! decl.fvars.all (λ fvar => mvarDecl.lctx.findFVar? fvar |>.isSome)
|
||||||
|
|
||||||
|
return .some {
|
||||||
|
mvarIdPending,
|
||||||
|
args := subst.toArray,
|
||||||
|
tail := args.toList.drop decl.fvars.size |>.toArray,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Condensed representation
|
||||||
|
|
||||||
|
namespace Condensed
|
||||||
|
|
||||||
|
-- Mirrors Lean's LocalDecl
|
||||||
|
structure LocalDecl where
|
||||||
|
-- Default value is for testing
|
||||||
|
fvarId: FVarId := { name := .anonymous }
|
||||||
|
userName: Name
|
||||||
|
|
||||||
|
-- Normalized expression
|
||||||
|
type : Expr
|
||||||
|
value? : Option Expr := .none
|
||||||
|
|
||||||
|
structure Goal where
|
||||||
|
mvarId: MVarId := { name := .anonymous }
|
||||||
|
userName: Name := .anonymous
|
||||||
|
context: Array LocalDecl
|
||||||
|
target: Expr
|
||||||
|
|
||||||
|
@[export pantograph_goal_is_lhs]
|
||||||
|
def isLHS (g: Goal) : Bool := isLHSGoal? g.target |>.isSome
|
||||||
|
|
||||||
|
end Condensed
|
||||||
|
|
||||||
|
-- Get the list of visible (by default) free variables from a goal
|
||||||
|
@[export pantograph_visible_fvars_of_mvar]
|
||||||
|
protected def visibleFVarsOfMVar (mctx: MetavarContext) (mvarId: MVarId): Option (Array FVarId) := do
|
||||||
|
let mvarDecl ← mctx.findDecl? mvarId
|
||||||
|
let lctx := mvarDecl.lctx
|
||||||
|
return lctx.decls.foldl (init := #[]) fun r decl? => match decl? with
|
||||||
|
| some decl => if decl.isAuxDecl ∨ decl.isImplementationDetail then r else r.push decl.fvarId
|
||||||
|
| none => r
|
||||||
|
|
||||||
|
@[export pantograph_to_condensed_goal_m]
|
||||||
|
def toCondensedGoal (mvarId: MVarId): MetaM Condensed.Goal := do
|
||||||
|
let ppAuxDecls := Meta.pp.auxDecls.get (← getOptions)
|
||||||
|
let ppImplDetailHyps := Meta.pp.implementationDetailHyps.get (← getOptions)
|
||||||
|
let mvarDecl ← mvarId.getDecl
|
||||||
|
let lctx := mvarDecl.lctx
|
||||||
|
let lctx := lctx.sanitizeNames.run' { options := (← getOptions) }
|
||||||
|
Meta.withLCtx lctx mvarDecl.localInstances do
|
||||||
|
let ppVar (localDecl : LocalDecl) : MetaM Condensed.LocalDecl := do
|
||||||
|
match localDecl with
|
||||||
|
| .cdecl _ fvarId userName type _ _ =>
|
||||||
|
let type ← instantiate type
|
||||||
|
return { fvarId, userName, type }
|
||||||
|
| .ldecl _ fvarId userName type value _ _ => do
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
let type ← instantiate type
|
||||||
|
let value ← instantiate value
|
||||||
|
return { fvarId, userName, type, value? := .some value }
|
||||||
|
let vars ← lctx.foldlM (init := []) fun acc (localDecl : LocalDecl) => do
|
||||||
|
let skip := !ppAuxDecls && localDecl.isAuxDecl ||
|
||||||
|
!ppImplDetailHyps && localDecl.isImplementationDetail
|
||||||
|
if skip then
|
||||||
|
return acc
|
||||||
|
else
|
||||||
|
let var ← ppVar localDecl
|
||||||
|
return var::acc
|
||||||
|
return {
|
||||||
|
mvarId,
|
||||||
|
userName := mvarDecl.userName,
|
||||||
|
context := vars.reverse.toArray,
|
||||||
|
target := ← instantiate mvarDecl.type
|
||||||
|
}
|
||||||
|
where
|
||||||
|
instantiate := instantiateAll
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_to_condensed_m]
|
||||||
|
protected def GoalState.toCondensed (state: GoalState):
|
||||||
|
CoreM (Array Condensed.Goal):= do
|
||||||
|
let metaM := do
|
||||||
|
let goals := state.goals.toArray
|
||||||
|
goals.mapM fun goal => do
|
||||||
|
match state.mctx.findDecl? goal with
|
||||||
|
| .some _ =>
|
||||||
|
let serializedGoal ← toCondensedGoal goal
|
||||||
|
pure serializedGoal
|
||||||
|
| .none => throwError s!"Metavariable does not exist in context {goal.name}"
|
||||||
|
metaM.run' (s := state.savedState.term.meta.meta)
|
||||||
|
|
||||||
|
def typeExprToBound (expr: Expr): MetaM Protocol.BoundExpression := do
|
||||||
|
Meta.forallTelescope expr fun arr body => do
|
||||||
|
let binders ← arr.mapM fun fvar => do
|
||||||
|
return (toString (← fvar.fvarId!.getUserName), toString (← Meta.ppExpr (← fvar.fvarId!.getType)))
|
||||||
|
return { binders, target := toString (← Meta.ppExpr body) }
|
||||||
|
|
||||||
|
def serializeName (name: Name) (sanitize: Bool := true): String :=
|
||||||
|
let internal := name.isInaccessibleUserName || name.hasMacroScopes
|
||||||
|
if sanitize && internal then "_"
|
||||||
|
else toString name |> addQuotes
|
||||||
|
where
|
||||||
|
addQuotes (n: String) :=
|
||||||
|
let quote := "\""
|
||||||
|
if n.contains Lean.idBeginEscape then s!"{quote}{n}{quote}" else n
|
||||||
|
|
||||||
|
/-- serialize a sort level. Expression is optimized to be compact e.g. `(+ u 2)` -/
|
||||||
|
partial def serializeSortLevel (level: Level) : String :=
|
||||||
|
let k := level.getOffset
|
||||||
|
let u := level.getLevelOffset
|
||||||
|
let u_str := match u with
|
||||||
|
| .zero => "0"
|
||||||
|
| .succ _ => panic! "getLevelOffset should not return .succ"
|
||||||
|
| .max v w =>
|
||||||
|
let v := serializeSortLevel v
|
||||||
|
let w := serializeSortLevel w
|
||||||
|
s!"(:max {v} {w})"
|
||||||
|
| .imax v w =>
|
||||||
|
let v := serializeSortLevel v
|
||||||
|
let w := serializeSortLevel w
|
||||||
|
s!"(:imax {v} {w})"
|
||||||
|
| .param name =>
|
||||||
|
s!"{name}"
|
||||||
|
| .mvar id =>
|
||||||
|
let name := id.name
|
||||||
|
s!"(:mv {name})"
|
||||||
|
match k, u with
|
||||||
|
| 0, _ => u_str
|
||||||
|
| _, .zero => s!"{k}"
|
||||||
|
| _, _ => s!"(+ {u_str} {k})"
|
||||||
|
|
||||||
|
|
||||||
|
/--
|
||||||
|
Completely serializes an expression tree. Json not used due to compactness
|
||||||
|
|
||||||
|
A `_` symbol in the AST indicates automatic deductions not present in the original expression.
|
||||||
|
-/
|
||||||
|
partial def serializeExpressionSexp (expr: Expr) : MetaM String := do
|
||||||
|
self expr
|
||||||
|
where
|
||||||
|
delayedMVarToSexp (e: Expr): MetaM (Option String) := do
|
||||||
|
let .some invocation ← toDelayedMVarInvocation e | return .none
|
||||||
|
let callee ← self $ .mvar invocation.mvarIdPending
|
||||||
|
let sites ← invocation.args.mapM (λ (fvarId, arg) => do
|
||||||
|
let arg := match arg with
|
||||||
|
| .some arg => arg
|
||||||
|
| .none => .fvar fvarId
|
||||||
|
self arg
|
||||||
|
)
|
||||||
|
let tailArgs ← invocation.tail.mapM self
|
||||||
|
|
||||||
|
let sites := " ".intercalate sites.toList
|
||||||
|
let result := if tailArgs.isEmpty then
|
||||||
|
s!"(:subst {callee} {sites})"
|
||||||
|
else
|
||||||
|
let tailArgs := " ".intercalate tailArgs.toList
|
||||||
|
s!"((:subst {callee} {sites}) {tailArgs})"
|
||||||
|
return .some result
|
||||||
|
|
||||||
|
self (e: Expr): MetaM String := do
|
||||||
|
if let .some result ← delayedMVarToSexp e then
|
||||||
|
return result
|
||||||
|
match e with
|
||||||
|
| .bvar deBruijnIndex =>
|
||||||
|
-- This is very common so the index alone is shown. Literals are handled below.
|
||||||
|
-- The raw de Bruijn index should never appear in an unbound setting. In
|
||||||
|
-- Lean these are handled using a `#` prefix.
|
||||||
|
pure s!"{deBruijnIndex}"
|
||||||
|
| .fvar fvarId =>
|
||||||
|
let name := fvarId.name
|
||||||
|
pure s!"(:fv {name})"
|
||||||
|
| .mvar mvarId => do
|
||||||
|
let pref := if ← mvarId.isDelayedAssigned then "mvd" else "mv"
|
||||||
|
let name := mvarId.name
|
||||||
|
pure s!"(:{pref} {name})"
|
||||||
|
| .sort level =>
|
||||||
|
let level := serializeSortLevel level
|
||||||
|
pure s!"(:sort {level})"
|
||||||
|
| .const declName _ =>
|
||||||
|
let declName := serializeName declName (sanitize := false)
|
||||||
|
-- The universe level of the const expression is elided since it should be
|
||||||
|
-- inferrable from surrounding expression
|
||||||
|
pure s!"(:c {declName})"
|
||||||
|
| .app _ _ => do
|
||||||
|
let fn' ← self e.getAppFn
|
||||||
|
let args := (← e.getAppArgs.mapM self) |>.toList
|
||||||
|
let args := " ".intercalate args
|
||||||
|
pure s!"({fn'} {args})"
|
||||||
|
| .lam binderName binderType body binderInfo => do
|
||||||
|
let binderName' := binderName.eraseMacroScopes
|
||||||
|
let binderType' ← self binderType
|
||||||
|
let body' ← self body
|
||||||
|
let binderInfo' := binderInfoSexp binderInfo
|
||||||
|
pure s!"(:lambda {binderName'} {binderType'} {body'}{binderInfo'})"
|
||||||
|
| .forallE binderName binderType body binderInfo => do
|
||||||
|
let binderName' := binderName.eraseMacroScopes
|
||||||
|
let binderType' ← self binderType
|
||||||
|
let body' ← self body
|
||||||
|
let binderInfo' := binderInfoSexp binderInfo
|
||||||
|
pure s!"(:forall {binderName'} {binderType'} {body'}{binderInfo'})"
|
||||||
|
| .letE name type value body _ => do
|
||||||
|
-- Dependent boolean flag diacarded
|
||||||
|
let name' := name.eraseMacroScopes
|
||||||
|
let type' ← self type
|
||||||
|
let value' ← self value
|
||||||
|
let body' ← self body
|
||||||
|
pure s!"(:let {name'} {type'} {value'} {body'})"
|
||||||
|
| .lit v =>
|
||||||
|
-- To not burden the downstream parser who needs to handle this, the literal
|
||||||
|
-- is wrapped in a :lit sexp.
|
||||||
|
let v' := match v with
|
||||||
|
| .natVal val => toString val
|
||||||
|
| .strVal val => IR.EmitC.quoteString val
|
||||||
|
pure s!"(:lit {v'})"
|
||||||
|
| .mdata _ inner =>
|
||||||
|
-- NOTE: Equivalent to expr itself, but mdata influences the prettyprinter
|
||||||
|
-- It may become necessary to incorporate the metadata.
|
||||||
|
self inner
|
||||||
|
| .proj _ _ _ => do
|
||||||
|
let env ← getEnv
|
||||||
|
let projApp := exprProjToApp env e
|
||||||
|
let autos := String.intercalate " " (List.replicate projApp.numParams "_")
|
||||||
|
let inner ← self projApp.inner
|
||||||
|
pure s!"((:c {projApp.projector}) {autos} {inner})"
|
||||||
|
-- Elides all unhygenic names
|
||||||
|
binderInfoSexp : Lean.BinderInfo → String
|
||||||
|
| .default => ""
|
||||||
|
| .implicit => " :i"
|
||||||
|
| .strictImplicit => " :si"
|
||||||
|
| .instImplicit => " :ii"
|
||||||
|
|
||||||
|
def serializeExpression (options: @&Protocol.Options) (e: Expr): MetaM Protocol.Expression := do
|
||||||
|
let pp?: Option String ← match options.printExprPretty with
|
||||||
|
| true => pure $ .some $ toString $ ← Meta.ppExpr e
|
||||||
|
| false => pure $ .none
|
||||||
|
let sexp?: Option String ← match options.printExprAST with
|
||||||
|
| true => pure $ .some $ ← serializeExpressionSexp e
|
||||||
|
| false => pure $ .none
|
||||||
|
let dependentMVars? ← match options.printDependentMVars with
|
||||||
|
| true => pure $ .some $ (← Meta.getMVars e).map (λ mvarId => mvarId.name.toString)
|
||||||
|
| false => pure $ .none
|
||||||
|
return {
|
||||||
|
pp?,
|
||||||
|
sexp?
|
||||||
|
dependentMVars?,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/-- Adapted from ppGoal -/
|
||||||
|
def serializeGoal (options: @&Protocol.Options) (goal: MVarId) (mvarDecl: MetavarDecl) (parentDecl?: Option MetavarDecl := .none)
|
||||||
|
: MetaM Protocol.Goal := do
|
||||||
|
-- Options for printing; See Meta.ppGoal for details
|
||||||
|
let showLetValues := true
|
||||||
|
let ppAuxDecls := options.printAuxDecls
|
||||||
|
let ppImplDetailHyps := options.printImplementationDetailHyps
|
||||||
|
let lctx := mvarDecl.lctx
|
||||||
|
let lctx := lctx.sanitizeNames.run' { options := (← getOptions) }
|
||||||
|
Meta.withLCtx lctx mvarDecl.localInstances do
|
||||||
|
let ppVarNameOnly (localDecl: LocalDecl): MetaM Protocol.Variable := do
|
||||||
|
match localDecl with
|
||||||
|
| .cdecl _ fvarId userName _ _ _ =>
|
||||||
|
return {
|
||||||
|
name := fvarId.name.toString,
|
||||||
|
userName:= ofName userName.simpMacroScopes,
|
||||||
|
isInaccessible := userName.isInaccessibleUserName
|
||||||
|
}
|
||||||
|
| .ldecl _ fvarId userName _ _ _ _ => do
|
||||||
|
return {
|
||||||
|
name := fvarId.name.toString,
|
||||||
|
userName := toString userName.simpMacroScopes,
|
||||||
|
isInaccessible := userName.isInaccessibleUserName
|
||||||
|
}
|
||||||
|
let ppVar (localDecl : LocalDecl) : MetaM Protocol.Variable := do
|
||||||
|
match localDecl with
|
||||||
|
| .cdecl _ fvarId userName type _ _ =>
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
let type ← instantiate type
|
||||||
|
return {
|
||||||
|
name := fvarId.name.toString,
|
||||||
|
userName:= ofName userName,
|
||||||
|
isInaccessible := userName.isInaccessibleUserName
|
||||||
|
type? := .some (← serializeExpression options type)
|
||||||
|
}
|
||||||
|
| .ldecl _ fvarId userName type val _ _ => do
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
let type ← instantiate type
|
||||||
|
let value? ← if showLetValues then
|
||||||
|
let val ← instantiate val
|
||||||
|
pure $ .some (← serializeExpression options val)
|
||||||
|
else
|
||||||
|
pure $ .none
|
||||||
|
return {
|
||||||
|
name := fvarId.name.toString,
|
||||||
|
userName:= ofName userName,
|
||||||
|
isInaccessible := userName.isInaccessibleUserName
|
||||||
|
type? := .some (← serializeExpression options type)
|
||||||
|
value? := value?
|
||||||
|
}
|
||||||
|
let vars ← lctx.foldlM (init := []) fun acc (localDecl : LocalDecl) => do
|
||||||
|
let skip := !ppAuxDecls && localDecl.isAuxDecl ||
|
||||||
|
!ppImplDetailHyps && localDecl.isImplementationDetail
|
||||||
|
if skip then
|
||||||
|
return acc
|
||||||
|
else
|
||||||
|
let nameOnly := options.noRepeat && (parentDecl?.map
|
||||||
|
(λ decl => decl.lctx.find? localDecl.fvarId |>.isSome) |>.getD false)
|
||||||
|
let var ← match nameOnly with
|
||||||
|
| true => ppVarNameOnly localDecl
|
||||||
|
| false => ppVar localDecl
|
||||||
|
return var::acc
|
||||||
|
return {
|
||||||
|
name := goal.name.toString,
|
||||||
|
userName? := if mvarDecl.userName == .anonymous then .none else .some (ofName mvarDecl.userName),
|
||||||
|
isConversion := isLHSGoal? mvarDecl.type |>.isSome,
|
||||||
|
target := (← serializeExpression options (← instantiate mvarDecl.type)),
|
||||||
|
vars := vars.reverse.toArray
|
||||||
|
}
|
||||||
|
where
|
||||||
|
instantiate := instantiateAll
|
||||||
|
ofName (n: Name) := serializeName n (sanitize := false)
|
||||||
|
|
||||||
|
protected def GoalState.serializeGoals
|
||||||
|
(state: GoalState)
|
||||||
|
(parent: Option GoalState := .none)
|
||||||
|
(options: @&Protocol.Options := {}):
|
||||||
|
MetaM (Array Protocol.Goal):= do
|
||||||
|
state.restoreMetaM
|
||||||
|
let goals := state.goals.toArray
|
||||||
|
let parentDecl? := parent.bind (λ parentState => parentState.mctx.findDecl? state.parentMVar?.get!)
|
||||||
|
goals.mapM fun goal => do
|
||||||
|
match state.mctx.findDecl? goal with
|
||||||
|
| .some mvarDecl =>
|
||||||
|
let serializedGoal ← serializeGoal options goal mvarDecl (parentDecl? := parentDecl?)
|
||||||
|
pure serializedGoal
|
||||||
|
| .none => throwError s!"Metavariable does not exist in context {goal.name}"
|
||||||
|
|
||||||
|
/-- Print the metavariables in a readable format -/
|
||||||
|
@[export pantograph_goal_state_diag_m]
|
||||||
|
protected def GoalState.diag (goalState: GoalState) (parent?: Option GoalState := .none) (options: Protocol.GoalDiag := {}): CoreM String := do
|
||||||
|
let metaM: MetaM String := do
|
||||||
|
goalState.restoreMetaM
|
||||||
|
let savedState := goalState.savedState
|
||||||
|
let goals := savedState.tactic.goals
|
||||||
|
let mctx ← getMCtx
|
||||||
|
let root := goalState.root
|
||||||
|
-- Print the root
|
||||||
|
let result: String ← match mctx.decls.find? root with
|
||||||
|
| .some decl => printMVar ">" root decl
|
||||||
|
| .none => pure s!">{root.name}: ??"
|
||||||
|
let resultGoals ← goals.filter (· != root) |>.mapM (fun mvarId =>
|
||||||
|
match mctx.decls.find? mvarId with
|
||||||
|
| .some decl => printMVar "⊢" mvarId decl
|
||||||
|
| .none => pure s!"⊢{mvarId.name}: ??"
|
||||||
|
)
|
||||||
|
let goals := goals.toSSet
|
||||||
|
let resultOthers ← mctx.decls.toList.filter (λ (mvarId, _) =>
|
||||||
|
!(goals.contains mvarId || mvarId == root) && options.printAll)
|
||||||
|
|>.mapM (fun (mvarId, decl) => do
|
||||||
|
let pref := if parentHasMVar mvarId then " " else "~"
|
||||||
|
printMVar pref mvarId decl
|
||||||
|
)
|
||||||
|
pure $ result ++ "\n" ++ (resultGoals.map (· ++ "\n") |> String.join) ++ (resultOthers.map (· ++ "\n") |> String.join)
|
||||||
|
metaM.run' {}
|
||||||
|
where
|
||||||
|
printMVar (pref: String) (mvarId: MVarId) (decl: MetavarDecl): MetaM String := mvarId.withContext do
|
||||||
|
let resultFVars: List String ←
|
||||||
|
if options.printContext then
|
||||||
|
decl.lctx.fvarIdToDecl.toList.mapM (λ (fvarId, decl) =>
|
||||||
|
do pure $ (← printFVar fvarId decl) ++ "\n")
|
||||||
|
else
|
||||||
|
pure []
|
||||||
|
let type ← if options.instantiate
|
||||||
|
then instantiateAll decl.type
|
||||||
|
else pure $ decl.type
|
||||||
|
let type_sexp ← if options.printSexp then
|
||||||
|
let sexp ← serializeExpressionSexp type
|
||||||
|
pure <| " " ++ sexp
|
||||||
|
else
|
||||||
|
pure ""
|
||||||
|
let resultMain: String := s!"{pref}{mvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type}{type_sexp}"
|
||||||
|
let resultValue: String ←
|
||||||
|
if options.printValue then
|
||||||
|
if let .some value ← getExprMVarAssignment? mvarId then
|
||||||
|
let value ← if options.instantiate
|
||||||
|
then instantiateAll value
|
||||||
|
else pure $ value
|
||||||
|
pure s!"\n := {← Meta.ppExpr value}"
|
||||||
|
else if let .some { mvarIdPending, .. } ← getDelayedMVarAssignment? mvarId then
|
||||||
|
pure s!"\n ::= {mvarIdPending.name}"
|
||||||
|
else
|
||||||
|
pure ""
|
||||||
|
else
|
||||||
|
pure ""
|
||||||
|
pure $ (String.join resultFVars) ++ resultMain ++ resultValue
|
||||||
|
printFVar (fvarId: FVarId) (decl: LocalDecl): MetaM String := do
|
||||||
|
pure s!" | {fvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type}"
|
||||||
|
userNameToString : Name → String
|
||||||
|
| .anonymous => ""
|
||||||
|
| other => s!"[{other}]"
|
||||||
|
parentHasMVar (mvarId: MVarId): Bool := parent?.map (λ state => state.mctx.decls.contains mvarId) |>.getD true
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,40 @@
|
||||||
|
import Lean
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
-- Functions for creating contexts and states
|
||||||
|
@[export pantograph_default_elab_context]
|
||||||
|
def defaultElabContext: Elab.Term.Context := {
|
||||||
|
errToSorry := false
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Read syntax object from string -/
|
||||||
|
def parseTerm (env: Environment) (s: String): Except String Syntax :=
|
||||||
|
Parser.runParserCategory
|
||||||
|
(env := env)
|
||||||
|
(catName := `term)
|
||||||
|
(input := s)
|
||||||
|
(fileName := "<stdin>")
|
||||||
|
|
||||||
|
def parseTermM [Monad m] [MonadEnv m] (s: String): m (Except String Syntax) := do
|
||||||
|
return Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := s)
|
||||||
|
(fileName := "<stdin>")
|
||||||
|
|
||||||
|
/-- Parse a syntax object. May generate additional metavariables! -/
|
||||||
|
def elabType (syn: Syntax): Elab.TermElabM (Except String Expr) := do
|
||||||
|
try
|
||||||
|
let expr ← Elab.Term.elabType syn
|
||||||
|
return .ok expr
|
||||||
|
catch ex => return .error (← ex.toMessageData.toString)
|
||||||
|
def elabTerm (syn: Syntax) (expectedType? : Option Expr := .none): Elab.TermElabM (Except String Expr) := do
|
||||||
|
try
|
||||||
|
let expr ← Elab.Term.elabTerm (stx := syn) expectedType?
|
||||||
|
return .ok expr
|
||||||
|
catch ex => return .error (← ex.toMessageData.toString)
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,170 @@
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Pantograph.Elab
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Lean.Environment
|
||||||
|
import Lean.Replay
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Environment
|
||||||
|
|
||||||
|
@[export pantograph_is_name_internal]
|
||||||
|
def isNameInternal (n: Name): Bool :=
|
||||||
|
-- Returns true if the name is an implementation detail which should not be shown to the user.
|
||||||
|
n.isAuxLemma ∨ n.hasMacroScopes
|
||||||
|
|
||||||
|
/-- Catalog all the non-internal and safe names -/
|
||||||
|
@[export pantograph_environment_catalog]
|
||||||
|
def env_catalog (env: Environment): Array Name := env.constants.fold (init := #[]) (λ acc name _ =>
|
||||||
|
match isNameInternal name with
|
||||||
|
| false => acc.push name
|
||||||
|
| true => acc)
|
||||||
|
|
||||||
|
@[export pantograph_environment_module_of_name]
|
||||||
|
def module_of_name (env: Environment) (name: Name): Option Name := do
|
||||||
|
let moduleId ← env.getModuleIdxFor? name
|
||||||
|
return env.allImportedModuleNames.get! moduleId.toNat
|
||||||
|
|
||||||
|
def toCompactSymbolName (n: Name) (info: ConstantInfo): String :=
|
||||||
|
let pref := match info with
|
||||||
|
| .axiomInfo _ => "a"
|
||||||
|
| .defnInfo _ => "d"
|
||||||
|
| .thmInfo _ => "t"
|
||||||
|
| .opaqueInfo _ => "o"
|
||||||
|
| .quotInfo _ => "q"
|
||||||
|
| .inductInfo _ => "i"
|
||||||
|
| .ctorInfo _ => "c"
|
||||||
|
| .recInfo _ => "r"
|
||||||
|
s!"{pref}{toString n}"
|
||||||
|
|
||||||
|
def toFilteredSymbol (n: Lean.Name) (info: Lean.ConstantInfo): Option String :=
|
||||||
|
if isNameInternal n || info.isUnsafe
|
||||||
|
then Option.none
|
||||||
|
else Option.some <| toCompactSymbolName n info
|
||||||
|
def catalog (_: Protocol.EnvCatalog): CoreM Protocol.EnvCatalogResult := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let names := env.constants.fold (init := #[]) (λ acc name info =>
|
||||||
|
match toFilteredSymbol name info with
|
||||||
|
| .some x => acc.push x
|
||||||
|
| .none => acc)
|
||||||
|
return { symbols := names }
|
||||||
|
def inspect (args: Protocol.EnvInspect) (options: @&Protocol.Options): CoreM (Protocol.CR Protocol.EnvInspectResult) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let name := args.name.toName
|
||||||
|
let info? := env.find? name
|
||||||
|
let info ← match info? with
|
||||||
|
| none => return .error $ Protocol.errorIndex s!"Symbol not found {args.name}"
|
||||||
|
| some info => pure info
|
||||||
|
let module? := env.getModuleIdxFor? name >>=
|
||||||
|
(λ idx => env.allImportedModuleNames.get? idx.toNat)
|
||||||
|
let value? := match args.value?, info with
|
||||||
|
| .some true, _ => info.value?
|
||||||
|
| .some false, _ => .none
|
||||||
|
| .none, .defnInfo _ => info.value?
|
||||||
|
| .none, _ => .none
|
||||||
|
let type ← unfoldAuxLemmas info.type
|
||||||
|
let value? ← value?.mapM (λ v => unfoldAuxLemmas v)
|
||||||
|
-- Information common to all symbols
|
||||||
|
let core := {
|
||||||
|
type := ← (serializeExpression options type).run',
|
||||||
|
isUnsafe := info.isUnsafe,
|
||||||
|
value? := ← value?.mapM (λ v => serializeExpression options v |>.run'),
|
||||||
|
publicName? := Lean.privateToUserName? name |>.map (·.toString),
|
||||||
|
-- BUG: Warning: getUsedConstants here will not include projections. This is a known bug.
|
||||||
|
typeDependency? := if args.dependency?.getD false
|
||||||
|
then .some <| type.getUsedConstants.map (λ n => serializeName n)
|
||||||
|
else .none,
|
||||||
|
valueDependency? := if args.dependency?.getD false
|
||||||
|
then value?.map (λ e =>
|
||||||
|
e.getUsedConstants.filter (!isNameInternal ·) |>.map (λ n => serializeName n) )
|
||||||
|
else .none,
|
||||||
|
module? := module?.map (·.toString)
|
||||||
|
}
|
||||||
|
let result ← match info with
|
||||||
|
| .inductInfo induct => pure { core with inductInfo? := .some {
|
||||||
|
numParams := induct.numParams,
|
||||||
|
numIndices := induct.numIndices,
|
||||||
|
all := induct.all.toArray.map (·.toString),
|
||||||
|
ctors := induct.ctors.toArray.map (·.toString),
|
||||||
|
isRec := induct.isRec,
|
||||||
|
isReflexive := induct.isReflexive,
|
||||||
|
isNested := induct.isNested,
|
||||||
|
} }
|
||||||
|
| .ctorInfo ctor => pure { core with constructorInfo? := .some {
|
||||||
|
induct := ctor.induct.toString,
|
||||||
|
cidx := ctor.cidx,
|
||||||
|
numParams := ctor.numParams,
|
||||||
|
numFields := ctor.numFields,
|
||||||
|
} }
|
||||||
|
| .recInfo r => pure { core with recursorInfo? := .some {
|
||||||
|
all := r.all.toArray.map (·.toString),
|
||||||
|
numParams := r.numParams,
|
||||||
|
numIndices := r.numIndices,
|
||||||
|
numMotives := r.numMotives,
|
||||||
|
numMinors := r.numMinors,
|
||||||
|
rules := ← r.rules.toArray.mapM (λ rule => do
|
||||||
|
pure {
|
||||||
|
ctor := rule.ctor.toString,
|
||||||
|
nFields := rule.nfields,
|
||||||
|
rhs := ← (serializeExpression options rule.rhs).run',
|
||||||
|
})
|
||||||
|
k := r.k,
|
||||||
|
} }
|
||||||
|
| _ => pure core
|
||||||
|
let result ← if args.source?.getD false then
|
||||||
|
let srcSearchPath ← initSrcSearchPath
|
||||||
|
let sourceUri? ← module?.bindM (Server.documentUriFromModule srcSearchPath ·)
|
||||||
|
let declRange? ← findDeclarationRanges? name
|
||||||
|
let sourceStart? := declRange?.map (·.range.pos)
|
||||||
|
let sourceEnd? := declRange?.map (·.range.endPos)
|
||||||
|
.pure {
|
||||||
|
result with
|
||||||
|
sourceUri?,
|
||||||
|
sourceStart?,
|
||||||
|
sourceEnd?,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
.pure result
|
||||||
|
return .ok result
|
||||||
|
def addDecl (args: Protocol.EnvAdd): CoreM (Protocol.CR Protocol.EnvAddResult) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let tvM: Elab.TermElabM (Except String (Expr × Expr)) := do
|
||||||
|
let type ← match parseTerm env args.type with
|
||||||
|
| .ok syn => do
|
||||||
|
match ← elabTerm syn with
|
||||||
|
| .error e => return .error e
|
||||||
|
| .ok expr => pure expr
|
||||||
|
| .error e => return .error e
|
||||||
|
let value ← match parseTerm env args.value with
|
||||||
|
| .ok syn => do
|
||||||
|
try
|
||||||
|
let expr ← Elab.Term.elabTerm (stx := syn) (expectedType? := .some type)
|
||||||
|
Lean.Elab.Term.synthesizeSyntheticMVarsNoPostponing
|
||||||
|
let expr ← instantiateMVars expr
|
||||||
|
pure $ expr
|
||||||
|
catch ex => return .error (← ex.toMessageData.toString)
|
||||||
|
| .error e => return .error e
|
||||||
|
pure $ .ok (type, value)
|
||||||
|
let (type, value) ← match ← tvM.run' (ctx := {}) |>.run' with
|
||||||
|
| .ok t => pure t
|
||||||
|
| .error e => return .error $ Protocol.errorExpr e
|
||||||
|
let constant := Lean.Declaration.defnDecl <| Lean.mkDefinitionValEx
|
||||||
|
(name := args.name.toName)
|
||||||
|
(levelParams := [])
|
||||||
|
(type := type)
|
||||||
|
(value := value)
|
||||||
|
(hints := Lean.mkReducibilityHintsRegularEx 1)
|
||||||
|
(safety := Lean.DefinitionSafety.safe)
|
||||||
|
(all := [])
|
||||||
|
let env' ← match env.addDecl (← getOptions) constant with
|
||||||
|
| .error e => do
|
||||||
|
let options ← Lean.MonadOptions.getOptions
|
||||||
|
let desc ← (e.toMessageData options).toString
|
||||||
|
return .error $ { error := "kernel", desc }
|
||||||
|
| .ok env' => pure env'
|
||||||
|
Lean.MonadEnv.modifyEnv (λ _ => env')
|
||||||
|
return .ok {}
|
||||||
|
|
||||||
|
end Pantograph.Environment
|
|
@ -0,0 +1,4 @@
|
||||||
|
import Pantograph.Frontend.Basic
|
||||||
|
import Pantograph.Frontend.Elab
|
||||||
|
import Pantograph.Frontend.InfoTree
|
||||||
|
import Pantograph.Frontend.MetaTranslate
|
|
@ -0,0 +1,127 @@
|
||||||
|
import Lean.Parser
|
||||||
|
import Lean.Elab.Frontend
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Lean.FileMap
|
||||||
|
|
||||||
|
/-- Extract the range of a `Syntax` expressed as lines and columns. -/
|
||||||
|
-- Extracted from the private declaration `Lean.Elab.formatStxRange`,
|
||||||
|
-- in `Lean.Elab.InfoTree.Main`.
|
||||||
|
@[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)
|
||||||
|
|
||||||
|
|
||||||
|
abbrev FrontendM := Elab.Frontend.FrontendM
|
||||||
|
|
||||||
|
structure CompilationStep where
|
||||||
|
fileName : String
|
||||||
|
fileMap : FileMap
|
||||||
|
src : Substring
|
||||||
|
stx : Syntax
|
||||||
|
before : Environment
|
||||||
|
after : Environment
|
||||||
|
msgs : List Message
|
||||||
|
trees : List Elab.InfoTree
|
||||||
|
|
||||||
|
namespace CompilationStep
|
||||||
|
|
||||||
|
@[export pantograph_frontend_compilation_step_message_strings_m]
|
||||||
|
def messageStrings (step: CompilationStep) : IO (Array String) := do
|
||||||
|
List.toArray <$> step.msgs.mapM (·.toString)
|
||||||
|
|
||||||
|
end CompilationStep
|
||||||
|
|
||||||
|
|
||||||
|
/--
|
||||||
|
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 ← Elab.Frontend.processCommand
|
||||||
|
let stx := (← get).commands.back!
|
||||||
|
let src := (← read).inputCtx.input.toSubstring.extract (← 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⟩ := (← read).inputCtx
|
||||||
|
return ({ fileName, fileMap, src, stx, before, after, msgs, trees }, done)
|
||||||
|
|
||||||
|
partial def mapCompilationSteps { α } (f: CompilationStep → IO α) : FrontendM (List α) := do
|
||||||
|
let (cmd, done) ← processOneCommand
|
||||||
|
if done then
|
||||||
|
if cmd.src.isEmpty then
|
||||||
|
return []
|
||||||
|
else
|
||||||
|
return [← f cmd]
|
||||||
|
else
|
||||||
|
return (← f cmd) :: (← mapCompilationSteps f)
|
||||||
|
|
||||||
|
|
||||||
|
@[export pantograph_frontend_find_source_path_m]
|
||||||
|
def findSourcePath (module : Name) : IO System.FilePath := do
|
||||||
|
return System.FilePath.mk ((← findOLean module).toString.replace ".lake/build/lib/" "") |>.withExtension "lean"
|
||||||
|
|
||||||
|
/--
|
||||||
|
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 := "<anonymous>")
|
||||||
|
(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 (env, parserState, messages) ← match env? with
|
||||||
|
| .some env => pure (env, {}, .empty)
|
||||||
|
| .none =>
|
||||||
|
let (header, parserState, messages) ← Parser.parseHeader inputCtx
|
||||||
|
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)
|
||||||
|
|
||||||
|
end Pantograph.Frontend
|
|
@ -0,0 +1,194 @@
|
||||||
|
import Lean.Elab.Import
|
||||||
|
import Lean.Elab.Command
|
||||||
|
import Lean.Elab.InfoTree
|
||||||
|
import Lean.DeclarationRange
|
||||||
|
|
||||||
|
import Pantograph.Frontend.Basic
|
||||||
|
import Pantograph.Frontend.MetaTranslate
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Frontend.InfoTree
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Frontend
|
||||||
|
|
||||||
|
-- Info tree filtering functions
|
||||||
|
|
||||||
|
/- Adapted from lean-training-data -/
|
||||||
|
structure TacticInvocation where
|
||||||
|
info : Elab.TacticInfo
|
||||||
|
ctx : Elab.ContextInfo
|
||||||
|
children : PersistentArray Elab.InfoTree
|
||||||
|
namespace TacticInvocation
|
||||||
|
|
||||||
|
/-- Return the range of the tactic, as a pair of file positions. -/
|
||||||
|
@[export pantograph_frontend_tactic_invocation_range]
|
||||||
|
protected def range (t : TacticInvocation) : Position × Position := t.ctx.fileMap.stxRange t.info.stx
|
||||||
|
|
||||||
|
/-- Pretty print a tactic. -/
|
||||||
|
protected def pp (t : TacticInvocation) : IO Format :=
|
||||||
|
t.ctx.runMetaM {} try
|
||||||
|
Lean.PrettyPrinter.ppTactic ⟨t.info.stx⟩
|
||||||
|
catch _ =>
|
||||||
|
pure "<failed to pretty print>"
|
||||||
|
|
||||||
|
/-- Run a tactic on the goals stored in a `TacticInvocation`. -/
|
||||||
|
protected def runMetaMGoalsBefore (t : TacticInvocation) (x : List MVarId → MetaM α) : IO α := do
|
||||||
|
t.ctx.runMetaM {} <| Meta.withMCtx t.info.mctxBefore <| x t.info.goalsBefore
|
||||||
|
|
||||||
|
/-- Run a tactic on the after goals stored in a `TacticInvocation`. -/
|
||||||
|
protected def runMetaMGoalsAfter (t : TacticInvocation) (x : List MVarId → MetaM α) : IO α := do
|
||||||
|
t.ctx.runMetaM {} <| Meta.withMCtx t.info.mctxAfter <| x t.info.goalsAfter
|
||||||
|
|
||||||
|
/-- Run a tactic on the main goal stored in a `TacticInvocation`. -/
|
||||||
|
protected def runMetaM (t : TacticInvocation) (x : MVarId → MetaM α) : IO α := do
|
||||||
|
match t.info.goalsBefore.head? with
|
||||||
|
| none => throw <| IO.userError s!"No goals at {← t.pp}"
|
||||||
|
| some g => t.runMetaMGoalsBefore fun _ => do g.withContext <| x g
|
||||||
|
|
||||||
|
protected def goalState (t : TacticInvocation) : IO (List Format) := do
|
||||||
|
t.runMetaMGoalsBefore (fun gs => gs.mapM fun g => do Meta.ppGoal g)
|
||||||
|
|
||||||
|
protected def goalStateAfter (t : TacticInvocation) : IO (List Format) := do
|
||||||
|
t.runMetaMGoalsAfter (fun gs => gs.mapM fun g => do Meta.ppGoal g)
|
||||||
|
|
||||||
|
protected def ppExpr (t : TacticInvocation) (e : Expr) : IO Format :=
|
||||||
|
t.runMetaM (fun _ => do Meta.ppExpr (← instantiateMVars e))
|
||||||
|
|
||||||
|
protected def usedConstants (t: TacticInvocation) : NameSet :=
|
||||||
|
let info := t.info
|
||||||
|
info.goalsBefore
|
||||||
|
|>.filterMap info.mctxAfter.getExprAssignmentCore?
|
||||||
|
|>.map Expr.getUsedConstantsAsSet
|
||||||
|
|>.foldl .union .empty
|
||||||
|
|
||||||
|
end TacticInvocation
|
||||||
|
|
||||||
|
/-- Return all `TacticInfo` nodes in an `InfoTree` corresponding to tactics,
|
||||||
|
each equipped with its relevant `ContextInfo`, and any children info trees. -/
|
||||||
|
private def collectTacticNodes (t : Elab.InfoTree) : List TacticInvocation :=
|
||||||
|
let infos := t.findAllInfo none false fun i => match i with
|
||||||
|
| .ofTacticInfo _ => true
|
||||||
|
| _ => false
|
||||||
|
infos.filterMap fun p => match p with
|
||||||
|
| (.ofTacticInfo i, some ctx, children) => .some ⟨i, ctx, children⟩
|
||||||
|
| _ => none
|
||||||
|
|
||||||
|
def collectTactics (t : Elab.InfoTree) : List TacticInvocation :=
|
||||||
|
collectTacticNodes t |>.filter fun i => i.info.isSubstantive
|
||||||
|
|
||||||
|
@[export pantograph_frontend_collect_tactics_from_compilation_step_m]
|
||||||
|
def collectTacticsFromCompilationStep (step : CompilationStep) : IO (List Protocol.InvokedTactic) := do
|
||||||
|
let tacticInfoTrees := step.trees.flatMap λ tree => tree.filter λ
|
||||||
|
| info@(.ofTacticInfo _) => info.isOriginal
|
||||||
|
| _ => false
|
||||||
|
let tactics := tacticInfoTrees.flatMap collectTactics
|
||||||
|
tactics.mapM λ invocation => do
|
||||||
|
let goalBefore := (Format.joinSep (← invocation.goalState) "\n").pretty
|
||||||
|
let goalAfter := (Format.joinSep (← invocation.goalStateAfter) "\n").pretty
|
||||||
|
let tactic ← invocation.ctx.runMetaM {} <| Meta.withMCtx invocation.info.mctxBefore do
|
||||||
|
return (← invocation.ctx.ppSyntax {} invocation.info.stx).pretty
|
||||||
|
-- FIXME: Why does this not work? There are problems with `term.pseudo.antiquot`
|
||||||
|
--PrettyPrinter.ppTactic ⟨invocation.info.stx⟩
|
||||||
|
--return t.pretty
|
||||||
|
let usedConstants := invocation.usedConstants.toArray.map λ n => n.toString
|
||||||
|
return {
|
||||||
|
goalBefore,
|
||||||
|
goalAfter,
|
||||||
|
tactic,
|
||||||
|
usedConstants,
|
||||||
|
}
|
||||||
|
|
||||||
|
structure InfoWithContext where
|
||||||
|
info: Elab.Info
|
||||||
|
context?: Option Elab.ContextInfo := .none
|
||||||
|
|
||||||
|
structure GoalCollectionOptions where
|
||||||
|
collectTypeErrors : Bool := false
|
||||||
|
|
||||||
|
private def collectSorrysInTree (t : Elab.InfoTree) (options : GoalCollectionOptions := {})
|
||||||
|
: IO (List InfoWithContext) := do
|
||||||
|
let infos ← t.findAllInfoM none fun i ctx? => match i with
|
||||||
|
| .ofTermInfo { expectedType?, expr, stx, lctx, isBinder := false, .. } => do
|
||||||
|
let .some ctx := ctx? | return (false, true)
|
||||||
|
if expr.isSorry ∧ stx.isOfKind `Lean.Parser.Term.sorry then
|
||||||
|
if expectedType?.isNone then
|
||||||
|
throw $ .userError "Sorry of indeterminant type is not allowed"
|
||||||
|
return (true, false)
|
||||||
|
unless options.collectTypeErrors do
|
||||||
|
return (false, true)
|
||||||
|
let .some expectedType := expectedType? | return (false, true)
|
||||||
|
let typeMatch ← ctx.runMetaM lctx do
|
||||||
|
let type ← Meta.inferType expr
|
||||||
|
Meta.isExprDefEqGuarded type expectedType
|
||||||
|
return match typeMatch, expr.hasSorry with
|
||||||
|
| false, true => (true, false) -- Types mismatch but has sorry -> collect, halt
|
||||||
|
| false, false => (true, false) -- Types mistmatch but no sorry -> collect, halt
|
||||||
|
| true, true => (false, true) -- Types match but has sorry -> continue
|
||||||
|
| true, false => (false, false) -- Types match but no sorries -> halt
|
||||||
|
| .ofTacticInfo { stx, goalsBefore, .. } =>
|
||||||
|
-- The `sorry` term is distinct from the `sorry` tactic
|
||||||
|
let isSorry := stx.isOfKind `Lean.Parser.Tactic.tacticSorry
|
||||||
|
return (isSorry ∧ !goalsBefore.isEmpty, ¬ isSorry)
|
||||||
|
| _ => return (false, true)
|
||||||
|
return infos.map fun (info, context?, _) => { info, context? }
|
||||||
|
|
||||||
|
-- NOTE: Plural deliberately not spelled "sorries"
|
||||||
|
@[export pantograph_frontend_collect_sorrys_m]
|
||||||
|
def collectSorrys (step: CompilationStep) (options : GoalCollectionOptions := {})
|
||||||
|
: IO (List InfoWithContext) := do
|
||||||
|
return (← step.trees.mapM $ λ tree => collectSorrysInTree tree options).flatten
|
||||||
|
|
||||||
|
structure AnnotatedGoalState where
|
||||||
|
state : GoalState
|
||||||
|
srcBoundaries : List (String.Pos × String.Pos)
|
||||||
|
|
||||||
|
/--
|
||||||
|
Since we cannot directly merge `MetavarContext`s, we have to get creative. This
|
||||||
|
function duplicates frozen mvars in term and tactic info nodes, and add them to
|
||||||
|
the current `MetavarContext`.
|
||||||
|
-/
|
||||||
|
@[export pantograph_frontend_sorrys_to_goal_state_m]
|
||||||
|
def sorrysToGoalState (sorrys : List InfoWithContext) : MetaM AnnotatedGoalState := do
|
||||||
|
assert! !sorrys.isEmpty
|
||||||
|
let goalsM := sorrys.mapM λ i => do
|
||||||
|
match i.info with
|
||||||
|
| .ofTermInfo termInfo => do
|
||||||
|
let mvarId ← MetaTranslate.translateMVarFromTermInfo termInfo i.context?
|
||||||
|
if (← mvarId.getType).hasSorry then
|
||||||
|
throwError s!"Coupling is not allowed in drafting"
|
||||||
|
return [(mvarId, stxByteRange termInfo.stx)]
|
||||||
|
| .ofTacticInfo tacticInfo => do
|
||||||
|
let mvarIds ← MetaTranslate.translateMVarFromTacticInfoBefore tacticInfo i.context?
|
||||||
|
for mvarId in mvarIds do
|
||||||
|
if (← mvarId.getType).hasSorry then
|
||||||
|
throwError s!"Coupling is not allowed in drafting"
|
||||||
|
let range := stxByteRange tacticInfo.stx
|
||||||
|
return mvarIds.map (·, range)
|
||||||
|
| _ => panic! "Invalid info"
|
||||||
|
let annotatedGoals := List.flatten (← goalsM.run {} |>.run' {})
|
||||||
|
let goals := annotatedGoals.map Prod.fst
|
||||||
|
let srcBoundaries := annotatedGoals.map Prod.snd
|
||||||
|
let root := match goals with
|
||||||
|
| [] => panic! "No MVars generated"
|
||||||
|
| [g] => g
|
||||||
|
| _ => { name := .anonymous }
|
||||||
|
let state ← GoalState.createFromMVars goals root
|
||||||
|
return { state, srcBoundaries }
|
||||||
|
|
||||||
|
|
||||||
|
@[export pantograph_frontend_collect_new_defined_constants_m]
|
||||||
|
def collectNewDefinedConstants (step : CompilationStep) : IO (List Name) := do
|
||||||
|
step.after.constants.map₂.foldlM (λ 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 name :: acc
|
||||||
|
| .ok false => return acc
|
||||||
|
| .error e => throw $ IO.userError (← e.toMessageData.toString)
|
||||||
|
) []
|
||||||
|
|
||||||
|
end Pantograph.Frontend
|
|
@ -0,0 +1,157 @@
|
||||||
|
/- Adapted from lean-training-data -/
|
||||||
|
import Lean.Elab.InfoTree
|
||||||
|
import Lean.Parser.Term
|
||||||
|
import Lean.PrettyPrinter
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Lean.Elab
|
||||||
|
|
||||||
|
private def elaboratorToString : Name → String
|
||||||
|
| .anonymous => ""
|
||||||
|
| n => s!"⟨{n}⟩ "
|
||||||
|
private def indent (s : String) : String := "\n".intercalate $ s.splitOn "\n" |>.map ("\t" ++ .)
|
||||||
|
|
||||||
|
/-- The `Syntax` for a `Lean.Elab.Info`, if there is one. -/
|
||||||
|
protected def Info.stx? : Info → Option Syntax
|
||||||
|
| .ofTacticInfo info => info.stx
|
||||||
|
| .ofTermInfo info => info.stx
|
||||||
|
| .ofCommandInfo info => info.stx
|
||||||
|
| .ofMacroExpansionInfo info => info.stx
|
||||||
|
| .ofOptionInfo info => info.stx
|
||||||
|
| .ofFieldInfo info => info.stx
|
||||||
|
| .ofCompletionInfo info => info.stx
|
||||||
|
| .ofUserWidgetInfo info => info.stx
|
||||||
|
| .ofCustomInfo info => info.stx
|
||||||
|
| .ofFVarAliasInfo _ => none
|
||||||
|
| .ofFieldRedeclInfo info => info.stx
|
||||||
|
| .ofOmissionInfo info => info.stx
|
||||||
|
| .ofChoiceInfo info => info.stx
|
||||||
|
| .ofPartialTermInfo info => info.stx
|
||||||
|
/-- Is the `Syntax` for this `Lean.Elab.Info` original, or synthetic? -/
|
||||||
|
protected def Info.isOriginal (i : Info) : Bool :=
|
||||||
|
match i.stx? with
|
||||||
|
| none => true -- Somewhat unclear what to do with `FVarAliasInfo`, so be conservative.
|
||||||
|
| some stx => match stx.getHeadInfo with
|
||||||
|
| .original .. => true
|
||||||
|
| _ => false
|
||||||
|
|
||||||
|
def ContextInfo.ppExpr (ctx : ContextInfo) (lctx : LocalContext) (e : Expr) : IO Format :=
|
||||||
|
ctx.runMetaM lctx (do Meta.ppExpr (← instantiateMVars e))
|
||||||
|
|
||||||
|
def CommandInfo.toString (info : CommandInfo) (ctx : ContextInfo) : IO String := do
|
||||||
|
let stx := (← ctx.ppSyntax {} info.stx).pretty
|
||||||
|
return s!"{elaboratorToString info.elaborator}\n{stx}"
|
||||||
|
|
||||||
|
def TermInfo.toString (info : TermInfo) (ctx : ContextInfo) : IO String := do
|
||||||
|
let stx := (← ctx.ppSyntax info.lctx info.stx).pretty
|
||||||
|
let expectedType := (← info.expectedType?.mapM fun ty => do
|
||||||
|
pure s!": {(← ctx.ppExpr info.lctx ty).pretty}").getD ""
|
||||||
|
let expr := (← ctx.ppExpr info.lctx info.expr).pretty
|
||||||
|
return s!"{elaboratorToString info.elaborator}{expr}{expectedType}\n{stx}"
|
||||||
|
|
||||||
|
/-- Find the name for the outermost `Syntax` in this `TacticInfo`. -/
|
||||||
|
def TacticInfo.name? (t : TacticInfo) : Option Name :=
|
||||||
|
match t.stx with
|
||||||
|
| Syntax.node _ n _ => some n
|
||||||
|
| _ => none
|
||||||
|
/-- Decide whether a tactic is "substantive",
|
||||||
|
or is merely a tactic combinator (e.g. `by`, `;`, multiline tactics, parenthesized tactics). -/
|
||||||
|
def TacticInfo.isSubstantive (t : TacticInfo) : Bool :=
|
||||||
|
match t.name? with
|
||||||
|
| none => false
|
||||||
|
| some `null => false
|
||||||
|
| some ``cdot => false
|
||||||
|
| some ``cdotTk => false
|
||||||
|
| some ``Lean.Parser.Term.byTactic => false
|
||||||
|
| some ``Lean.Parser.Tactic.tacticSeq => false
|
||||||
|
| some ``Lean.Parser.Tactic.tacticSeq1Indented => false
|
||||||
|
| some ``Lean.Parser.Tactic.«tactic_<;>_» => false
|
||||||
|
| some ``Lean.Parser.Tactic.paren => false
|
||||||
|
| _ => true
|
||||||
|
def TacticInfo.pp (info : TacticInfo) (ctx : ContextInfo) : IO Format :=
|
||||||
|
ctx.runMetaM {} try
|
||||||
|
Lean.PrettyPrinter.ppTactic ⟨info.stx⟩
|
||||||
|
catch _ =>
|
||||||
|
pure "<failed to pretty print>"
|
||||||
|
def TacticInfo.toString (i : TacticInfo) (ctx : ContextInfo) : IO String := do
|
||||||
|
let name := i.name?
|
||||||
|
let stx := Format.pretty (← i.pp ctx)
|
||||||
|
return s!"{name}\n{stx}"
|
||||||
|
|
||||||
|
/--
|
||||||
|
Keep `.node` nodes and `.hole` nodes satisfying predicates.
|
||||||
|
|
||||||
|
Returns a `List InfoTree`, although in most situations this will be a singleton.
|
||||||
|
-/
|
||||||
|
partial def InfoTree.filter (p : Info → Bool) (m : MVarId → Bool := fun _ => false) :
|
||||||
|
InfoTree → List InfoTree
|
||||||
|
| .context ctx tree => tree.filter p m |>.map (.context ctx)
|
||||||
|
| .node info children =>
|
||||||
|
if p info then
|
||||||
|
[.node info (children.toList.map (filter p m)).flatten.toPArray']
|
||||||
|
else
|
||||||
|
(children.toList.map (filter p m)).flatten
|
||||||
|
| .hole mvar => if m mvar then [.hole mvar] else []
|
||||||
|
|
||||||
|
/-- Analogue of `Lean.Elab.InfoTree.findInfo?`, but that returns a list of all results. -/
|
||||||
|
partial def InfoTree.findAllInfo
|
||||||
|
(t : InfoTree)
|
||||||
|
(context?: Option Elab.ContextInfo)
|
||||||
|
(haltOnMatch : Bool := false)
|
||||||
|
(pred : Elab.Info → Bool)
|
||||||
|
: List (Elab.Info × Option Elab.ContextInfo × PersistentArray Elab.InfoTree) :=
|
||||||
|
match t with
|
||||||
|
| .context inner t => findAllInfo t (inner.mergeIntoOuter? context?) haltOnMatch pred
|
||||||
|
| .node i children =>
|
||||||
|
let head := if pred i then [(i, context?, children)] else []
|
||||||
|
let tail := if haltOnMatch ∧ !head.isEmpty then [] else children.toList.flatMap (fun t => findAllInfo t context? haltOnMatch pred)
|
||||||
|
head ++ tail
|
||||||
|
| _ => []
|
||||||
|
|
||||||
|
/-- Monadic analogue of `findAllInfo`, but predicate controls whether to recurse. -/
|
||||||
|
partial def InfoTree.findAllInfoM [Monad m]
|
||||||
|
(t : InfoTree)
|
||||||
|
(context?: Option Elab.ContextInfo)
|
||||||
|
(pred : Elab.Info → Option Elab.ContextInfo → m (Bool × Bool))
|
||||||
|
: m (List (Elab.Info × Option Elab.ContextInfo × PersistentArray Elab.InfoTree)) := do
|
||||||
|
match t with
|
||||||
|
| .context inner t => t.findAllInfoM (inner.mergeIntoOuter? context?) pred
|
||||||
|
| .node i children =>
|
||||||
|
let (flagCollect, flagRecurse) ← pred i context?
|
||||||
|
let head := if flagCollect then [(i, context?, children)] else []
|
||||||
|
let tail := if ¬ flagRecurse then pure [] else children.toList.mapM (fun t => t.findAllInfoM context? pred)
|
||||||
|
return head ++ (← tail).flatten
|
||||||
|
| _ => return []
|
||||||
|
|
||||||
|
@[export pantograph_infotree_to_string_m]
|
||||||
|
partial def InfoTree.toString (t : InfoTree) (ctx?: Option Elab.ContextInfo := .none) : IO String := do
|
||||||
|
match t with
|
||||||
|
| .context ctx t => t.toString (ctx.mergeIntoOuter? ctx?)
|
||||||
|
| .node info children =>
|
||||||
|
if let some ctx := ctx? then
|
||||||
|
let node : String ← match info with
|
||||||
|
| .ofTermInfo info => pure s!"[term] {(← info.toString ctx)}"
|
||||||
|
| .ofCommandInfo info => pure s!"[command] {(← info.toString ctx)}"
|
||||||
|
| .ofTacticInfo info => pure s!"[tactic] {(← info.toString ctx)}"
|
||||||
|
| .ofMacroExpansionInfo _ => pure "[macro_exp]"
|
||||||
|
| .ofOptionInfo _ => pure "[option]"
|
||||||
|
| .ofFieldInfo _ => pure "[field]"
|
||||||
|
| .ofCompletionInfo _ => pure "[completion]"
|
||||||
|
| .ofUserWidgetInfo _ => pure "[user_widget]"
|
||||||
|
| .ofCustomInfo _ => pure "[custom]"
|
||||||
|
| .ofFVarAliasInfo _ => pure "[fvar]"
|
||||||
|
| .ofFieldRedeclInfo _ => pure "[field_redecl]"
|
||||||
|
| .ofOmissionInfo _ => pure "[omission]"
|
||||||
|
| .ofChoiceInfo _ => pure "[choice]"
|
||||||
|
| .ofPartialTermInfo _ => pure "[partial_term]"
|
||||||
|
let children := "\n".intercalate (← children.toList.mapM λ t' => do pure $ indent $ ← t'.toString ctx)
|
||||||
|
return s!"{node}\n{children}"
|
||||||
|
else throw <| IO.userError "No `ContextInfo` available."
|
||||||
|
| .hole mvarId =>
|
||||||
|
if let some ctx := ctx? then
|
||||||
|
let payload := (← ctx.runMetaM {} (do Meta.ppGoal mvarId)).pretty
|
||||||
|
return s!"[hole] {payload}"
|
||||||
|
else throw <| IO.userError "No `ContextInfo` available."
|
||||||
|
|
||||||
|
end Lean.Elab
|
|
@ -0,0 +1,165 @@
|
||||||
|
import Lean.Meta
|
||||||
|
import Std.Data.HashMap
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Frontend
|
||||||
|
|
||||||
|
namespace MetaTranslate
|
||||||
|
|
||||||
|
structure Context where
|
||||||
|
sourceMCtx : MetavarContext := {}
|
||||||
|
sourceLCtx : LocalContext := {}
|
||||||
|
|
||||||
|
abbrev FVarMap := Std.HashMap FVarId FVarId
|
||||||
|
|
||||||
|
structure State where
|
||||||
|
-- Stores mapping from old to new mvar/fvars
|
||||||
|
mvarMap: Std.HashMap MVarId MVarId := {}
|
||||||
|
fvarMap: Std.HashMap FVarId FVarId := {}
|
||||||
|
|
||||||
|
/-
|
||||||
|
Monadic state for translating a frozen meta state. The underlying `MetaM`
|
||||||
|
operates in the "target" context and state.
|
||||||
|
-/
|
||||||
|
abbrev MetaTranslateM := ReaderT Context StateRefT State MetaM
|
||||||
|
|
||||||
|
def getSourceLCtx : MetaTranslateM LocalContext := do pure (← read).sourceLCtx
|
||||||
|
def getSourceMCtx : MetaTranslateM MetavarContext := do pure (← read).sourceMCtx
|
||||||
|
def addTranslatedFVar (src dst: FVarId) : MetaTranslateM Unit := do
|
||||||
|
modifyGet λ state => ((), { state with fvarMap := state.fvarMap.insert src dst })
|
||||||
|
def addTranslatedMVar (src dst: MVarId) : MetaTranslateM Unit := do
|
||||||
|
modifyGet λ state => ((), { state with mvarMap := state.mvarMap.insert src dst })
|
||||||
|
|
||||||
|
def saveFVarMap : MetaTranslateM FVarMap := do
|
||||||
|
return (← get).fvarMap
|
||||||
|
def restoreFVarMap (map: FVarMap) : MetaTranslateM Unit := do
|
||||||
|
modifyGet λ state => ((), { state with fvarMap := map })
|
||||||
|
def resetFVarMap : MetaTranslateM Unit := do
|
||||||
|
modifyGet λ state => ((), { state with fvarMap := {} })
|
||||||
|
|
||||||
|
mutual
|
||||||
|
private partial def translateLevel (srcLevel: Level) : MetaTranslateM Level := do
|
||||||
|
let sourceMCtx ← getSourceMCtx
|
||||||
|
let (_, level) := instantiateLevelMVarsImp sourceMCtx srcLevel
|
||||||
|
match level with
|
||||||
|
| .zero => return .zero
|
||||||
|
| .succ inner => do
|
||||||
|
let inner' ← translateLevel inner
|
||||||
|
return .succ inner'
|
||||||
|
| .max l1 l2 => do
|
||||||
|
let l1' ← translateLevel l1
|
||||||
|
let l2' ← translateLevel l2
|
||||||
|
return .max l1' l2'
|
||||||
|
| .imax l1 l2 => do
|
||||||
|
let l1' ← translateLevel l1
|
||||||
|
let l2' ← translateLevel l2
|
||||||
|
return .imax l1' l2'
|
||||||
|
| .param p => return .param p
|
||||||
|
| .mvar _ =>
|
||||||
|
Meta.mkFreshLevelMVar
|
||||||
|
private partial def translateExpr (srcExpr: Expr) : MetaTranslateM Expr := do
|
||||||
|
let sourceMCtx ← getSourceMCtx
|
||||||
|
-- We want to create as few mvars as possible
|
||||||
|
let (srcExpr, _) := instantiateMVarsCore (mctx := sourceMCtx) srcExpr
|
||||||
|
--IO.println s!"Transform src: {srcExpr}"
|
||||||
|
let result ← Core.transform srcExpr λ e => do
|
||||||
|
let state ← get
|
||||||
|
match e with
|
||||||
|
| .fvar fvarId =>
|
||||||
|
let .some fvarId' := state.fvarMap[fvarId]? | panic! s!"FVar id not registered: {fvarId.name}"
|
||||||
|
-- Delegating this to `Meta.check` later
|
||||||
|
--assert! (← getLCtx).contains fvarId'
|
||||||
|
return .done $ .fvar fvarId'
|
||||||
|
| .mvar mvarId => do
|
||||||
|
-- Must not be assigned
|
||||||
|
assert! !(sourceMCtx.eAssignment.contains mvarId)
|
||||||
|
match state.mvarMap[mvarId]? with
|
||||||
|
| .some mvarId' => do
|
||||||
|
return .done $ .mvar mvarId'
|
||||||
|
| .none => do
|
||||||
|
-- Entering another LCtx, must save the current one
|
||||||
|
let fvarMap ← saveFVarMap
|
||||||
|
let mvarId' ← translateMVarId mvarId
|
||||||
|
restoreFVarMap fvarMap
|
||||||
|
return .done $ .mvar mvarId'
|
||||||
|
| .sort level => do
|
||||||
|
let level' ← translateLevel level
|
||||||
|
return .done $ .sort level'
|
||||||
|
| _ => return .continue
|
||||||
|
Meta.check result
|
||||||
|
return result
|
||||||
|
|
||||||
|
partial def translateLocalInstance (srcInstance: LocalInstance) : MetaTranslateM LocalInstance := do
|
||||||
|
return {
|
||||||
|
className := srcInstance.className,
|
||||||
|
fvar := ← translateExpr srcInstance.fvar
|
||||||
|
}
|
||||||
|
partial def translateLocalDecl (srcLocalDecl: LocalDecl) : MetaTranslateM LocalDecl := do
|
||||||
|
let fvarId ← mkFreshFVarId
|
||||||
|
addTranslatedFVar srcLocalDecl.fvarId fvarId
|
||||||
|
match srcLocalDecl with
|
||||||
|
| .cdecl index _ userName type bi kind => do
|
||||||
|
--IO.println s!"[CD] {userName} {toString type}"
|
||||||
|
return .cdecl index fvarId userName (← translateExpr type) bi kind
|
||||||
|
| .ldecl index _ userName type value nonDep kind => do
|
||||||
|
--IO.println s!"[LD] {toString type} := {toString value}"
|
||||||
|
return .ldecl index fvarId userName (← translateExpr type) (← translateExpr value) nonDep kind
|
||||||
|
|
||||||
|
partial def translateLCtx : MetaTranslateM LocalContext := do
|
||||||
|
resetFVarMap
|
||||||
|
let lctx ← MonadLCtx.getLCtx
|
||||||
|
assert! lctx.isEmpty
|
||||||
|
(← getSourceLCtx).foldlM (λ lctx srcLocalDecl => do
|
||||||
|
let localDecl ← Meta.withLCtx lctx #[] do
|
||||||
|
translateLocalDecl srcLocalDecl
|
||||||
|
pure $ lctx.addDecl localDecl
|
||||||
|
) lctx
|
||||||
|
|
||||||
|
partial def translateMVarId (srcMVarId: MVarId) : MetaTranslateM MVarId := do
|
||||||
|
if let .some mvarId' := (← get).mvarMap[srcMVarId]? then
|
||||||
|
return mvarId'
|
||||||
|
let mvarId' ← Meta.withLCtx .empty #[] do
|
||||||
|
let srcDecl := (← getSourceMCtx).findDecl? srcMVarId |>.get!
|
||||||
|
withTheReader Context (λ ctx => { ctx with sourceLCtx := srcDecl.lctx }) do
|
||||||
|
let lctx' ← translateLCtx
|
||||||
|
let localInstances' ← srcDecl.localInstances.mapM translateLocalInstance
|
||||||
|
Meta.withLCtx lctx' localInstances' do
|
||||||
|
let target' ← translateExpr srcDecl.type
|
||||||
|
let mvar' ← Meta.mkFreshExprMVar target' srcDecl.kind srcDecl.userName
|
||||||
|
let mvarId' := mvar'.mvarId!
|
||||||
|
if let .some { fvars, mvarIdPending }:= (← getSourceMCtx).getDelayedMVarAssignmentExp srcMVarId then
|
||||||
|
-- Map the fvars in the pending context.
|
||||||
|
let mvarIdPending' ← translateMVarId mvarIdPending
|
||||||
|
let fvars' ← mvarIdPending'.withContext $ fvars.mapM translateExpr
|
||||||
|
assignDelayedMVar mvarId' fvars' mvarIdPending'
|
||||||
|
pure mvarId'
|
||||||
|
addTranslatedMVar srcMVarId mvarId'
|
||||||
|
return mvarId'
|
||||||
|
end
|
||||||
|
|
||||||
|
def translateMVarFromTermInfo (termInfo : Elab.TermInfo) (context? : Option Elab.ContextInfo)
|
||||||
|
: MetaTranslateM MVarId := do
|
||||||
|
withTheReader Context (λ ctx => { ctx with
|
||||||
|
sourceMCtx := context?.map (·.mctx) |>.getD {},
|
||||||
|
sourceLCtx := termInfo.lctx,
|
||||||
|
}) do
|
||||||
|
let type := termInfo.expectedType?.get!
|
||||||
|
let lctx' ← translateLCtx
|
||||||
|
let mvar ← Meta.withLCtx lctx' #[] do
|
||||||
|
let type' ← translateExpr type
|
||||||
|
Meta.mkFreshExprSyntheticOpaqueMVar type'
|
||||||
|
return mvar.mvarId!
|
||||||
|
|
||||||
|
|
||||||
|
def translateMVarFromTacticInfoBefore (tacticInfo : Elab.TacticInfo) (_context? : Option Elab.ContextInfo)
|
||||||
|
: MetaTranslateM (List MVarId) := do
|
||||||
|
withTheReader Context (λ ctx => { ctx with sourceMCtx := tacticInfo.mctxBefore }) do
|
||||||
|
tacticInfo.goalsBefore.mapM translateMVarId
|
||||||
|
|
||||||
|
|
||||||
|
end MetaTranslate
|
||||||
|
|
||||||
|
export MetaTranslate (MetaTranslateM)
|
||||||
|
|
||||||
|
end Pantograph.Frontend
|
|
@ -0,0 +1,454 @@
|
||||||
|
/-
|
||||||
|
Functions for handling metavariables
|
||||||
|
|
||||||
|
All the functions starting with `try` resume their inner monadic state.
|
||||||
|
-/
|
||||||
|
import Pantograph.Tactic
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
/--
|
||||||
|
Represents an interconnected set of metavariables, or a state in proof search
|
||||||
|
-/
|
||||||
|
structure GoalState where
|
||||||
|
savedState : Elab.Tactic.SavedState
|
||||||
|
|
||||||
|
-- The root hole which is the search target
|
||||||
|
root: MVarId
|
||||||
|
|
||||||
|
-- Parent state metavariable source
|
||||||
|
parentMVar?: Option MVarId
|
||||||
|
|
||||||
|
-- Existence of this field shows that we are currently in `conv` mode.
|
||||||
|
-- (convRhs, goal, dormant)
|
||||||
|
convMVar?: Option (MVarId × MVarId × List MVarId) := .none
|
||||||
|
-- Previous RHS for calc, so we don't have to repeat it every time
|
||||||
|
-- WARNING: If using `state with` outside of `calc`, this must be set to `.none`
|
||||||
|
calcPrevRhs?: Option (MVarId × Expr) := .none
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_create_m]
|
||||||
|
protected def GoalState.create (expr: Expr): Elab.TermElabM GoalState := do
|
||||||
|
-- May be necessary to immediately synthesise all metavariables if we need to leave the elaboration context.
|
||||||
|
-- See https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Unknown.20universe.20metavariable/near/360130070
|
||||||
|
|
||||||
|
--Elab.Term.synthesizeSyntheticMVarsNoPostponing
|
||||||
|
--let expr ← instantiateMVars expr
|
||||||
|
let root ← Meta.mkFreshExprMVar expr (kind := MetavarKind.synthetic) (userName := .anonymous)
|
||||||
|
let savedStateMonad: Elab.Tactic.TacticM Elab.Tactic.SavedState := MonadBacktrack.saveState
|
||||||
|
let savedState ← savedStateMonad { elaborator := .anonymous } |>.run' { goals := [root.mvarId!]}
|
||||||
|
return {
|
||||||
|
root := root.mvarId!,
|
||||||
|
savedState,
|
||||||
|
parentMVar? := .none,
|
||||||
|
}
|
||||||
|
@[export pantograph_goal_state_create_from_mvars_m]
|
||||||
|
protected def GoalState.createFromMVars (goals: List MVarId) (root: MVarId): MetaM GoalState := do
|
||||||
|
let savedStateMonad: Elab.Tactic.TacticM Elab.Tactic.SavedState := MonadBacktrack.saveState
|
||||||
|
let savedState ← savedStateMonad { elaborator := .anonymous } |>.run' { goals } |>.run' {}
|
||||||
|
return {
|
||||||
|
root,
|
||||||
|
savedState,
|
||||||
|
parentMVar? := .none,
|
||||||
|
}
|
||||||
|
@[export pantograph_goal_state_is_conv]
|
||||||
|
protected def GoalState.isConv (state: GoalState): Bool :=
|
||||||
|
state.convMVar?.isSome
|
||||||
|
protected def GoalState.goals (state: GoalState): List MVarId :=
|
||||||
|
state.savedState.tactic.goals
|
||||||
|
@[export pantograph_goal_state_goals]
|
||||||
|
protected def GoalState.goalsArray (state: GoalState): Array MVarId := state.goals.toArray
|
||||||
|
protected def GoalState.mctx (state: GoalState): MetavarContext :=
|
||||||
|
state.savedState.term.meta.meta.mctx
|
||||||
|
protected def GoalState.env (state: GoalState): Environment :=
|
||||||
|
state.savedState.term.meta.core.env
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_meta_context_of_goal]
|
||||||
|
protected def GoalState.metaContextOfGoal (state: GoalState) (mvarId: MVarId): Option Meta.Context := do
|
||||||
|
let mvarDecl ← state.mctx.findDecl? mvarId
|
||||||
|
return { lctx := mvarDecl.lctx, localInstances := mvarDecl.localInstances }
|
||||||
|
protected def GoalState.metaState (state: GoalState): Meta.State :=
|
||||||
|
state.savedState.term.meta.meta
|
||||||
|
protected def GoalState.coreState (state: GoalState): Core.SavedState :=
|
||||||
|
state.savedState.term.meta.core
|
||||||
|
|
||||||
|
protected def GoalState.withContext (state: GoalState) (mvarId: MVarId) (m: MetaM α): MetaM α := do
|
||||||
|
mvarId.withContext m |>.run' (← read) state.metaState
|
||||||
|
|
||||||
|
protected def GoalState.withParentContext { n } [MonadControlT MetaM n] [Monad n] (state: GoalState): n α → n α :=
|
||||||
|
Meta.mapMetaM <| state.withContext state.parentMVar?.get!
|
||||||
|
protected def GoalState.withRootContext { n } [MonadControlT MetaM n] [Monad n] (state: GoalState): n α → n α :=
|
||||||
|
Meta.mapMetaM <| state.withContext state.root
|
||||||
|
|
||||||
|
private def GoalState.mvars (state: GoalState): SSet MVarId :=
|
||||||
|
state.mctx.decls.foldl (init := .empty) fun acc k _ => acc.insert k
|
||||||
|
protected def GoalState.restoreMetaM (state: GoalState): MetaM Unit :=
|
||||||
|
state.savedState.term.meta.restore
|
||||||
|
protected def GoalState.restoreElabM (state: GoalState): Elab.TermElabM Unit :=
|
||||||
|
state.savedState.term.restore
|
||||||
|
private def GoalState.restoreTacticM (state: GoalState) (goal: MVarId): Elab.Tactic.TacticM Unit := do
|
||||||
|
state.savedState.restore
|
||||||
|
Elab.Tactic.setGoals [goal]
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_focus]
|
||||||
|
protected def GoalState.focus (state: GoalState) (goalId: Nat): Option GoalState := do
|
||||||
|
let goal ← state.savedState.tactic.goals.get? goalId
|
||||||
|
return {
|
||||||
|
state with
|
||||||
|
savedState := {
|
||||||
|
state.savedState with
|
||||||
|
tactic := { goals := [goal] },
|
||||||
|
},
|
||||||
|
calcPrevRhs? := .none,
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Immediately bring all parent goals back into scope. Used in automatic mode -/
|
||||||
|
@[export pantograph_goal_state_immediate_resume_parent]
|
||||||
|
protected def GoalState.immediateResume (state: GoalState) (parent: GoalState): GoalState :=
|
||||||
|
-- Prune parents solved goals
|
||||||
|
let mctx := state.mctx
|
||||||
|
let parentGoals := parent.goals.filter $ λ goal => mctx.eAssignment.contains goal
|
||||||
|
{
|
||||||
|
state with
|
||||||
|
savedState := {
|
||||||
|
state.savedState with
|
||||||
|
tactic := { goals := state.goals ++ parentGoals },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/--
|
||||||
|
Brings into scope a list of goals
|
||||||
|
-/
|
||||||
|
@[export pantograph_goal_state_resume]
|
||||||
|
protected def GoalState.resume (state: GoalState) (goals: List MVarId): Except String GoalState :=
|
||||||
|
if ¬ (goals.all (λ goal => state.mvars.contains goal)) then
|
||||||
|
let invalid_goals := goals.filter (λ goal => ¬ state.mvars.contains goal) |>.map (·.name.toString)
|
||||||
|
.error s!"Goals {invalid_goals} are not in scope"
|
||||||
|
else
|
||||||
|
-- Set goals to the goals that have not been assigned yet, similar to the `focus` tactic.
|
||||||
|
let unassigned := goals.filter (λ goal =>
|
||||||
|
let mctx := state.mctx
|
||||||
|
¬(mctx.eAssignment.contains goal || mctx.dAssignment.contains goal))
|
||||||
|
.ok {
|
||||||
|
state with
|
||||||
|
savedState := {
|
||||||
|
term := state.savedState.term,
|
||||||
|
tactic := { goals := unassigned },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/--
|
||||||
|
Brings into scope all goals from `branch`
|
||||||
|
-/
|
||||||
|
@[export pantograph_goal_state_continue]
|
||||||
|
protected def GoalState.continue (target: GoalState) (branch: GoalState): Except String GoalState :=
|
||||||
|
if !target.goals.isEmpty then
|
||||||
|
.error s!"Target state has unresolved goals"
|
||||||
|
else if target.root != branch.root then
|
||||||
|
.error s!"Roots of two continued goal states do not match: {target.root.name} != {branch.root.name}"
|
||||||
|
else
|
||||||
|
target.resume (goals := branch.goals)
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_root_expr]
|
||||||
|
protected def GoalState.rootExpr? (goalState: GoalState): Option Expr := do
|
||||||
|
if goalState.root.name == .anonymous then
|
||||||
|
.none
|
||||||
|
let expr ← goalState.mctx.eAssignment.find? goalState.root
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := goalState.mctx) (e := expr)
|
||||||
|
if expr.hasExprMVar then
|
||||||
|
-- Must not assert that the goal state is empty here. We could be in a branch goal.
|
||||||
|
--assert! ¬goalState.goals.isEmpty
|
||||||
|
.none
|
||||||
|
else
|
||||||
|
assert! goalState.goals.isEmpty
|
||||||
|
return expr
|
||||||
|
@[export pantograph_goal_state_parent_expr]
|
||||||
|
protected def GoalState.parentExpr? (goalState: GoalState): Option Expr := do
|
||||||
|
let parent ← goalState.parentMVar?
|
||||||
|
let expr := goalState.mctx.eAssignment.find! parent
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := goalState.mctx) (e := expr)
|
||||||
|
return expr
|
||||||
|
@[export pantograph_goal_state_get_mvar_e_assignment]
|
||||||
|
protected def GoalState.getMVarEAssignment (goalState: GoalState) (mvarId: MVarId): Option Expr := do
|
||||||
|
let expr ← goalState.mctx.eAssignment.find? mvarId
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := goalState.mctx) (e := expr)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
--- Tactic execution functions ---
|
||||||
|
|
||||||
|
-- Mimics `Elab.Term.logUnassignedUsingErrorInfos`
|
||||||
|
private def collectAllErroredMVars (src : MVarId) : Elab.TermElabM (List MVarId) := do
|
||||||
|
-- These descendants serve as "seed" mvars. If a MVarError's mvar is related
|
||||||
|
-- to one of these seed mvars, it means an error has occurred when a tactic
|
||||||
|
-- was executing on `src`. `evalTactic`, will not capture these mvars, so we
|
||||||
|
-- need to manually find them and save them into the goal list.
|
||||||
|
|
||||||
|
let descendants ← Meta.getMVars (.mvar src)
|
||||||
|
--let _ ← Elab.Term.logUnassignedUsingErrorInfos descendants
|
||||||
|
let mut alreadyVisited : MVarIdSet := {}
|
||||||
|
let mut result : MVarIdSet := {}
|
||||||
|
for { mvarId, .. } in (← get).mvarErrorInfos do
|
||||||
|
unless alreadyVisited.contains mvarId do
|
||||||
|
alreadyVisited := alreadyVisited.insert mvarId
|
||||||
|
/- The metavariable `mvarErrorInfo.mvarId` may have been assigned or
|
||||||
|
delayed assigned to another metavariable that is unassigned. -/
|
||||||
|
let mvarDeps ← Meta.getMVars (.mvar mvarId)
|
||||||
|
if mvarDeps.any descendants.contains then do
|
||||||
|
result := mvarDeps.foldl (·.insert ·) result
|
||||||
|
return result.toList
|
||||||
|
|
||||||
|
private def mergeMVarLists (li1 li2 : List MVarId) : List MVarId :=
|
||||||
|
let li2' := li2.filter (¬ li1.contains ·)
|
||||||
|
li1 ++ li2'
|
||||||
|
|
||||||
|
/--
|
||||||
|
Set `guardMVarErrors` to true to capture mvar errors. Lean will not
|
||||||
|
automatically collect mvars from text tactics (vide
|
||||||
|
`test_tactic_failure_synthesize_placeholder`)
|
||||||
|
-/
|
||||||
|
protected def GoalState.step (state: GoalState) (goal: MVarId) (tacticM: Elab.Tactic.TacticM Unit) (guardMVarErrors : Bool := false)
|
||||||
|
: Elab.TermElabM GoalState := do
|
||||||
|
unless (← getMCtx).decls.contains goal do
|
||||||
|
throwError s!"Goal is not in context: {goal.name}"
|
||||||
|
goal.checkNotAssigned `GoalState.step
|
||||||
|
let (_, { goals }) ← tacticM { elaborator := .anonymous } |>.run { goals := [goal] }
|
||||||
|
let nextElabState ← MonadBacktrack.saveState
|
||||||
|
Elab.Term.synthesizeSyntheticMVarsNoPostponing
|
||||||
|
|
||||||
|
let goals ← if guardMVarErrors then
|
||||||
|
pure $ mergeMVarLists goals (← collectAllErroredMVars goal)
|
||||||
|
else
|
||||||
|
pure goals
|
||||||
|
return {
|
||||||
|
state with
|
||||||
|
savedState := { term := nextElabState, tactic := { goals }, },
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
calcPrevRhs? := .none,
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Response for executing a tactic -/
|
||||||
|
inductive TacticResult where
|
||||||
|
-- Goes to next state
|
||||||
|
| success (state: GoalState)
|
||||||
|
-- Tactic failed with messages
|
||||||
|
| failure (messages: Array String)
|
||||||
|
-- Could not parse tactic
|
||||||
|
| parseError (message: String)
|
||||||
|
-- The given action cannot be executed in the state
|
||||||
|
| invalidAction (message: String)
|
||||||
|
|
||||||
|
private def dumpMessageLog (prevMessageLength : Nat) : CoreM (Array String) := do
|
||||||
|
let newMessages ← (← Core.getMessageLog).toList.drop prevMessageLength
|
||||||
|
|>.filterMapM λ m => do
|
||||||
|
if m.severity == .error then
|
||||||
|
return .some $ ← m.toString
|
||||||
|
else
|
||||||
|
return .none
|
||||||
|
Core.resetMessageLog
|
||||||
|
return newMessages.toArray
|
||||||
|
|
||||||
|
/-- Executes a `TacticM` monad on this `GoalState`, collecting the errors as necessary -/
|
||||||
|
protected def GoalState.tryTacticM
|
||||||
|
(state: GoalState) (goal: MVarId) (tacticM: Elab.Tactic.TacticM Unit)
|
||||||
|
(guardMVarErrors : Bool := false)
|
||||||
|
: Elab.TermElabM TacticResult := do
|
||||||
|
let prevMessageLength := state.coreState.messages.toList.length
|
||||||
|
try
|
||||||
|
let nextState ← state.step goal tacticM guardMVarErrors
|
||||||
|
|
||||||
|
-- Check if error messages have been generated in the core.
|
||||||
|
let newMessages ← dumpMessageLog prevMessageLength
|
||||||
|
if ¬ newMessages.isEmpty then
|
||||||
|
return .failure newMessages
|
||||||
|
return .success nextState
|
||||||
|
catch exception =>
|
||||||
|
match exception with
|
||||||
|
| .internal _ => return .failure $ ← dumpMessageLog prevMessageLength
|
||||||
|
| _ => return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
/-- Execute a string tactic on given state. Restores TermElabM -/
|
||||||
|
@[export pantograph_goal_state_try_tactic_m]
|
||||||
|
protected def GoalState.tryTactic (state: GoalState) (goal: MVarId) (tactic: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let tactic ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := if state.isConv then `conv else `tactic)
|
||||||
|
(input := tactic)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok stx => pure $ stx
|
||||||
|
| .error error => return .parseError error
|
||||||
|
state.tryTacticM goal (Elab.Tactic.evalTactic tactic) true
|
||||||
|
|
||||||
|
protected def GoalState.tryAssign (state: GoalState) (goal: MVarId) (expr: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let expr ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := expr)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
state.tryTacticM goal $ Tactic.evalAssign expr
|
||||||
|
|
||||||
|
-- Specialized Tactics
|
||||||
|
|
||||||
|
protected def GoalState.tryLet (state: GoalState) (goal: MVarId) (binderName: String) (type: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let type ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := type)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
state.tryTacticM goal $ Tactic.evalLet binderName.toName type
|
||||||
|
|
||||||
|
/-- Enter conv tactic mode -/
|
||||||
|
protected def GoalState.conv (state: GoalState) (goal: MVarId):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
if state.convMVar?.isSome then
|
||||||
|
return .invalidAction "Already in conv state"
|
||||||
|
goal.checkNotAssigned `GoalState.conv
|
||||||
|
let tacticM : Elab.Tactic.TacticM (Elab.Tactic.SavedState × MVarId) := do
|
||||||
|
state.restoreTacticM goal
|
||||||
|
|
||||||
|
-- See Lean.Elab.Tactic.Conv.convTarget
|
||||||
|
let convMVar ← Elab.Tactic.withMainContext do
|
||||||
|
let (rhs, newGoal) ← Elab.Tactic.Conv.mkConvGoalFor (← Elab.Tactic.getMainTarget)
|
||||||
|
Elab.Tactic.replaceMainGoal [newGoal.mvarId!]
|
||||||
|
pure rhs.mvarId!
|
||||||
|
return (← MonadBacktrack.saveState, convMVar)
|
||||||
|
try
|
||||||
|
let (nextSavedState, convRhs) ← tacticM { elaborator := .anonymous } |>.run' state.savedState.tactic
|
||||||
|
-- Other goals are now dormant
|
||||||
|
let otherGoals := state.goals.filter $ λ g => g != goal
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := nextSavedState
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
convMVar? := .some (convRhs, goal, otherGoals),
|
||||||
|
calcPrevRhs? := .none
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
/-- Exit from `conv` mode. Resumes all goals before the mode starts and applys the conv -/
|
||||||
|
@[export pantograph_goal_state_conv_exit_m]
|
||||||
|
protected def GoalState.convExit (state: GoalState):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
let (convRhs, convGoal, _) ← match state.convMVar? with
|
||||||
|
| .some mvar => pure mvar
|
||||||
|
| .none => return .invalidAction "Not in conv state"
|
||||||
|
let tacticM : Elab.Tactic.TacticM Elab.Tactic.SavedState:= do
|
||||||
|
-- Vide `Lean.Elab.Tactic.Conv.convert`
|
||||||
|
state.savedState.restore
|
||||||
|
|
||||||
|
-- Close all existing goals with `refl`
|
||||||
|
for mvarId in (← Elab.Tactic.getGoals) do
|
||||||
|
liftM <| mvarId.refl <|> mvarId.inferInstance <|> pure ()
|
||||||
|
Elab.Tactic.pruneSolvedGoals
|
||||||
|
unless (← Elab.Tactic.getGoals).isEmpty do
|
||||||
|
throwError "convert tactic failed, there are unsolved goals\n{Elab.goalsToMessageData (← Elab.Tactic.getGoals)}"
|
||||||
|
|
||||||
|
Elab.Tactic.setGoals [convGoal]
|
||||||
|
|
||||||
|
let targetNew ← instantiateMVars (.mvar convRhs)
|
||||||
|
let proof ← instantiateMVars (.mvar convGoal)
|
||||||
|
|
||||||
|
Elab.Tactic.liftMetaTactic1 fun mvarId => mvarId.replaceTargetEq targetNew proof
|
||||||
|
MonadBacktrack.saveState
|
||||||
|
try
|
||||||
|
let nextSavedState ← tacticM { elaborator := .anonymous } |>.run' state.savedState.tactic
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := nextSavedState
|
||||||
|
parentMVar? := .some convGoal,
|
||||||
|
convMVar? := .none
|
||||||
|
calcPrevRhs? := .none
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
protected def GoalState.calcPrevRhsOf? (state: GoalState) (goal: MVarId): Option Expr := do
|
||||||
|
let (mvarId, rhs) ← state.calcPrevRhs?
|
||||||
|
if mvarId == goal then
|
||||||
|
.some rhs
|
||||||
|
else
|
||||||
|
.none
|
||||||
|
@[export pantograph_goal_state_try_calc_m]
|
||||||
|
protected def GoalState.tryCalc (state: GoalState) (goal: MVarId) (pred: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
if state.convMVar?.isSome then
|
||||||
|
return .invalidAction "Cannot initiate `calc` while in `conv` state"
|
||||||
|
let `(term|$pred) ← match Parser.runParserCategory
|
||||||
|
(env := state.env)
|
||||||
|
(catName := `term)
|
||||||
|
(input := pred)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
goal.checkNotAssigned `GoalState.tryCalc
|
||||||
|
let calcPrevRhs? := state.calcPrevRhsOf? goal
|
||||||
|
let decl ← goal.getDecl
|
||||||
|
let target ← instantiateMVars decl.type
|
||||||
|
let tag := decl.userName
|
||||||
|
try
|
||||||
|
goal.withContext do
|
||||||
|
|
||||||
|
let mut step ← Elab.Term.elabType <| ← do
|
||||||
|
if let some prevRhs := calcPrevRhs? then
|
||||||
|
Elab.Term.annotateFirstHoleWithType pred (← Meta.inferType prevRhs)
|
||||||
|
else
|
||||||
|
pure pred
|
||||||
|
|
||||||
|
let some (_, lhs, rhs) ← Elab.Term.getCalcRelation? step |
|
||||||
|
throwErrorAt pred "invalid 'calc' step, relation expected{indentExpr step}"
|
||||||
|
if let some prevRhs := calcPrevRhs? then
|
||||||
|
unless ← Meta.isDefEqGuarded lhs prevRhs do
|
||||||
|
throwErrorAt pred "invalid 'calc' step, left-hand-side is{indentD m!"{lhs} : {← Meta.inferType lhs}"}\nprevious right-hand-side is{indentD m!"{prevRhs} : {← Meta.inferType prevRhs}"}"
|
||||||
|
|
||||||
|
-- Creates a mvar to represent the proof that the calc tactic solves the
|
||||||
|
-- current branch
|
||||||
|
-- In the Lean `calc` tactic this is gobbled up by
|
||||||
|
-- `withCollectingNewGoalsFrom`
|
||||||
|
let mut proof ← Meta.mkFreshExprMVarAt (← getLCtx) (← Meta.getLocalInstances) step
|
||||||
|
(userName := tag ++ `calc)
|
||||||
|
let mvarBranch := proof.mvarId!
|
||||||
|
|
||||||
|
let mut proofType ← Meta.inferType proof
|
||||||
|
let mut remainder? := Option.none
|
||||||
|
|
||||||
|
-- The calc tactic either solves the main goal or leaves another relation.
|
||||||
|
-- Replace the main goal, and save the new goal if necessary
|
||||||
|
unless ← Meta.isDefEq proofType target do
|
||||||
|
let rec throwFailed :=
|
||||||
|
throwError "'calc' tactic failed, has type{indentExpr proofType}\nbut it is expected to have type{indentExpr target}"
|
||||||
|
let some (_, _, rhs) ← Elab.Term.getCalcRelation? proofType | throwFailed
|
||||||
|
let some (r, _, rhs') ← Elab.Term.getCalcRelation? target | throwFailed
|
||||||
|
let lastStep := mkApp2 r rhs rhs'
|
||||||
|
let lastStepGoal ← Meta.mkFreshExprSyntheticOpaqueMVar lastStep tag
|
||||||
|
(proof, proofType) ← Elab.Term.mkCalcTrans proof proofType lastStepGoal lastStep
|
||||||
|
unless ← Meta.isDefEq proofType target do throwFailed
|
||||||
|
remainder? := .some lastStepGoal.mvarId!
|
||||||
|
goal.assign proof
|
||||||
|
|
||||||
|
let goals := [ mvarBranch ] ++ remainder?.toList
|
||||||
|
let calcPrevRhs? := remainder?.map $ λ g => (g, rhs)
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := {
|
||||||
|
term := ← MonadBacktrack.saveState,
|
||||||
|
tactic := { goals },
|
||||||
|
},
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
calcPrevRhs?
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,231 @@
|
||||||
|
import Pantograph.Environment
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Pantograph.Version
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
namespace Lean
|
||||||
|
|
||||||
|
/-- This is better than the default version since it handles `.` and doesn't
|
||||||
|
crash the program when it fails. -/
|
||||||
|
def setOptionFromString' (opts : Options) (entry : String) : ExceptT String IO Options := do
|
||||||
|
let ps := (entry.splitOn "=").map String.trim
|
||||||
|
let [key, val] ← pure ps | throw "invalid configuration option entry, it must be of the form '<key> = <value>'"
|
||||||
|
let key := key.toName
|
||||||
|
let defValue ← getOptionDefaultValue key
|
||||||
|
match defValue with
|
||||||
|
| DataValue.ofString _ => pure $ opts.setString key val
|
||||||
|
| DataValue.ofBool _ =>
|
||||||
|
match val with
|
||||||
|
| "true" => pure $ opts.setBool key true
|
||||||
|
| "false" => pure $ opts.setBool key false
|
||||||
|
| _ => throw s!"invalid Bool option value '{val}'"
|
||||||
|
| DataValue.ofName _ => pure $ opts.setName key val.toName
|
||||||
|
| DataValue.ofNat _ =>
|
||||||
|
match val.toNat? with
|
||||||
|
| none => throw s!"invalid Nat option value '{val}'"
|
||||||
|
| some v => pure $ opts.setNat key v
|
||||||
|
| DataValue.ofInt _ =>
|
||||||
|
match val.toInt? with
|
||||||
|
| none => throw s!"invalid Int option value '{val}'"
|
||||||
|
| some v => pure $ opts.setInt key v
|
||||||
|
| DataValue.ofSyntax _ => throw s!"invalid Syntax option value"
|
||||||
|
|
||||||
|
end Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
def runMetaM { α } (metaM: MetaM α): CoreM α :=
|
||||||
|
metaM.run'
|
||||||
|
def runTermElabM { α } (termElabM: Elab.TermElabM α): CoreM α :=
|
||||||
|
termElabM.run' (ctx := defaultElabContext) |>.run'
|
||||||
|
|
||||||
|
def errorI (type desc: String): Protocol.InteractionError := { error := type, desc := desc }
|
||||||
|
|
||||||
|
/-- Adds the given paths to Lean package search path -/
|
||||||
|
@[export pantograph_init_search]
|
||||||
|
unsafe def initSearch (sp: String): IO Unit := do
|
||||||
|
Lean.enableInitializersExecution
|
||||||
|
Lean.initSearchPath (← Lean.findSysroot) (sp := System.SearchPath.parse sp)
|
||||||
|
|
||||||
|
/-- Creates a Core.Context object needed to run all monads -/
|
||||||
|
@[export pantograph_create_core_context]
|
||||||
|
def createCoreContext (options: Array String): IO Core.Context := do
|
||||||
|
let options? ← options.foldlM setOptionFromString' Options.empty |>.run
|
||||||
|
let options ← match options? with
|
||||||
|
| .ok options => pure options
|
||||||
|
| .error e => throw $ IO.userError s!"Options cannot be parsed: {e}"
|
||||||
|
return {
|
||||||
|
currNamespace := Name.str .anonymous "Aniva"
|
||||||
|
openDecls := [], -- No 'open' directives needed
|
||||||
|
fileName := "<Pantograph>",
|
||||||
|
fileMap := { source := "", positions := #[0] },
|
||||||
|
options := options
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Creates a Core.State object needed to run all monads -/
|
||||||
|
@[export pantograph_create_core_state]
|
||||||
|
def createCoreState (imports: Array String): IO Core.State := do
|
||||||
|
let env ← Lean.importModules
|
||||||
|
(imports := imports.map (λ str => { module := str.toName, runtimeOnly := false }))
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
return { env := env }
|
||||||
|
|
||||||
|
@[export pantograph_env_add_m]
|
||||||
|
def envAdd (name: String) (type: String) (value: String) (isTheorem: Bool):
|
||||||
|
CoreM (Protocol.CR Protocol.EnvAddResult) :=
|
||||||
|
Environment.addDecl { name, type, value, isTheorem }
|
||||||
|
|
||||||
|
@[export pantograph_parse_elab_type_m]
|
||||||
|
def parseElabType (type: String): Elab.TermElabM (Protocol.CR Expr) := do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let syn ← match parseTerm env type with
|
||||||
|
| .error str => return .error $ errorI "parsing" str
|
||||||
|
| .ok syn => pure syn
|
||||||
|
match ← elabType syn with
|
||||||
|
| .error str => return .error $ errorI "elab" str
|
||||||
|
| .ok expr => return .ok (← instantiateMVars expr)
|
||||||
|
|
||||||
|
/-- This must be a TermElabM since the parsed expr contains extra information -/
|
||||||
|
@[export pantograph_parse_elab_expr_m]
|
||||||
|
def parseElabExpr (expr: String) (expectedType?: Option String := .none): Elab.TermElabM (Protocol.CR Expr) := do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let expectedType? ← match ← expectedType?.mapM parseElabType with
|
||||||
|
| .none => pure $ .none
|
||||||
|
| .some (.ok t) => pure $ .some t
|
||||||
|
| .some (.error e) => return .error e
|
||||||
|
let syn ← match parseTerm env expr with
|
||||||
|
| .error str => return .error $ errorI "parsing" str
|
||||||
|
| .ok syn => pure syn
|
||||||
|
match ← elabTerm syn expectedType? with
|
||||||
|
| .error str => return .error $ errorI "elab" str
|
||||||
|
| .ok expr => return .ok (← instantiateMVars expr)
|
||||||
|
|
||||||
|
@[export pantograph_expr_echo_m]
|
||||||
|
def exprEcho (expr: String) (expectedType?: Option String := .none) (levels: Array String := #[]) (options: @&Protocol.Options := {}):
|
||||||
|
CoreM (Protocol.CR Protocol.ExprEchoResult) :=
|
||||||
|
runTermElabM $ Elab.Term.withLevelNames (levels.toList.map (·.toName)) do
|
||||||
|
let expr ← match ← parseElabExpr expr expectedType? with
|
||||||
|
| .error e => return .error e
|
||||||
|
| .ok expr => pure expr
|
||||||
|
try
|
||||||
|
let type ← unfoldAuxLemmas (← Meta.inferType expr)
|
||||||
|
return .ok {
|
||||||
|
type := (← serializeExpression options type),
|
||||||
|
expr := (← serializeExpression options expr)
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .error $ errorI "typing" (← exception.toMessageData.toString)
|
||||||
|
|
||||||
|
@[export pantograph_goal_start_expr_m]
|
||||||
|
def goalStartExpr (expr: String) (levels: Array String): CoreM (Protocol.CR GoalState) :=
|
||||||
|
runTermElabM $ Elab.Term.withLevelNames (levels.toList.map (·.toName)) do
|
||||||
|
let expr ← match ← parseElabType expr with
|
||||||
|
| .error e => return .error e
|
||||||
|
| .ok expr => pure $ expr
|
||||||
|
return .ok $ ← GoalState.create expr
|
||||||
|
|
||||||
|
@[export pantograph_goal_resume]
|
||||||
|
def goalResume (target: GoalState) (goals: Array String): Except String GoalState :=
|
||||||
|
target.resume (goals.map (λ n => { name := n.toName }) |>.toList)
|
||||||
|
|
||||||
|
@[export pantograph_goal_serialize_m]
|
||||||
|
def goalSerialize (state: GoalState) (options: @&Protocol.Options): CoreM (Array Protocol.Goal) :=
|
||||||
|
runMetaM <| state.serializeGoals (parent := .none) options
|
||||||
|
|
||||||
|
@[export pantograph_goal_print_m]
|
||||||
|
def goalPrint (state: GoalState) (rootExpr: Bool) (parentExpr: Bool) (goals: Bool) (extraMVars : Array String) (options: @&Protocol.Options)
|
||||||
|
: CoreM Protocol.GoalPrintResult := runMetaM do
|
||||||
|
state.restoreMetaM
|
||||||
|
|
||||||
|
let root? ← if rootExpr then
|
||||||
|
state.rootExpr?.mapM λ expr => state.withRootContext do
|
||||||
|
serializeExpression options (← instantiateAll expr)
|
||||||
|
else
|
||||||
|
pure .none
|
||||||
|
let parent? ← if parentExpr then
|
||||||
|
state.parentExpr?.mapM λ expr => state.withParentContext do
|
||||||
|
serializeExpression options (← instantiateAll expr)
|
||||||
|
else
|
||||||
|
pure .none
|
||||||
|
let goals ← if goals then
|
||||||
|
goalSerialize state options
|
||||||
|
else
|
||||||
|
pure #[]
|
||||||
|
let extraMVars ← extraMVars.mapM λ mvarId => do
|
||||||
|
let mvarId: MVarId := { name := mvarId.toName }
|
||||||
|
let .some _ ← mvarId.findDecl? | return {}
|
||||||
|
state.withContext mvarId do
|
||||||
|
let .some expr ← getExprMVarAssignment? mvarId | return {}
|
||||||
|
serializeExpression options (← instantiateAll expr)
|
||||||
|
return {
|
||||||
|
root?,
|
||||||
|
parent?,
|
||||||
|
goals,
|
||||||
|
extraMVars,
|
||||||
|
}
|
||||||
|
|
||||||
|
@[export pantograph_goal_tactic_m]
|
||||||
|
def goalTactic (state: GoalState) (goal: MVarId) (tactic: String): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryTactic goal tactic
|
||||||
|
@[export pantograph_goal_assign_m]
|
||||||
|
def goalAssign (state: GoalState) (goal: MVarId) (expr: String): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryAssign goal expr
|
||||||
|
@[export pantograph_goal_have_m]
|
||||||
|
protected def GoalState.tryHave (state: GoalState) (goal: MVarId) (binderName: String) (type: String): CoreM TacticResult := do
|
||||||
|
let type ← match (← parseTermM type) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
runTermElabM do
|
||||||
|
state.restoreElabM
|
||||||
|
state.tryTacticM goal $ Tactic.evalHave binderName.toName type
|
||||||
|
@[export pantograph_goal_try_define_m]
|
||||||
|
protected def GoalState.tryDefine (state: GoalState) (goal: MVarId) (binderName: String) (expr: String): CoreM TacticResult := do
|
||||||
|
let expr ← match (← parseTermM expr) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
runTermElabM do
|
||||||
|
state.restoreElabM
|
||||||
|
state.tryTacticM goal (Tactic.evalDefine binderName.toName expr)
|
||||||
|
@[export pantograph_goal_try_motivated_apply_m]
|
||||||
|
protected def GoalState.tryMotivatedApply (state: GoalState) (goal: MVarId) (recursor: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let recursor ← match (← parseTermM recursor) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
state.tryTacticM goal (tacticM := Tactic.evalMotivatedApply recursor)
|
||||||
|
@[export pantograph_goal_try_no_confuse_m]
|
||||||
|
protected def GoalState.tryNoConfuse (state: GoalState) (goal: MVarId) (eq: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let eq ← match (← parseTermM eq) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
state.tryTacticM goal (tacticM := Tactic.evalNoConfuse eq)
|
||||||
|
@[export pantograph_goal_try_draft_m]
|
||||||
|
protected def GoalState.tryDraft (state: GoalState) (goal: MVarId) (expr: String): CoreM TacticResult := do
|
||||||
|
let expr ← match (← parseTermM expr) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
runTermElabM do
|
||||||
|
state.restoreElabM
|
||||||
|
state.tryTacticM goal (Tactic.evalDraft expr)
|
||||||
|
@[export pantograph_goal_let_m]
|
||||||
|
def goalLet (state: GoalState) (goal: MVarId) (binderName: String) (type: String): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryLet goal binderName type
|
||||||
|
@[export pantograph_goal_conv_m]
|
||||||
|
def goalConv (state: GoalState) (goal: MVarId): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.conv goal
|
||||||
|
@[export pantograph_goal_conv_exit_m]
|
||||||
|
def goalConvExit (state: GoalState): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.convExit
|
||||||
|
@[export pantograph_goal_calc_m]
|
||||||
|
def goalCalc (state: GoalState) (goal: MVarId) (pred: String): CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryCalc goal pred
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,369 @@
|
||||||
|
/-
|
||||||
|
All the command input/output structures are stored here
|
||||||
|
|
||||||
|
Note that no command other than `InteractionError` may have `error` as one of
|
||||||
|
its field names to avoid confusion with error messages generated by the REPL.
|
||||||
|
-/
|
||||||
|
import Lean.Data.Json
|
||||||
|
import Lean.Data.Position
|
||||||
|
|
||||||
|
namespace Pantograph.Protocol
|
||||||
|
|
||||||
|
|
||||||
|
/-- Main Option structure, placed here to avoid name collision -/
|
||||||
|
structure Options where
|
||||||
|
-- When false, suppress newlines in Json objects. Useful for machine-to-machine interaction.
|
||||||
|
-- This should be false` by default to avoid any surprises with parsing.
|
||||||
|
printJsonPretty: Bool := false
|
||||||
|
-- When enabled, pretty print every expression
|
||||||
|
printExprPretty: Bool := true
|
||||||
|
-- When enabled, print the raw AST of expressions
|
||||||
|
printExprAST: Bool := false
|
||||||
|
printDependentMVars: Bool := false
|
||||||
|
-- When enabled, the types and values of persistent variables in a goal
|
||||||
|
-- are not shown unless they are new to the proof step. Reduces overhead.
|
||||||
|
-- NOTE: that this assumes the type and assignment of variables can never change.
|
||||||
|
noRepeat: Bool := false
|
||||||
|
-- See `pp.auxDecls`
|
||||||
|
printAuxDecls: Bool := false
|
||||||
|
-- See `pp.implementationDetailHyps`
|
||||||
|
printImplementationDetailHyps: Bool := false
|
||||||
|
-- If this is set to `true`, goals will never go dormant, so you don't have to manage resumption
|
||||||
|
automaticMode: Bool := true
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
abbrev OptionsT := ReaderT Options
|
||||||
|
|
||||||
|
--- Expression Objects ---
|
||||||
|
|
||||||
|
structure BoundExpression where
|
||||||
|
binders: Array (String × String)
|
||||||
|
target: String
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure Expression where
|
||||||
|
-- Pretty printed expression
|
||||||
|
pp?: Option String := .none
|
||||||
|
-- AST structure
|
||||||
|
sexp?: Option String := .none
|
||||||
|
dependentMVars?: Option (Array String) := .none
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure Variable where
|
||||||
|
/-- The internal name used in raw expressions -/
|
||||||
|
name: String := ""
|
||||||
|
/-- The name displayed to the user -/
|
||||||
|
userName: String
|
||||||
|
/-- Does the name contain a dagger -/
|
||||||
|
isInaccessible: Bool := false
|
||||||
|
type?: Option Expression := .none
|
||||||
|
value?: Option Expression := .none
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure Goal where
|
||||||
|
name: String := ""
|
||||||
|
/-- Name of the metavariable -/
|
||||||
|
userName?: Option String := .none
|
||||||
|
/-- Is the goal in conversion mode -/
|
||||||
|
isConversion: Bool := false
|
||||||
|
/-- target expression type -/
|
||||||
|
target: Expression
|
||||||
|
/-- Variables -/
|
||||||
|
vars: Array Variable := #[]
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Individual Commands and return types ---
|
||||||
|
|
||||||
|
structure Command where
|
||||||
|
cmd: String
|
||||||
|
payload: Lean.Json
|
||||||
|
deriving Lean.FromJson
|
||||||
|
|
||||||
|
structure InteractionError where
|
||||||
|
error: String
|
||||||
|
desc: String
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
def errorIndex (desc: String): InteractionError := { error := "index", desc }
|
||||||
|
def errorExpr (desc: String): InteractionError := { error := "expr", desc }
|
||||||
|
|
||||||
|
|
||||||
|
--- Individual command and return types ---
|
||||||
|
|
||||||
|
|
||||||
|
structure Reset where
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure Stat where
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure StatResult where
|
||||||
|
-- Number of goals states
|
||||||
|
nGoals: Nat
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
-- Return the type of an expression
|
||||||
|
structure ExprEcho where
|
||||||
|
expr: String
|
||||||
|
type?: Option String
|
||||||
|
-- universe levels
|
||||||
|
levels: Option (Array String) := .none
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure ExprEchoResult where
|
||||||
|
expr: Expression
|
||||||
|
type: Expression
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
-- Print all symbols in environment
|
||||||
|
structure EnvCatalog where
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure EnvCatalogResult where
|
||||||
|
symbols: Array String
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
-- Print the type of a symbol
|
||||||
|
structure EnvInspect where
|
||||||
|
name: String
|
||||||
|
-- Show the value expressions; By default definitions values are shown and
|
||||||
|
-- theorem values are hidden.
|
||||||
|
value?: Option Bool := .some false
|
||||||
|
-- Show the type and value dependencies
|
||||||
|
dependency?: Option Bool := .some false
|
||||||
|
-- Show source location
|
||||||
|
source?: Option Bool := .some false
|
||||||
|
deriving Lean.FromJson
|
||||||
|
-- See `InductiveVal`
|
||||||
|
structure InductInfo where
|
||||||
|
numParams: Nat
|
||||||
|
numIndices: Nat
|
||||||
|
all: Array String
|
||||||
|
ctors: Array String
|
||||||
|
isRec: Bool := false
|
||||||
|
isReflexive: Bool := false
|
||||||
|
isNested: Bool := false
|
||||||
|
deriving Lean.ToJson
|
||||||
|
-- See `ConstructorVal`
|
||||||
|
structure ConstructorInfo where
|
||||||
|
induct: String
|
||||||
|
cidx: Nat
|
||||||
|
numParams: Nat
|
||||||
|
numFields: Nat
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
/-- See `Lean/Declaration.lean` -/
|
||||||
|
structure RecursorRule where
|
||||||
|
ctor: String
|
||||||
|
nFields: Nat
|
||||||
|
rhs: Expression
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure RecursorInfo where
|
||||||
|
all: Array String
|
||||||
|
numParams: Nat
|
||||||
|
numIndices: Nat
|
||||||
|
numMotives: Nat
|
||||||
|
numMinors: Nat
|
||||||
|
rules: Array RecursorRule
|
||||||
|
k: Bool
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure EnvInspectResult where
|
||||||
|
type: Expression
|
||||||
|
isUnsafe: Bool := false
|
||||||
|
value?: Option Expression := .none
|
||||||
|
module?: Option String := .none
|
||||||
|
-- If the name is private, displays the public facing name
|
||||||
|
publicName?: Option String := .none
|
||||||
|
typeDependency?: Option (Array String) := .none
|
||||||
|
valueDependency?: Option (Array String) := .none
|
||||||
|
inductInfo?: Option InductInfo := .none
|
||||||
|
constructorInfo?: Option ConstructorInfo := .none
|
||||||
|
recursorInfo?: Option RecursorInfo := .none
|
||||||
|
|
||||||
|
-- Location in source
|
||||||
|
sourceUri?: Option String := .none
|
||||||
|
sourceStart?: Option Lean.Position := .none
|
||||||
|
sourceEnd?: Option Lean.Position := .none
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure EnvAdd where
|
||||||
|
name: String
|
||||||
|
type: String
|
||||||
|
value: String
|
||||||
|
isTheorem: Bool
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure EnvAddResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure EnvSaveLoad where
|
||||||
|
path: System.FilePath
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure EnvSaveLoadResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
/-- Set options; See `Options` struct above for meanings -/
|
||||||
|
structure OptionsSet where
|
||||||
|
printJsonPretty?: Option Bool
|
||||||
|
printExprPretty?: Option Bool
|
||||||
|
printExprAST?: Option Bool
|
||||||
|
printDependentMVars?: Option Bool
|
||||||
|
noRepeat?: Option Bool
|
||||||
|
printAuxDecls?: Option Bool
|
||||||
|
printImplementationDetailHyps?: Option Bool
|
||||||
|
automaticMode?: Option Bool
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure OptionsSetResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure OptionsPrint where
|
||||||
|
deriving Lean.FromJson
|
||||||
|
|
||||||
|
structure GoalStart where
|
||||||
|
-- Only one of the fields below may be populated.
|
||||||
|
expr: Option String -- Directly parse in an expression
|
||||||
|
-- universe levels
|
||||||
|
levels: Option (Array String) := .none
|
||||||
|
copyFrom: Option String -- Copy the type from a theorem in the environment
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalStartResult where
|
||||||
|
stateId: Nat := 0
|
||||||
|
-- Name of the root metavariable
|
||||||
|
root: String
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure GoalTactic where
|
||||||
|
-- Identifiers for tree, state, and goal
|
||||||
|
stateId: Nat
|
||||||
|
goalId: Nat := 0
|
||||||
|
-- One of the fields here must be filled
|
||||||
|
tactic?: Option String := .none
|
||||||
|
expr?: Option String := .none
|
||||||
|
have?: Option String := .none
|
||||||
|
let?: Option String := .none
|
||||||
|
calc?: Option String := .none
|
||||||
|
-- true to enter `conv`, `false` to exit. In case of exit the `goalId` is ignored.
|
||||||
|
conv?: Option Bool := .none
|
||||||
|
draft?: Option String := .none
|
||||||
|
|
||||||
|
-- In case of the `have` tactic, the new free variable name is provided here
|
||||||
|
binderName?: Option String := .none
|
||||||
|
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalTacticResult where
|
||||||
|
-- The next goal state id. Existence of this field shows success
|
||||||
|
nextStateId?: Option Nat := .none
|
||||||
|
-- If the array is empty, it shows the goals have been fully resolved.
|
||||||
|
goals?: Option (Array Goal) := .none
|
||||||
|
|
||||||
|
-- Existence of this field shows tactic execution failure
|
||||||
|
tacticErrors?: Option (Array String) := .none
|
||||||
|
|
||||||
|
-- Existence of this field shows the tactic parsing has failed
|
||||||
|
parseError?: Option String := .none
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure GoalContinue where
|
||||||
|
-- State from which the continuation acquires the context
|
||||||
|
target: Nat
|
||||||
|
|
||||||
|
-- One of the following must be supplied
|
||||||
|
-- The state which is an ancestor of `target` where goals will be extracted from
|
||||||
|
branch?: Option Nat := .none
|
||||||
|
-- Or, the particular goals that should be brought back into scope
|
||||||
|
goals?: Option (Array String) := .none
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalContinueResult where
|
||||||
|
nextStateId: Nat
|
||||||
|
goals: (Array Goal)
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
-- Remove goal states
|
||||||
|
structure GoalDelete where
|
||||||
|
-- This is ok being a List because it doesn't show up in the ABI
|
||||||
|
stateIds: List Nat
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalDeleteResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure GoalPrint where
|
||||||
|
stateId: Nat
|
||||||
|
|
||||||
|
-- Print root?
|
||||||
|
rootExpr?: Option Bool := .some False
|
||||||
|
-- Print the parent expr?
|
||||||
|
parentExpr?: Option Bool := .some False
|
||||||
|
-- Print goals?
|
||||||
|
goals?: Option Bool := .some False
|
||||||
|
-- Print values of extra mvars?
|
||||||
|
extraMVars?: Option (Array String) := .none
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalPrintResult where
|
||||||
|
-- The root expression
|
||||||
|
root?: Option Expression := .none
|
||||||
|
-- The filling expression of the parent goal
|
||||||
|
parent?: Option Expression := .none
|
||||||
|
goals: Array Goal := #[]
|
||||||
|
extraMVars: Array Expression := #[]
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
-- Diagnostic Options, not available in REPL
|
||||||
|
structure GoalDiag where
|
||||||
|
printContext: Bool := true
|
||||||
|
printValue: Bool := true
|
||||||
|
printNewMVars: Bool := false
|
||||||
|
-- Print all mvars
|
||||||
|
printAll: Bool := false
|
||||||
|
instantiate: Bool := true
|
||||||
|
printSexp: Bool := false
|
||||||
|
|
||||||
|
structure GoalSave where
|
||||||
|
id: Nat
|
||||||
|
path: System.FilePath
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalSaveResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure GoalLoad where
|
||||||
|
path: System.FilePath
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalLoadResult where
|
||||||
|
id: Nat
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
|
||||||
|
/-- Executes the Lean compiler on a single file -/
|
||||||
|
structure FrontendProcess where
|
||||||
|
-- One of these two must be supplied: Either supply the file name or the content.
|
||||||
|
fileName?: Option String := .none
|
||||||
|
file?: Option String := .none
|
||||||
|
-- collect tactic invocations
|
||||||
|
invocations: Bool := false
|
||||||
|
-- collect `sorry`s
|
||||||
|
sorrys: Bool := false
|
||||||
|
-- collect type errors
|
||||||
|
typeErrorsAsGoals: Bool := false
|
||||||
|
-- list new constants from each compilation step
|
||||||
|
newConstants: Bool := false
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure InvokedTactic where
|
||||||
|
goalBefore: String
|
||||||
|
goalAfter: String
|
||||||
|
tactic: String
|
||||||
|
|
||||||
|
-- List of used constants
|
||||||
|
usedConstants: Array String
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure CompilationUnit where
|
||||||
|
-- String boundaries of compilation units
|
||||||
|
boundary: (Nat × Nat)
|
||||||
|
messages: Array String := #[]
|
||||||
|
-- Tactic invocations
|
||||||
|
invocations?: Option (List InvokedTactic) := .none
|
||||||
|
goalStateId?: Option Nat := .none
|
||||||
|
goals?: Option (Array Goal) := .none
|
||||||
|
-- Code segments which generated the goals
|
||||||
|
goalSrcBoundaries?: Option (Array (Nat × Nat)) := .none
|
||||||
|
|
||||||
|
-- New constants defined in compilation unit
|
||||||
|
newConstants?: Option (Array String) := .none
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure FrontendProcessResult where
|
||||||
|
units: List CompilationUnit
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
abbrev CR α := Except InteractionError α
|
||||||
|
|
||||||
|
end Pantograph.Protocol
|
|
@ -0,0 +1,162 @@
|
||||||
|
import Lean.Environment
|
||||||
|
import Lean.Replay
|
||||||
|
import Init.System.IOError
|
||||||
|
import Std.Data.HashMap
|
||||||
|
import Pantograph.Goal
|
||||||
|
|
||||||
|
/-!
|
||||||
|
Input/Output functions
|
||||||
|
|
||||||
|
# Pickling and unpickling objects
|
||||||
|
|
||||||
|
By abusing `saveModuleData` and `readModuleData` we can pickle and unpickle objects to disk.
|
||||||
|
-/
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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) : IO Unit :=
|
||||||
|
Pantograph.pickle path (env.header.imports, env.constants.map₂)
|
||||||
|
|
||||||
|
/--
|
||||||
|
Unpickle an `Environment` from disk.
|
||||||
|
|
||||||
|
We construct a fresh `Environment` with the relevant imports,
|
||||||
|
and then replace the new constants.
|
||||||
|
-/
|
||||||
|
@[export pantograph_env_unpickle_m]
|
||||||
|
def environmentUnpickle (path : System.FilePath) : IO (Environment × CompactedRegion) := unsafe do
|
||||||
|
let ((imports, map₂), region) ← Pantograph.unpickle (Array Import × PHashMap Name ConstantInfo) path
|
||||||
|
let env ← importModules imports {} 0
|
||||||
|
return (← env.replay (Std.HashMap.ofList map₂.toList), region)
|
||||||
|
|
||||||
|
|
||||||
|
open Lean.Core in
|
||||||
|
structure CompactCoreState where
|
||||||
|
-- env : Environment
|
||||||
|
nextMacroScope : MacroScope := firstFrontendMacroScope + 1
|
||||||
|
ngen : NameGenerator := {}
|
||||||
|
-- traceState : TraceState := {}
|
||||||
|
-- cache : Cache := {}
|
||||||
|
-- messages : MessageLog := {}
|
||||||
|
-- infoState : Elab.InfoState := {}
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_pickle_m]
|
||||||
|
def goalStatePickle (goalState : GoalState) (path : System.FilePath) : IO Unit :=
|
||||||
|
let {
|
||||||
|
savedState := {
|
||||||
|
term := {
|
||||||
|
meta := {
|
||||||
|
core,
|
||||||
|
meta,
|
||||||
|
}
|
||||||
|
«elab»,
|
||||||
|
},
|
||||||
|
tactic
|
||||||
|
}
|
||||||
|
root,
|
||||||
|
parentMVar?,
|
||||||
|
convMVar?,
|
||||||
|
calcPrevRhs?,
|
||||||
|
} := goalState
|
||||||
|
--let env := core.env
|
||||||
|
Pantograph.pickle path (
|
||||||
|
({ core with } : CompactCoreState),
|
||||||
|
meta,
|
||||||
|
«elab»,
|
||||||
|
tactic,
|
||||||
|
|
||||||
|
root,
|
||||||
|
parentMVar?,
|
||||||
|
convMVar?,
|
||||||
|
calcPrevRhs?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@[export pantograph_goal_state_unpickle_m]
|
||||||
|
def goalStateUnpickle (path : System.FilePath) (env : Environment)
|
||||||
|
: IO (GoalState × CompactedRegion) := unsafe do
|
||||||
|
let ((
|
||||||
|
compactCore,
|
||||||
|
meta,
|
||||||
|
«elab»,
|
||||||
|
tactic,
|
||||||
|
|
||||||
|
root,
|
||||||
|
parentMVar?,
|
||||||
|
convMVar?,
|
||||||
|
calcPrevRhs?,
|
||||||
|
), region) ← Pantograph.unpickle (
|
||||||
|
CompactCoreState ×
|
||||||
|
Meta.State ×
|
||||||
|
Elab.Term.State ×
|
||||||
|
Elab.Tactic.State ×
|
||||||
|
|
||||||
|
MVarId ×
|
||||||
|
Option MVarId ×
|
||||||
|
Option (MVarId × MVarId × List MVarId) ×
|
||||||
|
Option (MVarId × Expr)
|
||||||
|
) path
|
||||||
|
let goalState := {
|
||||||
|
savedState := {
|
||||||
|
term := {
|
||||||
|
meta := {
|
||||||
|
core := {
|
||||||
|
compactCore with
|
||||||
|
passedHeartbeats := 0,
|
||||||
|
env,
|
||||||
|
},
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
«elab»,
|
||||||
|
},
|
||||||
|
tactic,
|
||||||
|
},
|
||||||
|
root,
|
||||||
|
parentMVar?,
|
||||||
|
convMVar?,
|
||||||
|
calcPrevRhs?,
|
||||||
|
}
|
||||||
|
return (goalState, region)
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Pantograph.Tactic.Assign
|
||||||
|
import Pantograph.Tactic.Congruence
|
||||||
|
import Pantograph.Tactic.MotivatedApply
|
||||||
|
import Pantograph.Tactic.NoConfuse
|
||||||
|
import Pantograph.Tactic.Prograde
|
|
@ -0,0 +1,64 @@
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Tactic
|
||||||
|
|
||||||
|
/-- WARNING: This should be used with a function like `elabTermWithHoles` that properly collects the mvar information from `expr`. -/
|
||||||
|
def assign (goal: MVarId) (expr: Expr) (nextGoals: List MVarId): MetaM (List MVarId) := do
|
||||||
|
goal.checkNotAssigned `Pantograph.Tactic.assign
|
||||||
|
|
||||||
|
-- This run of the unifier is critical in resolving mvars in passing
|
||||||
|
let exprType ← Meta.inferType expr
|
||||||
|
let goalType ← goal.getType
|
||||||
|
unless ← Meta.isDefEq goalType exprType do
|
||||||
|
throwError s!"{← Meta.ppExpr expr} : {← Meta.ppExpr exprType} ≠ {← Meta.ppExpr goalType}"
|
||||||
|
goal.assign expr
|
||||||
|
nextGoals.filterM (not <$> ·.isAssigned)
|
||||||
|
|
||||||
|
def evalAssign : Elab.Tactic.Tactic := fun stx => Elab.Tactic.withMainContext do
|
||||||
|
let target ← Elab.Tactic.getMainTarget
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
goal.checkNotAssigned `Pantograph.Tactic.evalAssign
|
||||||
|
let (expr, nextGoals) ← Elab.Tactic.elabTermWithHoles stx
|
||||||
|
(expectedType? := .some target)
|
||||||
|
(tagSuffix := .anonymous )
|
||||||
|
(allowNaturalHoles := true)
|
||||||
|
goal.assign expr
|
||||||
|
Elab.Tactic.replaceMainGoal nextGoals
|
||||||
|
|
||||||
|
def sorryToHole (src : Expr) : StateRefT (List MVarId) MetaM Expr := do
|
||||||
|
Meta.transform src λ
|
||||||
|
| .app (.app (.const ``sorryAx ..) type) .. => do
|
||||||
|
let type ← instantiateMVars type
|
||||||
|
if type.hasSorry then
|
||||||
|
throwError s!"Coupling is not allowed in draft tactic: {← Meta.ppExpr type}"
|
||||||
|
let mvar ← Meta.mkFreshExprSyntheticOpaqueMVar type
|
||||||
|
modify (mvar.mvarId! :: .)
|
||||||
|
pure $ .done mvar
|
||||||
|
| _ => pure .continue
|
||||||
|
|
||||||
|
-- Given a complete (no holes) expression, extract the sorry's from it and convert them into goals.
|
||||||
|
def draft (goal : MVarId) (expr : Expr) : MetaM (List MVarId) := do
|
||||||
|
goal.checkNotAssigned `Pantograph.Tactic.draft
|
||||||
|
let exprType ← Meta.inferType expr
|
||||||
|
let goalType ← goal.getType
|
||||||
|
unless ← Meta.isDefEq goalType exprType do
|
||||||
|
throwError s!"{← Meta.ppExpr expr} : {← Meta.ppExpr exprType} ≠ {← Meta.ppExpr goalType}"
|
||||||
|
|
||||||
|
let (expr', holes) ← sorryToHole expr |>.run []
|
||||||
|
goal.assign expr'
|
||||||
|
return holes.reverse
|
||||||
|
|
||||||
|
def evalDraft : Elab.Tactic.Tactic := fun stx ↦ Elab.Tactic.withMainContext do
|
||||||
|
let target ← Elab.Tactic.getMainTarget
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let (expr, holeGoals) ← Elab.Tactic.elabTermWithHoles stx
|
||||||
|
(expectedType? := .some target)
|
||||||
|
(tagSuffix := .anonymous)
|
||||||
|
(allowNaturalHoles := true)
|
||||||
|
let draftGoals ← draft goal expr
|
||||||
|
Elab.Tactic.replaceMainGoal $ holeGoals ++ draftGoals
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph.Tactic
|
|
@ -0,0 +1,98 @@
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Tactic
|
||||||
|
|
||||||
|
def congruenceArg (mvarId: MVarId): MetaM (List MVarId) := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.congruenceArg
|
||||||
|
let target ← mvarId.getType
|
||||||
|
let .some (β, _, _) := (← instantiateMVars target).eq? | throwError "Goal is not an Eq"
|
||||||
|
let userName := (← mvarId.getDecl).userName
|
||||||
|
|
||||||
|
let u ← Meta.mkFreshLevelMVar
|
||||||
|
let α ← Meta.mkFreshExprSyntheticOpaqueMVar (mkSort u)
|
||||||
|
(tag := userName ++ `α)
|
||||||
|
let f ← Meta.mkFreshExprSyntheticOpaqueMVar (.forallE .anonymous α β .default)
|
||||||
|
(tag := userName ++ `f)
|
||||||
|
let a₁ ← Meta.mkFreshExprSyntheticOpaqueMVar α
|
||||||
|
(tag := userName ++ `a₁)
|
||||||
|
let a₂ ← Meta.mkFreshExprSyntheticOpaqueMVar α
|
||||||
|
(tag := userName ++ `a₂)
|
||||||
|
let h ← Meta.mkFreshExprSyntheticOpaqueMVar (← Meta.mkEq a₁ a₂)
|
||||||
|
(tag := userName ++ `h)
|
||||||
|
let conduitType ← Meta.mkEq (← Meta.mkEq (.app f a₁) (.app f a₂)) target
|
||||||
|
let conduit ← Meta.mkFreshExprSyntheticOpaqueMVar conduitType
|
||||||
|
(tag := userName ++ `conduit)
|
||||||
|
mvarId.assign $ ← Meta.mkEqMP conduit (← Meta.mkCongrArg f h)
|
||||||
|
let result := [α, a₁, a₂, f, h, conduit]
|
||||||
|
return result.map (·.mvarId!)
|
||||||
|
|
||||||
|
def evalCongruenceArg: Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let nextGoals ← congruenceArg goal
|
||||||
|
Elab.Tactic.replaceMainGoal nextGoals
|
||||||
|
|
||||||
|
def congruenceFun (mvarId: MVarId): MetaM (List MVarId) := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.congruenceFun
|
||||||
|
let target ← mvarId.getType
|
||||||
|
let .some (β, _, _) := (← instantiateMVars target).eq? | throwError "Goal is not an Eq"
|
||||||
|
let userName := (← mvarId.getDecl).userName
|
||||||
|
let u ← Meta.mkFreshLevelMVar
|
||||||
|
let α ← Meta.mkFreshExprSyntheticOpaqueMVar (mkSort u)
|
||||||
|
(tag := userName ++ `α)
|
||||||
|
let fType := .forallE .anonymous α β .default
|
||||||
|
let f₁ ← Meta.mkFreshExprSyntheticOpaqueMVar fType
|
||||||
|
(tag := userName ++ `f₁)
|
||||||
|
let f₂ ← Meta.mkFreshExprSyntheticOpaqueMVar fType
|
||||||
|
(tag := userName ++ `f₂)
|
||||||
|
let a ← Meta.mkFreshExprSyntheticOpaqueMVar α
|
||||||
|
(tag := userName ++ `a)
|
||||||
|
let h ← Meta.mkFreshExprSyntheticOpaqueMVar (← Meta.mkEq f₁ f₂)
|
||||||
|
(tag := userName ++ `h)
|
||||||
|
let conduitType ← Meta.mkEq (← Meta.mkEq (.app f₁ a) (.app f₂ a)) target
|
||||||
|
let conduit ← Meta.mkFreshExprSyntheticOpaqueMVar conduitType
|
||||||
|
(tag := userName ++ `conduit)
|
||||||
|
mvarId.assign $ ← Meta.mkEqMP conduit (← Meta.mkCongrFun h a)
|
||||||
|
let result := [α, f₁, f₂, h, a, conduit]
|
||||||
|
return result.map (·.mvarId!)
|
||||||
|
|
||||||
|
def evalCongruenceFun: Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let nextGoals ← congruenceFun goal
|
||||||
|
Elab.Tactic.replaceMainGoal nextGoals
|
||||||
|
|
||||||
|
def congruence (mvarId: MVarId): MetaM (List MVarId) := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.congruence
|
||||||
|
let target ← mvarId.getType
|
||||||
|
let .some (β, _, _) := (← instantiateMVars target).eq? | throwError "Goal is not an Eq"
|
||||||
|
let userName := (← mvarId.getDecl).userName
|
||||||
|
let u ← Meta.mkFreshLevelMVar
|
||||||
|
let α ← Meta.mkFreshExprSyntheticOpaqueMVar (mkSort u)
|
||||||
|
(tag := userName ++ `α)
|
||||||
|
let fType := .forallE .anonymous α β .default
|
||||||
|
let f₁ ← Meta.mkFreshExprSyntheticOpaqueMVar fType
|
||||||
|
(tag := userName ++ `f₁)
|
||||||
|
let f₂ ← Meta.mkFreshExprSyntheticOpaqueMVar fType
|
||||||
|
(tag := userName ++ `f₂)
|
||||||
|
let a₁ ← Meta.mkFreshExprSyntheticOpaqueMVar α
|
||||||
|
(tag := userName ++ `a₁)
|
||||||
|
let a₂ ← Meta.mkFreshExprSyntheticOpaqueMVar α
|
||||||
|
(tag := userName ++ `a₂)
|
||||||
|
let h₁ ← Meta.mkFreshExprSyntheticOpaqueMVar (← Meta.mkEq f₁ f₂)
|
||||||
|
(tag := userName ++ `h₁)
|
||||||
|
let h₂ ← Meta.mkFreshExprSyntheticOpaqueMVar (← Meta.mkEq a₁ a₂)
|
||||||
|
(tag := userName ++ `h₂)
|
||||||
|
let conduitType ← Meta.mkEq (← Meta.mkEq (.app f₁ a₁) (.app f₂ a₂)) target
|
||||||
|
let conduit ← Meta.mkFreshExprSyntheticOpaqueMVar conduitType
|
||||||
|
(tag := userName ++ `conduit)
|
||||||
|
mvarId.assign $ ← Meta.mkEqMP conduit (← Meta.mkCongr h₁ h₂)
|
||||||
|
let result := [α, f₁, f₂, a₁, a₂, h₁, h₂, conduit]
|
||||||
|
return result.map (·.mvarId!)
|
||||||
|
|
||||||
|
def evalCongruence: Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let nextGoals ← congruence goal
|
||||||
|
Elab.Tactic.replaceMainGoal nextGoals
|
||||||
|
|
||||||
|
end Pantograph.Tactic
|
|
@ -0,0 +1,106 @@
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Tactic
|
||||||
|
|
||||||
|
def getForallArgsBody: Expr → List Expr × Expr
|
||||||
|
| .forallE _ d b _ =>
|
||||||
|
let (innerArgs, innerBody) := getForallArgsBody b
|
||||||
|
(d :: innerArgs, innerBody)
|
||||||
|
| e => ([], e)
|
||||||
|
|
||||||
|
def replaceForallBody: Expr → Expr → Expr
|
||||||
|
| .forallE param domain body binderInfo, target =>
|
||||||
|
let body := replaceForallBody body target
|
||||||
|
.forallE param domain body binderInfo
|
||||||
|
| _, target => target
|
||||||
|
|
||||||
|
structure RecursorWithMotive where
|
||||||
|
args: List Expr
|
||||||
|
body: Expr
|
||||||
|
|
||||||
|
-- .bvar index for the motive and major from the body
|
||||||
|
iMotive: Nat
|
||||||
|
|
||||||
|
namespace RecursorWithMotive
|
||||||
|
|
||||||
|
protected def nArgs (info: RecursorWithMotive): Nat := info.args.length
|
||||||
|
|
||||||
|
protected def getMotiveType (info: RecursorWithMotive): Expr :=
|
||||||
|
let level := info.nArgs - info.iMotive - 1
|
||||||
|
let a := info.args.get! level
|
||||||
|
a
|
||||||
|
|
||||||
|
protected def surrogateMotiveType (info: RecursorWithMotive) (mvars: Array Expr) (resultant: Expr): MetaM Expr := do
|
||||||
|
let motiveType := Expr.instantiateRev info.getMotiveType mvars
|
||||||
|
let resultantType ← Meta.inferType resultant
|
||||||
|
return replaceForallBody motiveType resultantType
|
||||||
|
|
||||||
|
protected def conduitType (info: RecursorWithMotive) (mvars: Array Expr) (resultant: Expr): MetaM Expr := do
|
||||||
|
let motiveCall := Expr.instantiateRev info.body mvars
|
||||||
|
Meta.mkEq motiveCall resultant
|
||||||
|
|
||||||
|
end RecursorWithMotive
|
||||||
|
|
||||||
|
def getRecursorInformation (recursorType: Expr): Option RecursorWithMotive := do
|
||||||
|
let (args, body) := getForallArgsBody recursorType
|
||||||
|
if ¬ body.isApp then
|
||||||
|
.none
|
||||||
|
let iMotive ← match body.getAppFn with
|
||||||
|
| .bvar iMotive => pure iMotive
|
||||||
|
| _ => .none
|
||||||
|
return {
|
||||||
|
args,
|
||||||
|
body,
|
||||||
|
iMotive,
|
||||||
|
}
|
||||||
|
|
||||||
|
def collectMotiveArguments (forallBody: Expr): SSet Nat :=
|
||||||
|
match forallBody with
|
||||||
|
| .app (.bvar i) _ => SSet.empty.insert i
|
||||||
|
| _ => SSet.empty
|
||||||
|
|
||||||
|
/-- Applies a symbol of the type `∀ (motive: α → Sort u) (a: α)..., (motive α)` -/
|
||||||
|
def motivatedApply (mvarId: MVarId) (recursor: Expr) : MetaM (Array Meta.InductionSubgoal) := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.motivatedApply
|
||||||
|
let recursorType ← Meta.inferType recursor
|
||||||
|
let resultant ← mvarId.getType
|
||||||
|
let tag ← mvarId.getTag
|
||||||
|
|
||||||
|
let info ← match getRecursorInformation recursorType with
|
||||||
|
| .some info => pure info
|
||||||
|
| .none => throwError "Recursor return type does not correspond with the invocation of a motive: {← Meta.ppExpr recursorType}"
|
||||||
|
|
||||||
|
let rec go (i: Nat) (prev: Array Expr): MetaM (Array Expr) := do
|
||||||
|
if i ≥ info.nArgs then
|
||||||
|
return prev
|
||||||
|
else
|
||||||
|
let argType := info.args.get! i
|
||||||
|
-- If `argType` has motive references, its goal needs to be placed in it
|
||||||
|
let argType := argType.instantiateRev prev
|
||||||
|
let bvarIndex := info.nArgs - i - 1
|
||||||
|
let argGoal ← if bvarIndex = info.iMotive then
|
||||||
|
let surrogateMotiveType ← info.surrogateMotiveType prev resultant
|
||||||
|
Meta.mkFreshExprSyntheticOpaqueMVar surrogateMotiveType (tag := tag ++ `motive)
|
||||||
|
else
|
||||||
|
Meta.mkFreshExprSyntheticOpaqueMVar argType (tag := .anonymous)
|
||||||
|
let prev := prev ++ [argGoal]
|
||||||
|
go (i + 1) prev
|
||||||
|
termination_by info.nArgs - i
|
||||||
|
let mut newMVars ← go 0 #[]
|
||||||
|
|
||||||
|
-- Create the conduit type which proves the result of the motive is equal to the goal
|
||||||
|
let conduitType ← info.conduitType newMVars resultant
|
||||||
|
let goalConduit ← Meta.mkFreshExprSyntheticOpaqueMVar conduitType (tag := `conduit)
|
||||||
|
mvarId.assign $ ← Meta.mkEqMP goalConduit (mkAppN recursor newMVars)
|
||||||
|
newMVars := newMVars ++ [goalConduit]
|
||||||
|
|
||||||
|
return newMVars.map (λ mvar => { mvarId := mvar.mvarId!})
|
||||||
|
|
||||||
|
def evalMotivatedApply : Elab.Tactic.Tactic := fun stx => Elab.Tactic.withMainContext do
|
||||||
|
let recursor ← Elab.Term.elabTerm (stx := stx) .none
|
||||||
|
let nextGoals ← motivatedApply (← Elab.Tactic.getMainGoal) recursor
|
||||||
|
Elab.Tactic.replaceMainGoal $ nextGoals.toList.map (·.mvarId)
|
||||||
|
|
||||||
|
end Pantograph.Tactic
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Tactic
|
||||||
|
|
||||||
|
def noConfuse (mvarId: MVarId) (h: Expr): MetaM Unit := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.noConfuse
|
||||||
|
let target ← mvarId.getType
|
||||||
|
let noConfusion ← Meta.mkNoConfusion (target := target) (h := h)
|
||||||
|
|
||||||
|
unless ← Meta.isDefEq (← Meta.inferType noConfusion) target do
|
||||||
|
throwError "invalid noConfuse call: The resultant type {← Meta.ppExpr $ ← Meta.inferType noConfusion} cannot be unified with {← Meta.ppExpr target}"
|
||||||
|
mvarId.assign noConfusion
|
||||||
|
|
||||||
|
def evalNoConfuse: Elab.Tactic.Tactic := λ stx => do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let h ← goal.withContext $ Elab.Term.elabTerm (stx := stx) .none
|
||||||
|
noConfuse goal h
|
||||||
|
Elab.Tactic.replaceMainGoal []
|
||||||
|
|
||||||
|
end Pantograph.Tactic
|
|
@ -0,0 +1,88 @@
|
||||||
|
/- Prograde (forward) reasoning tactics -/
|
||||||
|
|
||||||
|
import Lean
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Tactic
|
||||||
|
|
||||||
|
private def mkUpstreamMVar (goal: MVarId) : MetaM Expr := do
|
||||||
|
Meta.mkFreshExprSyntheticOpaqueMVar (← goal.getType) (tag := ← goal.getTag)
|
||||||
|
|
||||||
|
|
||||||
|
/-- Introduces a fvar to the current mvar -/
|
||||||
|
def define (mvarId: MVarId) (binderName: Name) (expr: Expr): MetaM (FVarId × MVarId) := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.define
|
||||||
|
let type ← Meta.inferType expr
|
||||||
|
|
||||||
|
Meta.withLetDecl binderName type expr λ fvar => do
|
||||||
|
let mvarUpstream ← mkUpstreamMVar mvarId
|
||||||
|
mvarId.assign $ ← Meta.mkLetFVars #[fvar] mvarUpstream
|
||||||
|
pure (fvar.fvarId!, mvarUpstream.mvarId!)
|
||||||
|
|
||||||
|
def evalDefine (binderName: Name) (expr: Syntax): Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let expr ← goal.withContext $ Elab.Term.elabTerm (stx := expr) (expectedType? := .none)
|
||||||
|
let (_, mvarId) ← define goal binderName expr
|
||||||
|
Elab.Tactic.replaceMainGoal [mvarId]
|
||||||
|
|
||||||
|
structure BranchResult where
|
||||||
|
fvarId?: Option FVarId := .none
|
||||||
|
branch: MVarId
|
||||||
|
main: MVarId
|
||||||
|
|
||||||
|
def «have» (mvarId: MVarId) (binderName: Name) (type: Expr): MetaM BranchResult := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.have
|
||||||
|
let lctx ← MonadLCtx.getLCtx
|
||||||
|
-- The branch goal inherits the same context, but with a different type
|
||||||
|
let mvarBranch ← Meta.mkFreshExprMVarAt lctx (← Meta.getLocalInstances) type
|
||||||
|
|
||||||
|
-- Create the context for the `upstream` goal
|
||||||
|
let fvarId ← mkFreshFVarId
|
||||||
|
let lctxUpstream := lctx.mkLocalDecl fvarId binderName type
|
||||||
|
let mvarUpstream ←
|
||||||
|
Meta.withLCtx lctxUpstream #[] do
|
||||||
|
Meta.withNewLocalInstances #[.fvar fvarId] 0 do
|
||||||
|
let mvarUpstream ← mkUpstreamMVar mvarId
|
||||||
|
--let expr: Expr := .app (.lam binderName type mvarBranch .default) mvarUpstream
|
||||||
|
mvarId.assign $ ← Meta.mkLambdaFVars #[.fvar fvarId] mvarUpstream
|
||||||
|
pure mvarUpstream
|
||||||
|
|
||||||
|
return {
|
||||||
|
fvarId? := .some fvarId,
|
||||||
|
branch := mvarBranch.mvarId!,
|
||||||
|
main := mvarUpstream.mvarId!,
|
||||||
|
}
|
||||||
|
|
||||||
|
def evalHave (binderName: Name) (type: Syntax): Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let nextGoals: List MVarId ← goal.withContext do
|
||||||
|
let type ← Elab.Term.elabType (stx := type)
|
||||||
|
let result ← «have» goal binderName type
|
||||||
|
pure [result.branch, result.main]
|
||||||
|
Elab.Tactic.replaceMainGoal nextGoals
|
||||||
|
|
||||||
|
def «let» (mvarId: MVarId) (binderName: Name) (type: Expr): MetaM BranchResult := mvarId.withContext do
|
||||||
|
mvarId.checkNotAssigned `Pantograph.Tactic.let
|
||||||
|
let lctx ← MonadLCtx.getLCtx
|
||||||
|
|
||||||
|
-- The branch goal inherits the same context, but with a different type
|
||||||
|
let mvarBranch ← Meta.mkFreshExprMVarAt lctx (← Meta.getLocalInstances) type (userName := binderName)
|
||||||
|
|
||||||
|
assert! ¬ type.hasLooseBVars
|
||||||
|
let mvarUpstream ← Meta.withLetDecl binderName type mvarBranch $ λ fvar => do
|
||||||
|
let mvarUpstream ← mkUpstreamMVar mvarId
|
||||||
|
mvarId.assign $ ← Meta.mkLetFVars #[fvar] mvarUpstream
|
||||||
|
pure mvarUpstream
|
||||||
|
|
||||||
|
return {
|
||||||
|
branch := mvarBranch.mvarId!,
|
||||||
|
main := mvarUpstream.mvarId!,
|
||||||
|
}
|
||||||
|
|
||||||
|
def evalLet (binderName: Name) (type: Syntax): Elab.Tactic.TacticM Unit := do
|
||||||
|
let goal ← Elab.Tactic.getMainGoal
|
||||||
|
let type ← goal.withContext $ Elab.Term.elabType (stx := type)
|
||||||
|
let result ← «let» goal binderName type
|
||||||
|
Elab.Tactic.replaceMainGoal [result.branch, result.main]
|
||||||
|
|
||||||
|
end Pantograph.Tactic
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
@[export pantograph_version]
|
||||||
|
def version := "0.2.25"
|
||||||
|
|
||||||
|
end Pantograph
|
166
README.md
166
README.md
|
@ -1,60 +1,130 @@
|
||||||
# PyPantograph
|
# Pantograph
|
||||||
|
|
||||||
A Machine-to-Machine Interaction System for Lean 4.
|
A Machine-to-Machine interaction system for Lean 4.
|
||||||
|
|
||||||
|
![Pantograph](doc/icon.svg)
|
||||||
|
|
||||||
|
Pantograph provides interfaces to execute proofs, construct expressions, and
|
||||||
|
examine the symbol list of a Lean project for machine learning.
|
||||||
|
|
||||||
|
See [documentations](doc/rationale.md) for design rationale and references.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install `poetry`
|
For Nix users, run
|
||||||
2. Clone this repository with submodules:
|
``` sh
|
||||||
|
nix build .#{sharedLib,executable}
|
||||||
|
```
|
||||||
|
to build either the shared library or executable.
|
||||||
|
|
||||||
|
Install `lake` and `lean` fixed to the version of the `lean-toolchain` file, and
|
||||||
|
run
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
lake build
|
||||||
|
```
|
||||||
|
This builds the executable in `.lake/build/bin/pantograph-repl`.
|
||||||
|
|
||||||
|
## Executable Usage
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
pantograph-repl MODULES|LEAN_OPTIONS
|
||||||
|
```
|
||||||
|
|
||||||
|
The `pantograph-repl` executable must be run with a list of modules to import.
|
||||||
|
It can also accept lean options of the form `--key=value` e.g. `--pp.raw=true`.
|
||||||
|
|
||||||
|
The REPL loop accepts commands as single-line JSON inputs and outputs either an
|
||||||
|
`Error:` (indicating malformed command) or a JSON return value indicating the
|
||||||
|
result of a command execution. The command can be passed in one of two formats
|
||||||
|
```
|
||||||
|
command { ... }
|
||||||
|
{ "cmd": command, "payload": ... }
|
||||||
|
```
|
||||||
|
The list of available commands can be found in `Pantograph/Protocol.lean` and below. An
|
||||||
|
empty command aborts the REPL.
|
||||||
|
|
||||||
|
|
||||||
|
Example: (~5k symbols)
|
||||||
|
```
|
||||||
|
$ pantograph Init
|
||||||
|
env.catalog
|
||||||
|
env.inspect {"name": "Nat.le_add_left"}
|
||||||
|
```
|
||||||
|
Example with `mathlib4` (~90k symbols, may stack overflow, see troubleshooting)
|
||||||
|
```
|
||||||
|
$ pantograph Mathlib.Analysis.Seminorm
|
||||||
|
env.catalog
|
||||||
|
```
|
||||||
|
Example proving a theorem: (alternatively use `goal.start {"copyFrom": "Nat.add_comm"}`) to prime the proof
|
||||||
|
```
|
||||||
|
$ pantograph Init
|
||||||
|
goal.start {"expr": "∀ (n m : Nat), n + m = m + n"}
|
||||||
|
goal.tactic {"stateId": 0, "goalId": 0, "tactic": "intro n m"}
|
||||||
|
goal.tactic {"stateId": 1, "goalId": 0, "tactic": "assumption"}
|
||||||
|
goal.delete {"stateIds": [0]}
|
||||||
|
stat {}
|
||||||
|
goal.tactic {"stateId": 1, "goalId": 0, "tactic": "rw [Nat.add_comm]"}
|
||||||
|
stat
|
||||||
|
```
|
||||||
|
where the application of `assumption` should lead to a failure.
|
||||||
|
|
||||||
|
For a list of commands, see [REPL Documentation](doc/repl.md).
|
||||||
|
|
||||||
|
### Project Environment
|
||||||
|
|
||||||
|
To use Pantograph in a project environment, setup the `LEAN_PATH` environment
|
||||||
|
variable so it contains the library path of lean libraries. The libraries must
|
||||||
|
be built in advance. For example, if `mathlib4` is stored at `../lib/mathlib4`,
|
||||||
|
the environment might be setup like this:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
LIB="../lib"
|
||||||
|
LIB_MATHLIB="$LIB/mathlib4/.lake"
|
||||||
|
export LEAN_PATH="$LIB/mathlib4/build/lib:$LIB_MATHLIB/aesop/build/lib:$LIB_MATHLIB/Qq/build/lib:$LIB_MATHLIB/std/build/lib"
|
||||||
|
|
||||||
|
LEAN_PATH=$LEAN_PATH build/bin/pantograph $@
|
||||||
|
```
|
||||||
|
The `$LEAN_PATH` executable of any project can be extracted by
|
||||||
|
``` sh
|
||||||
|
lake env printenv LEAN_PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
If lean encounters stack overflow problems when printing catalog, execute this before running lean:
|
||||||
```sh
|
```sh
|
||||||
git clone --recurse-submodules <repo-path>
|
ulimit -s unlimited
|
||||||
```
|
|
||||||
3. Install `elan` and `lake`: See [Lean Manual](https://docs.lean-lang.org/lean4/doc/setup.html)
|
|
||||||
4. Execute
|
|
||||||
```sh
|
|
||||||
poetry build
|
|
||||||
poetry install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Library Usage
|
||||||
|
|
||||||
Build the documentations by
|
`Pantograph/Library.lean` exposes a series of interfaces which allow FFI call
|
||||||
```sh
|
with `Pantograph` which mirrors the REPL commands above. It is recommended to
|
||||||
poetry install --only doc
|
call Pantograph via this FFI since it provides a tremendous speed up.
|
||||||
poetry run jupyter-book build docs
|
|
||||||
|
The executable can be used as-is, but linking against the shared library
|
||||||
|
requires the presence of `lean-all`. Note that there isn't a 1-1 correspondence
|
||||||
|
between executable (REPL) commands and library functions.
|
||||||
|
|
||||||
|
Inject any project path via the `pantograph_init_search` function.
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
A Lean development shell is provided in the Nix flake.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
The tests are based on `LSpec`. To run tests, use either
|
||||||
|
``` sh
|
||||||
|
nix flake check
|
||||||
```
|
```
|
||||||
Then serve
|
or
|
||||||
```sh
|
``` sh
|
||||||
cd docs/_build/html
|
lake test
|
||||||
python3 -m http.server -d .
|
|
||||||
```
|
```
|
||||||
|
You can run an individual test by specifying a prefix
|
||||||
|
|
||||||
## Examples
|
``` sh
|
||||||
|
lake test -- "Tactic/No Confuse"
|
||||||
For API interaction examples, see `examples/README.md`. The examples directory
|
|
||||||
also contains a comprehensive Jupyter notebook.
|
|
||||||
|
|
||||||
## Experiments
|
|
||||||
|
|
||||||
In `experiments/`, there are some experiments:
|
|
||||||
1. `minif2f` is an example of executing a `sglang` based prover on the miniF2F dataset
|
|
||||||
2. `dsp` is an Lean implementation of Draft-Sketch-Prove
|
|
||||||
|
|
||||||
The experiments should be run in `poetry shell`. The environment variable
|
|
||||||
`OPENAI_API_KEY` must be set when running experiments calling the OpenAI API.
|
|
||||||
|
|
||||||
## Referencing
|
|
||||||
|
|
||||||
[Paper Link](https://arxiv.org/abs/2410.16429)
|
|
||||||
|
|
||||||
```bib
|
|
||||||
@misc{pantograph,
|
|
||||||
title={Pantograph: A Machine-to-Machine Interaction Interface for Advanced Theorem Proving, High Level Reasoning, and Data Extraction in Lean 4},
|
|
||||||
author={Leni Aniva and Chuyue Sun and Brando Miranda and Clark Barrett and Sanmi Koyejo},
|
|
||||||
year={2024},
|
|
||||||
eprint={2410.16429},
|
|
||||||
archivePrefix={arXiv},
|
|
||||||
primaryClass={cs.LO},
|
|
||||||
url={https://arxiv.org/abs/2410.16429},
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
import Std.Data.HashMap
|
||||||
|
import Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Repl
|
||||||
|
|
||||||
|
structure Context where
|
||||||
|
imports: List String
|
||||||
|
|
||||||
|
/-- Stores state of the REPL -/
|
||||||
|
structure State where
|
||||||
|
options: Protocol.Options := {}
|
||||||
|
nextId: Nat := 0
|
||||||
|
goalStates: Std.HashMap Nat GoalState := Std.HashMap.empty
|
||||||
|
|
||||||
|
/-- Main state monad for executing commands -/
|
||||||
|
abbrev MainM := ReaderT Context (StateT State Lean.CoreM)
|
||||||
|
|
||||||
|
def newGoalState (goalState: GoalState) : MainM Nat := do
|
||||||
|
let state ← get
|
||||||
|
let stateId := state.nextId
|
||||||
|
set { state with
|
||||||
|
goalStates := state.goalStates.insert stateId goalState,
|
||||||
|
nextId := state.nextId + 1
|
||||||
|
}
|
||||||
|
return stateId
|
||||||
|
|
||||||
|
|
||||||
|
-- HACK: For some reason writing `CommandM α := MainM (Except ... α)` disables
|
||||||
|
-- certain monadic features in `MainM`
|
||||||
|
abbrev CR α := Except Protocol.InteractionError α
|
||||||
|
|
||||||
|
def runMetaInMainM { α } (metaM: Lean.MetaM α): MainM α :=
|
||||||
|
metaM.run'
|
||||||
|
def runTermElabInMainM { α } (termElabM: Lean.Elab.TermElabM α) : MainM α :=
|
||||||
|
termElabM.run' (ctx := defaultElabContext) |>.run'
|
||||||
|
|
||||||
|
/-- Main loop command of the REPL -/
|
||||||
|
def execute (command: Protocol.Command): MainM Lean.Json := do
|
||||||
|
let run { α β: Type } [Lean.FromJson α] [Lean.ToJson β] (comm: α → MainM (CR β)): MainM Lean.Json :=
|
||||||
|
match Lean.fromJson? command.payload with
|
||||||
|
| .ok args => do
|
||||||
|
match (← comm args) with
|
||||||
|
| .ok result => return Lean.toJson result
|
||||||
|
| .error ierror => return Lean.toJson ierror
|
||||||
|
| .error error => return Lean.toJson $ errorCommand s!"Unable to parse json: {error}"
|
||||||
|
try
|
||||||
|
match command.cmd with
|
||||||
|
| "reset" => run reset
|
||||||
|
| "stat" => run stat
|
||||||
|
| "expr.echo" => run expr_echo
|
||||||
|
| "env.catalog" => run env_catalog
|
||||||
|
| "env.inspect" => run env_inspect
|
||||||
|
| "env.add" => run env_add
|
||||||
|
| "env.save" => run env_save
|
||||||
|
| "env.load" => run env_load
|
||||||
|
| "options.set" => run options_set
|
||||||
|
| "options.print" => run options_print
|
||||||
|
| "goal.start" => run goal_start
|
||||||
|
| "goal.tactic" => run goal_tactic
|
||||||
|
| "goal.continue" => run goal_continue
|
||||||
|
| "goal.delete" => run goal_delete
|
||||||
|
| "goal.print" => run goal_print
|
||||||
|
| "goal.save" => run goal_save
|
||||||
|
| "goal.load" => run goal_load
|
||||||
|
| "frontend.process" => run frontend_process
|
||||||
|
| cmd =>
|
||||||
|
let error: Protocol.InteractionError :=
|
||||||
|
errorCommand s!"Unknown command {cmd}"
|
||||||
|
return Lean.toJson error
|
||||||
|
catch ex => do
|
||||||
|
let error ← ex.toMessageData.toString
|
||||||
|
return Lean.toJson $ errorIO error
|
||||||
|
where
|
||||||
|
errorCommand := errorI "command"
|
||||||
|
errorIndex := errorI "index"
|
||||||
|
errorIO := errorI "io"
|
||||||
|
-- Command Functions
|
||||||
|
reset (_: Protocol.Reset): MainM (CR Protocol.StatResult) := do
|
||||||
|
let state ← get
|
||||||
|
let nGoals := state.goalStates.size
|
||||||
|
set { state with nextId := 0, goalStates := .empty }
|
||||||
|
Lean.Core.resetMessageLog
|
||||||
|
return .ok { nGoals }
|
||||||
|
stat (_: Protocol.Stat): MainM (CR Protocol.StatResult) := do
|
||||||
|
let state ← get
|
||||||
|
let nGoals := state.goalStates.size
|
||||||
|
return .ok { nGoals }
|
||||||
|
env_catalog (args: Protocol.EnvCatalog): MainM (CR Protocol.EnvCatalogResult) := do
|
||||||
|
let result ← Environment.catalog args
|
||||||
|
return .ok result
|
||||||
|
env_inspect (args: Protocol.EnvInspect): MainM (CR Protocol.EnvInspectResult) := do
|
||||||
|
let state ← get
|
||||||
|
Environment.inspect args state.options
|
||||||
|
env_add (args: Protocol.EnvAdd): MainM (CR Protocol.EnvAddResult) := do
|
||||||
|
Environment.addDecl args
|
||||||
|
env_save (args: Protocol.EnvSaveLoad): MainM (CR Protocol.EnvSaveLoadResult) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
environmentPickle env args.path
|
||||||
|
return .ok {}
|
||||||
|
env_load (args: Protocol.EnvSaveLoad): MainM (CR Protocol.EnvSaveLoadResult) := do
|
||||||
|
let (env, _) ← environmentUnpickle args.path
|
||||||
|
Lean.setEnv env
|
||||||
|
return .ok {}
|
||||||
|
expr_echo (args: Protocol.ExprEcho): MainM (CR Protocol.ExprEchoResult) := do
|
||||||
|
let state ← get
|
||||||
|
exprEcho args.expr (expectedType? := args.type?) (levels := args.levels.getD #[]) (options := state.options)
|
||||||
|
options_set (args: Protocol.OptionsSet): MainM (CR Protocol.OptionsSetResult) := do
|
||||||
|
let state ← get
|
||||||
|
let options := state.options
|
||||||
|
set { state with
|
||||||
|
options := {
|
||||||
|
-- FIXME: This should be replaced with something more elegant
|
||||||
|
printJsonPretty := args.printJsonPretty?.getD options.printJsonPretty,
|
||||||
|
printExprPretty := args.printExprPretty?.getD options.printExprPretty,
|
||||||
|
printExprAST := args.printExprAST?.getD options.printExprAST,
|
||||||
|
printDependentMVars := args.printDependentMVars?.getD options.printDependentMVars,
|
||||||
|
noRepeat := args.noRepeat?.getD options.noRepeat,
|
||||||
|
printAuxDecls := args.printAuxDecls?.getD options.printAuxDecls,
|
||||||
|
printImplementationDetailHyps := args.printImplementationDetailHyps?.getD options.printImplementationDetailHyps
|
||||||
|
automaticMode := args.automaticMode?.getD options.automaticMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .ok { }
|
||||||
|
options_print (_: Protocol.OptionsPrint): MainM (CR Protocol.Options) := do
|
||||||
|
return .ok (← get).options
|
||||||
|
goal_start (args: Protocol.GoalStart): MainM (CR Protocol.GoalStartResult) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let expr?: Except _ GoalState ← runTermElabInMainM (match args.expr, args.copyFrom with
|
||||||
|
| .some expr, .none => goalStartExpr expr (args.levels.getD #[])
|
||||||
|
| .none, .some copyFrom =>
|
||||||
|
(match env.find? <| copyFrom.toName with
|
||||||
|
| .none => return .error <| errorIndex s!"Symbol not found: {copyFrom}"
|
||||||
|
| .some cInfo => return .ok (← GoalState.create cInfo.type))
|
||||||
|
| _, _ =>
|
||||||
|
return .error <| errorI "arguments" "Exactly one of {expr, copyFrom} must be supplied")
|
||||||
|
match expr? with
|
||||||
|
| .error error => return .error error
|
||||||
|
| .ok goalState =>
|
||||||
|
let stateId ← newGoalState goalState
|
||||||
|
return .ok { stateId, root := goalState.root.name.toString }
|
||||||
|
goal_tactic (args: Protocol.GoalTactic): MainM (CR Protocol.GoalTacticResult) := do
|
||||||
|
let state ← get
|
||||||
|
let .some goalState := state.goalStates[args.stateId]? |
|
||||||
|
return .error $ errorIndex s!"Invalid state index {args.stateId}"
|
||||||
|
let .some goal := goalState.goals.get? args.goalId |
|
||||||
|
return .error $ errorIndex s!"Invalid goal index {args.goalId}"
|
||||||
|
let nextGoalState?: Except _ TacticResult ← runTermElabInMainM do
|
||||||
|
-- NOTE: Should probably use a macro to handle this...
|
||||||
|
match args.tactic?, args.expr?, args.have?, args.let?, args.calc?, args.conv?, args.draft? with
|
||||||
|
| .some tactic, .none, .none, .none, .none, .none, .none => do
|
||||||
|
pure <| Except.ok <| ← goalState.tryTactic goal tactic
|
||||||
|
| .none, .some expr, .none, .none, .none, .none, .none => do
|
||||||
|
pure <| Except.ok <| ← goalState.tryAssign goal expr
|
||||||
|
| .none, .none, .some type, .none, .none, .none, .none => do
|
||||||
|
let binderName := args.binderName?.getD ""
|
||||||
|
pure <| Except.ok <| ← goalState.tryHave goal binderName type
|
||||||
|
| .none, .none, .none, .some type, .none, .none, .none => do
|
||||||
|
let binderName := args.binderName?.getD ""
|
||||||
|
pure <| Except.ok <| ← goalState.tryLet goal binderName type
|
||||||
|
| .none, .none, .none, .none, .some pred, .none, .none => do
|
||||||
|
pure <| Except.ok <| ← goalState.tryCalc goal pred
|
||||||
|
| .none, .none, .none, .none, .none, .some true, .none => do
|
||||||
|
pure <| Except.ok <| ← goalState.conv goal
|
||||||
|
| .none, .none, .none, .none, .none, .some false, .none => do
|
||||||
|
pure <| Except.ok <| ← goalState.convExit
|
||||||
|
| .none, .none, .none, .none, .none, .none, .some draft => do
|
||||||
|
pure <| Except.ok <| ← goalState.tryDraft goal draft
|
||||||
|
| _, _, _, _, _, _, _ =>
|
||||||
|
let error := errorI "arguments" "Exactly one of {tactic, expr, have, calc, conv} must be supplied"
|
||||||
|
pure $ Except.error $ error
|
||||||
|
match nextGoalState? with
|
||||||
|
| .error error => return .error error
|
||||||
|
| .ok (.success nextGoalState) => do
|
||||||
|
let nextGoalState ← match state.options.automaticMode, args.conv? with
|
||||||
|
| true, .none => do
|
||||||
|
let .ok result := nextGoalState.resume (nextGoalState.goals ++ goalState.goals) |
|
||||||
|
throwError "Resuming known goals"
|
||||||
|
pure result
|
||||||
|
| true, .some true => pure nextGoalState
|
||||||
|
| true, .some false => do
|
||||||
|
let .some (_, _, dormantGoals) := goalState.convMVar? |
|
||||||
|
throwError "If conv exit succeeded this should not fail"
|
||||||
|
let .ok result := nextGoalState.resume (nextGoalState.goals ++ dormantGoals) |
|
||||||
|
throwError "Resuming known goals"
|
||||||
|
pure result
|
||||||
|
| false, _ => pure nextGoalState
|
||||||
|
let nextStateId ← newGoalState nextGoalState
|
||||||
|
let goals ← nextGoalState.serializeGoals (parent := .some goalState) (options := state.options) |>.run'
|
||||||
|
return .ok {
|
||||||
|
nextStateId? := .some nextStateId,
|
||||||
|
goals? := .some goals,
|
||||||
|
}
|
||||||
|
| .ok (.parseError message) =>
|
||||||
|
return .ok { parseError? := .some message }
|
||||||
|
| .ok (.invalidAction message) =>
|
||||||
|
return .error $ errorI "invalid" message
|
||||||
|
| .ok (.failure messages) =>
|
||||||
|
return .ok { tacticErrors? := .some messages }
|
||||||
|
goal_continue (args: Protocol.GoalContinue): MainM (CR Protocol.GoalContinueResult) := do
|
||||||
|
let state ← get
|
||||||
|
let .some target := state.goalStates[args.target]? |
|
||||||
|
return .error $ errorIndex s!"Invalid state index {args.target}"
|
||||||
|
let nextState? ← match args.branch?, args.goals? with
|
||||||
|
| .some branchId, .none => do
|
||||||
|
match state.goalStates[branchId]? with
|
||||||
|
| .none => return .error $ errorIndex s!"Invalid state index {branchId}"
|
||||||
|
| .some branch => pure $ target.continue branch
|
||||||
|
| .none, .some goals =>
|
||||||
|
pure $ goalResume target goals
|
||||||
|
| _, _ => return .error <| errorI "arguments" "Exactly one of {branch, goals} must be supplied"
|
||||||
|
match nextState? with
|
||||||
|
| .error error => return .error <| errorI "structure" error
|
||||||
|
| .ok nextGoalState =>
|
||||||
|
let nextStateId ← newGoalState nextGoalState
|
||||||
|
let goals ← goalSerialize nextGoalState (options := state.options)
|
||||||
|
return .ok {
|
||||||
|
nextStateId,
|
||||||
|
goals,
|
||||||
|
}
|
||||||
|
goal_delete (args: Protocol.GoalDelete): MainM (CR Protocol.GoalDeleteResult) := do
|
||||||
|
let state ← get
|
||||||
|
let goalStates := args.stateIds.foldl (λ map id => map.erase id) state.goalStates
|
||||||
|
set { state with goalStates }
|
||||||
|
return .ok {}
|
||||||
|
goal_print (args: Protocol.GoalPrint): MainM (CR Protocol.GoalPrintResult) := do
|
||||||
|
let state ← get
|
||||||
|
let .some goalState := state.goalStates[args.stateId]? |
|
||||||
|
return .error $ errorIndex s!"Invalid state index {args.stateId}"
|
||||||
|
let result ← runMetaInMainM <| goalPrint
|
||||||
|
goalState
|
||||||
|
(rootExpr := args.rootExpr?.getD False)
|
||||||
|
(parentExpr := args.parentExpr?.getD False)
|
||||||
|
(goals := args.goals?.getD False)
|
||||||
|
(extraMVars := args.extraMVars?.getD #[])
|
||||||
|
(options := state.options)
|
||||||
|
return .ok result
|
||||||
|
goal_save (args: Protocol.GoalSave): MainM (CR Protocol.GoalSaveResult) := do
|
||||||
|
let state ← get
|
||||||
|
let .some goalState := state.goalStates[args.id]? |
|
||||||
|
return .error $ errorIndex s!"Invalid state index {args.id}"
|
||||||
|
goalStatePickle goalState args.path
|
||||||
|
return .ok {}
|
||||||
|
goal_load (args: Protocol.GoalLoad): MainM (CR Protocol.GoalLoadResult) := do
|
||||||
|
let (goalState, _) ← goalStateUnpickle args.path (← Lean.MonadEnv.getEnv)
|
||||||
|
let id ← newGoalState goalState
|
||||||
|
return .ok { id }
|
||||||
|
frontend_process (args: Protocol.FrontendProcess): MainM (CR Protocol.FrontendProcessResult) := do
|
||||||
|
let options := (← get).options
|
||||||
|
try
|
||||||
|
let (fileName, file) ← match args.fileName?, args.file? with
|
||||||
|
| .some fileName, .none => do
|
||||||
|
let file ← IO.FS.readFile fileName
|
||||||
|
pure (fileName, file)
|
||||||
|
| .none, .some file =>
|
||||||
|
pure ("<anonymous>", file)
|
||||||
|
| _, _ => return .error <| errorI "arguments" "Exactly one of {fileName, file} must be supplied"
|
||||||
|
let env?: Option Lean.Environment ← if args.fileName?.isSome then
|
||||||
|
pure .none
|
||||||
|
else do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
pure <| .some env
|
||||||
|
let (context, state) ← do Frontend.createContextStateFromFile file fileName env? {}
|
||||||
|
let frontendM := Frontend.mapCompilationSteps λ step => do
|
||||||
|
let boundary := (step.src.startPos.byteIdx, step.src.stopPos.byteIdx)
|
||||||
|
let invocations?: Option (List Protocol.InvokedTactic) ← if args.invocations then
|
||||||
|
let invocations ← Frontend.collectTacticsFromCompilationStep step
|
||||||
|
pure $ .some invocations
|
||||||
|
else
|
||||||
|
pure .none
|
||||||
|
let sorrys ← if args.sorrys then
|
||||||
|
Frontend.collectSorrys step (options := { collectTypeErrors := args.typeErrorsAsGoals })
|
||||||
|
else
|
||||||
|
pure []
|
||||||
|
let messages ← step.messageStrings
|
||||||
|
let newConstants ← if args.newConstants then
|
||||||
|
Frontend.collectNewDefinedConstants step
|
||||||
|
else
|
||||||
|
pure []
|
||||||
|
return (step.before, boundary, invocations?, sorrys, messages, newConstants)
|
||||||
|
let li ← frontendM.run context |>.run' state
|
||||||
|
let units ← li.mapM λ (env, boundary, invocations?, sorrys, messages, newConstants) => Lean.withEnv env do
|
||||||
|
let newConstants? := if args.newConstants then
|
||||||
|
.some $ newConstants.toArray.map λ name => name.toString
|
||||||
|
else
|
||||||
|
.none
|
||||||
|
let (goalStateId?, goals?, goalSrcBoundaries?) ← if sorrys.isEmpty then do
|
||||||
|
pure (.none, .none, .none)
|
||||||
|
else do
|
||||||
|
let { state, srcBoundaries } ← runMetaInMainM $ Frontend.sorrysToGoalState sorrys
|
||||||
|
let stateId ← newGoalState state
|
||||||
|
let goals ← goalSerialize state options
|
||||||
|
let srcBoundaries := srcBoundaries.toArray.map (λ (b, e) => (b.byteIdx, e.byteIdx))
|
||||||
|
pure (.some stateId, .some goals, .some srcBoundaries)
|
||||||
|
return {
|
||||||
|
boundary,
|
||||||
|
messages,
|
||||||
|
invocations?,
|
||||||
|
goalStateId?,
|
||||||
|
goals?,
|
||||||
|
goalSrcBoundaries?,
|
||||||
|
newConstants?,
|
||||||
|
}
|
||||||
|
return .ok { units }
|
||||||
|
catch e =>
|
||||||
|
return .error $ errorI "frontend" (← e.toMessageData.toString)
|
||||||
|
|
||||||
|
end Pantograph.Repl
|
150
SNAP.md
150
SNAP.md
|
@ -1,150 +0,0 @@
|
||||||
# Instructions for the SNAP Cluster
|
|
||||||
|
|
||||||
Brando's [self-contained `install.sh` script for
|
|
||||||
lean](https://github.com/brando90/learning_lean/blob/main/install.sh). (Warning:
|
|
||||||
The Lean version in the script is outdated.)
|
|
||||||
|
|
||||||
## Install 1: With Conda and Pip in the SNAP cluster
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# - Install Lean 4 manually (elan & lake): gets install script (doesn't save it) & directly gives it to sh to install it
|
|
||||||
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s -- -y
|
|
||||||
# (install command from the Lean4 official instlal guide, not the one we use)
|
|
||||||
# curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s
|
|
||||||
|
|
||||||
# - Make sure Lean4 tools (lean, lake) are available
|
|
||||||
echo $PATH | tr ':' '\n'
|
|
||||||
export PATH="$HOME/.elan/bin:$PATH"
|
|
||||||
echo 'export PATH="$HOME/.elan/bin:$PATH"' >> ~/.bashrc
|
|
||||||
# bash
|
|
||||||
elan
|
|
||||||
lake
|
|
||||||
|
|
||||||
# - Create and activate the right python env (this is needed so that poetry build works)
|
|
||||||
conda create -n pypantograph_env python=3.11 -y
|
|
||||||
conda activate pypantograph_env
|
|
||||||
#conda remove --name pypantograph_env --all
|
|
||||||
|
|
||||||
# - Install poetry with python venv (needs seperate install so poetry & your projs deps don't crash)
|
|
||||||
mkdir $HOME/.virtualenvs
|
|
||||||
|
|
||||||
# put the follow BEFORE your conda init stuff in your .bashrc
|
|
||||||
export VENV_PATH=$HOME/.virtualenvs/venv_for_poetry
|
|
||||||
export PATH="$VENV_PATH/bin:$PATH"
|
|
||||||
|
|
||||||
# now actually install poetry in a python env after creating an python env for poetry with venv
|
|
||||||
python3 -m venv $VENV_PATH
|
|
||||||
$VENV_PATH/bin/pip install -U pip setuptools
|
|
||||||
$VENV_PATH/bin/pip install poetry
|
|
||||||
|
|
||||||
poetry
|
|
||||||
|
|
||||||
# - Init the git submodules (i.e., make git aware of them/track them) + fetch/clone/update (and double check submodule is inited)
|
|
||||||
git submodule init
|
|
||||||
git submodule update --init
|
|
||||||
|
|
||||||
# - For snap make sure the repo is sym linked you're using your
|
|
||||||
git clone git@github.com:lenianiva/PyPantograph.git
|
|
||||||
# git checkout <your-branch>
|
|
||||||
git checkout brando
|
|
||||||
ln -s $AFS/PyPantograph $HOME/PyPantograph
|
|
||||||
|
|
||||||
# - Build the PyPantograph proj (build the py distribution, py deps and custom (lean4) installs). Note: pip install -e doesn't work on the dist .whl builds etc so you instead the next command
|
|
||||||
cd $HOME/PyPantograph
|
|
||||||
poetry build
|
|
||||||
|
|
||||||
```
|
|
||||||
To run server tests:
|
|
||||||
``` bash
|
|
||||||
python -m pantograph.server
|
|
||||||
python -m pantograph.search
|
|
||||||
```
|
|
||||||
The tests in `pantograph/server.py` also serve as simple interaction examples
|
|
||||||
|
|
||||||
|
|
||||||
# - Install pypantograph in editable mode (only pyproject.toml (or setup.py!) needed! Assuming your at the proj root)
|
|
||||||
cd $HOME/PyPantograph
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# - Confirm intalls
|
|
||||||
pip list | grep pantograph
|
|
||||||
pip list | grep vllm
|
|
||||||
pip list | grep torch
|
|
||||||
|
|
||||||
# - Make sure the PyPantrograph server tests by Leni work
|
|
||||||
cd ~/PyPantograph
|
|
||||||
python $HOME/PyPantograph/pantograph/server.py
|
|
||||||
python $HOME/PyPantograph/test_vllm.py
|
|
||||||
```
|
|
||||||
Note: the tests in `pantograph/server.py` also serve as simple interaction examples
|
|
||||||
|
|
||||||
References:
|
|
||||||
- My SNAP `.bashrc`: https://github.com/brando90/snap-cluster-setup/blob/main/.bashrc
|
|
||||||
- Especially useful for Conda vs Poetry export order
|
|
||||||
- Poetry in SNAP: https://github.com/brando90/snap-cluster-setup?tab=readme-ov-file#poetry
|
|
||||||
- Gitsubmodules: https://github.com/brando90/snap-cluster-setup?tab=readme-ov-file#git-submodules
|
|
||||||
- Lean in SNAP: https://github.com/brando90/snap-cluster-setup?tab=readme-ov-file#lean-in-snap
|
|
||||||
- ChatGPT: https://chat.openai.com/c/e01336a7-6f67-4cd2-b6cd-09b8ee8aef5a
|
|
||||||
|
|
||||||
# Install 2: With only Poetry in the SNAP cluster
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# - Install Lean4 manually (elan and lake), 1st one is the SNAP one, 2nd is the most common one
|
|
||||||
curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s -- -y
|
|
||||||
# curl -sSf https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh | sh -s
|
|
||||||
|
|
||||||
# - Make sure Lean4 tools (lean, lake) are available
|
|
||||||
export PATH="$HOME/.elan/bin:$PATH"
|
|
||||||
echo 'export PATH="$HOME/.elan/bin:$PATH"' >> ~/.bashrc
|
|
||||||
bash
|
|
||||||
elan
|
|
||||||
lake
|
|
||||||
|
|
||||||
# - Init the git submodules (i.e., make git aware of them/track them) + fetch/clone/update (and double check submodule is inited)
|
|
||||||
git submodule init
|
|
||||||
git submodule update --init --recursive
|
|
||||||
git clone git@github.com:lenianiva/PyPantograph.git --recurse-submodules
|
|
||||||
|
|
||||||
# - For snap make sure the repo is sym linked you're using your
|
|
||||||
git clone git@github.com:lenianiva/PyPantograph.git
|
|
||||||
git checkout <your-branch>
|
|
||||||
ln -s $AFS/PyPantograph $HOME/PyPantograph
|
|
||||||
|
|
||||||
# - Install poetry with python venv (needs seperate install so poetry & your projs deps don't crash)
|
|
||||||
mkdir $HOME/.virtualenvs
|
|
||||||
|
|
||||||
# - Put the follow BEFORE your conda init stuff in your .bashrc
|
|
||||||
export VENV_PATH=$HOME/.virtualenvs/venv_for_poetry
|
|
||||||
export PATH="$VENV_PATH/bin:$PATH"
|
|
||||||
|
|
||||||
# - Now actually install poetry in a python env after creating an python env for poetry with venv
|
|
||||||
python3 -m venv $VENV_PATH
|
|
||||||
$VENV_PATH/bin/pip install -U pip setuptools
|
|
||||||
$VENV_PATH/bin/pip install poetry
|
|
||||||
|
|
||||||
poetry
|
|
||||||
|
|
||||||
# poetry build is only needed when you build a python distribution e.g., .whl or .tar.gz and want to distribute it. You can't use those files for edtiable development anyway
|
|
||||||
# # - Build the PyPantograph proj (build the py distribution, py deps and custom (lean4) installs)
|
|
||||||
# cd $HOME/PyPantograph
|
|
||||||
# poetry build
|
|
||||||
|
|
||||||
# - Install pypantograph in editable mode with poetry
|
|
||||||
cd $HOME/PyPantograph
|
|
||||||
#Installs the project and its dependencies into the virtual environment, creating the environment if it doesn't exist, in editable mode. This will run our custom build for Lean already (the build.py file!)
|
|
||||||
poetry install
|
|
||||||
# if it create a new python env, check it out
|
|
||||||
poetry env list
|
|
||||||
# activate the current poetry env in a new shell
|
|
||||||
poetry shell
|
|
||||||
|
|
||||||
# - Confirm intalls
|
|
||||||
# poetry show | grep pantograph # note, doesn't do anything since poetry already only works by installing things in editable mode
|
|
||||||
poetry show | grep vllm
|
|
||||||
poetry show | grep torch
|
|
||||||
|
|
||||||
# - Make sure the PyPantrograph server tests by Leni work
|
|
||||||
cd ~/PyPantograph
|
|
||||||
python -m pantograph.server
|
|
||||||
# python $HOME/PyPantograph/pantograph/server.py
|
|
||||||
# python $HOME/PyPantograph/test_vllm.py
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Library
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Lean
|
||||||
|
import LSpec
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
deriving instance Repr for Expr
|
||||||
|
-- Use strict equality check for expressions
|
||||||
|
instance : BEq Expr := ⟨Expr.equal⟩
|
||||||
|
|
||||||
|
def uniq (n: Nat): Name := .num (.str .anonymous "_uniq") n
|
||||||
|
|
||||||
|
-- Auxiliary functions
|
||||||
|
namespace Protocol
|
||||||
|
def Goal.devolatilizeVars (goal: Goal): Goal :=
|
||||||
|
{
|
||||||
|
goal with
|
||||||
|
vars := goal.vars.map removeInternalAux,
|
||||||
|
|
||||||
|
}
|
||||||
|
where removeInternalAux (v: Variable): Variable :=
|
||||||
|
{
|
||||||
|
v with
|
||||||
|
name := ""
|
||||||
|
}
|
||||||
|
/-- Set internal names to "" -/
|
||||||
|
def Goal.devolatilize (goal: Goal): Goal :=
|
||||||
|
{
|
||||||
|
goal.devolatilizeVars with
|
||||||
|
name := "",
|
||||||
|
}
|
||||||
|
|
||||||
|
deriving instance DecidableEq, Repr for Name
|
||||||
|
deriving instance DecidableEq, Repr for Expression
|
||||||
|
deriving instance DecidableEq, Repr for Variable
|
||||||
|
deriving instance DecidableEq, Repr for Goal
|
||||||
|
deriving instance DecidableEq, Repr for ExprEchoResult
|
||||||
|
deriving instance DecidableEq, Repr for InteractionError
|
||||||
|
deriving instance DecidableEq, Repr for Option
|
||||||
|
end Protocol
|
||||||
|
|
||||||
|
namespace Condensed
|
||||||
|
|
||||||
|
deriving instance BEq, Repr for LocalDecl
|
||||||
|
deriving instance BEq, Repr for Goal
|
||||||
|
|
||||||
|
-- Enable string interpolation
|
||||||
|
instance : ToString FVarId where
|
||||||
|
toString id := id.name.toString
|
||||||
|
instance : ToString MVarId where
|
||||||
|
toString id := id.name.toString
|
||||||
|
|
||||||
|
protected def LocalDecl.devolatilize (decl: LocalDecl): LocalDecl :=
|
||||||
|
{
|
||||||
|
decl with fvarId := { name := .anonymous }
|
||||||
|
}
|
||||||
|
protected def Goal.devolatilize (goal: Goal): Goal :=
|
||||||
|
{
|
||||||
|
goal with
|
||||||
|
mvarId := { name := .anonymous },
|
||||||
|
context := goal.context.map LocalDecl.devolatilize
|
||||||
|
}
|
||||||
|
|
||||||
|
end Condensed
|
||||||
|
|
||||||
|
def GoalState.get! (state: GoalState) (i: Nat): MVarId := state.goals.get! i
|
||||||
|
def GoalState.tacticOn (state: GoalState) (goalId: Nat) (tactic: String) := state.tryTactic (state.goals.get! goalId) tactic
|
||||||
|
|
||||||
|
def TacticResult.toString : TacticResult → String
|
||||||
|
| .success state => s!".success ({state.goals.length} goals)"
|
||||||
|
| .failure messages =>
|
||||||
|
let messages := "\n".intercalate messages.toList
|
||||||
|
s!".failure {messages}"
|
||||||
|
| .parseError error => s!".parseError {error}"
|
||||||
|
| .invalidAction error => s!".invalidAction {error}"
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
|
||||||
|
def expectationFailure (desc: String) (error: String): LSpec.TestSeq := LSpec.test desc (LSpec.ExpectationFailure "ok _" error)
|
||||||
|
def assertUnreachable (message: String): LSpec.TestSeq := LSpec.check message false
|
||||||
|
|
||||||
|
def parseFailure (error: String) := expectationFailure "parse" error
|
||||||
|
def elabFailure (error: String) := expectationFailure "elab" error
|
||||||
|
|
||||||
|
def runCoreMSeq (env: Environment) (coreM: CoreM LSpec.TestSeq) (options: Array String := #[]): IO LSpec.TestSeq := do
|
||||||
|
let coreContext: Core.Context ← createCoreContext options
|
||||||
|
match ← (coreM.run' coreContext { env := env }).toBaseIO with
|
||||||
|
| .error exception =>
|
||||||
|
return LSpec.test "Exception" (s!"internal exception #{← exception.toMessageData.toString}" = "")
|
||||||
|
| .ok a => return a
|
||||||
|
def runMetaMSeq (env: Environment) (metaM: MetaM LSpec.TestSeq): IO LSpec.TestSeq :=
|
||||||
|
runCoreMSeq env metaM.run'
|
||||||
|
def runTermElabMInMeta { α } (termElabM: Lean.Elab.TermElabM α): Lean.MetaM α :=
|
||||||
|
termElabM.run' (ctx := defaultElabContext)
|
||||||
|
def runTermElabMSeq (env: Environment) (termElabM: Elab.TermElabM LSpec.TestSeq): IO LSpec.TestSeq :=
|
||||||
|
runMetaMSeq env $ termElabM.run' (ctx := defaultElabContext)
|
||||||
|
|
||||||
|
def exprToStr (e: Expr): Lean.MetaM String := toString <$> Meta.ppExpr e
|
||||||
|
|
||||||
|
def strToTermSyntax (s: String): CoreM Syntax := do
|
||||||
|
let .ok stx := Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := s)
|
||||||
|
(fileName := ← getFileName) | panic! s!"Failed to parse {s}"
|
||||||
|
return stx
|
||||||
|
def parseSentence (s: String): Elab.TermElabM Expr := do
|
||||||
|
let stx ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := s)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
Elab.Term.elabTerm (stx := stx) .none
|
||||||
|
|
||||||
|
def runTacticOnMVar (tacticM: Elab.Tactic.TacticM Unit) (goal: MVarId): Elab.TermElabM (List MVarId) := do
|
||||||
|
let (_, newGoals) ← tacticM { elaborator := .anonymous } |>.run { goals := [goal] }
|
||||||
|
return newGoals.goals
|
||||||
|
def mvarUserNameAndType (mvarId: MVarId): MetaM (Name × String) := do
|
||||||
|
let name := (← mvarId.getDecl).userName
|
||||||
|
let t ← exprToStr (← mvarId.getType)
|
||||||
|
return (name, t)
|
||||||
|
|
||||||
|
|
||||||
|
-- Monadic testing
|
||||||
|
|
||||||
|
abbrev TestT := StateRefT' IO.RealWorld LSpec.TestSeq
|
||||||
|
|
||||||
|
section Monadic
|
||||||
|
|
||||||
|
variable [Monad m] [MonadLiftT (ST IO.RealWorld) m]
|
||||||
|
|
||||||
|
def addTest (test: LSpec.TestSeq) : TestT m Unit := do
|
||||||
|
set $ (← get) ++ test
|
||||||
|
|
||||||
|
def checkEq [DecidableEq α] [Repr α] (desc : String) (lhs rhs : α) : TestT m Unit := do
|
||||||
|
addTest $ LSpec.check desc (lhs = rhs)
|
||||||
|
def checkTrue (desc : String) (flag : Bool) : TestT m Unit := do
|
||||||
|
addTest $ LSpec.check desc flag
|
||||||
|
def fail (desc : String) : TestT m Unit := do
|
||||||
|
addTest $ LSpec.check desc false
|
||||||
|
|
||||||
|
def runTest (t: TestT m Unit): m LSpec.TestSeq :=
|
||||||
|
Prod.snd <$> t.run LSpec.TestSeq.done
|
||||||
|
def runTestWithResult { α } (t: TestT m α): m (α × LSpec.TestSeq) :=
|
||||||
|
t.run LSpec.TestSeq.done
|
||||||
|
def runTestCoreM (env: Environment) (coreM: TestT CoreM Unit) (options: Array String := #[]): IO LSpec.TestSeq := do
|
||||||
|
runCoreMSeq env (runTest coreM) options
|
||||||
|
|
||||||
|
end Monadic
|
||||||
|
|
||||||
|
def runTestTermElabM (env: Environment) (t: TestT Elab.TermElabM Unit):
|
||||||
|
IO LSpec.TestSeq :=
|
||||||
|
runTermElabMSeq env $ runTest t
|
||||||
|
|
||||||
|
def cdeclOf (userName: Name) (type: Expr): Condensed.LocalDecl :=
|
||||||
|
{ userName, type }
|
||||||
|
|
||||||
|
def buildGoal (nameType: List (String × String)) (target: String) (userName?: Option String := .none):
|
||||||
|
Protocol.Goal :=
|
||||||
|
{
|
||||||
|
userName?,
|
||||||
|
target := { pp? := .some target},
|
||||||
|
vars := (nameType.map fun x => ({
|
||||||
|
userName := x.fst,
|
||||||
|
type? := .some { pp? := .some x.snd },
|
||||||
|
})).toArray
|
||||||
|
}
|
||||||
|
|
||||||
|
end Test
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,109 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
namespace Pantograph.Test.Delate
|
||||||
|
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
deriving instance Repr, DecidableEq for Protocol.BoundExpression
|
||||||
|
|
||||||
|
def test_serializeName: LSpec.TestSeq :=
|
||||||
|
let quote := "\""
|
||||||
|
let escape := "\\"
|
||||||
|
LSpec.test "a.b.1" (serializeName (Name.num (.str (.str .anonymous "a") "b") 1) = "a.b.1") ++
|
||||||
|
LSpec.test "seg.«a.b»" (serializeName (Name.str (.str .anonymous "seg") "a.b") = s!"{quote}seg.«a.b»{quote}") ++
|
||||||
|
-- Pathological test case
|
||||||
|
LSpec.test s!"«̈{escape}{quote}»" (serializeName (Name.str .anonymous s!"{escape}{quote}") = s!"{quote}«{escape}{quote}»{quote}")
|
||||||
|
|
||||||
|
def test_expr_to_binder (env: Environment): IO LSpec.TestSeq := do
|
||||||
|
let entries: List (Name × Protocol.BoundExpression) := [
|
||||||
|
("Nat.add_comm".toName, { binders := #[("n", "Nat"), ("m", "Nat")], target := "n + m = m + n" }),
|
||||||
|
("Nat.le_of_succ_le".toName, { binders := #[("n", "Nat"), ("m", "Nat"), ("h", "n.succ ≤ m")], target := "n ≤ m" })
|
||||||
|
]
|
||||||
|
runCoreMSeq env $ entries.foldlM (λ suites (symbol, target) => do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let expr := env.find? symbol |>.get! |>.type
|
||||||
|
let test := LSpec.check symbol.toString ((← typeExprToBound expr) = target)
|
||||||
|
return LSpec.TestSeq.append suites test) LSpec.TestSeq.done |>.run'
|
||||||
|
|
||||||
|
def test_sexp_of_symbol (env: Environment): IO LSpec.TestSeq := do
|
||||||
|
let entries: List (String × String) := [
|
||||||
|
-- This one contains unhygienic variable names which must be suppressed
|
||||||
|
("Nat.add", "(:forall a (:c Nat) (:forall a (:c Nat) (:c Nat)))"),
|
||||||
|
-- These ones are normal and easy
|
||||||
|
("Nat.add_one", "(:forall n (:c Nat) ((:c Eq) (:c Nat) ((:c HAdd.hAdd) (:c Nat) (:c Nat) (:c Nat) ((:c instHAdd) (:c Nat) (:c instAddNat)) 0 ((:c OfNat.ofNat) (:c Nat) (:lit 1) ((:c instOfNatNat) (:lit 1)))) ((:c Nat.succ) 0)))"),
|
||||||
|
("Nat.le_of_succ_le", "(:forall n (:c Nat) (:forall m (:c Nat) (:forall h ((:c LE.le) (:c Nat) (:c instLENat) ((:c Nat.succ) 1) 0) ((:c LE.le) (:c Nat) (:c instLENat) 2 1)) :i) :i)"),
|
||||||
|
-- Handling of higher order types
|
||||||
|
("Or", "(:forall a (:sort 0) (:forall b (:sort 0) (:sort 0)))"),
|
||||||
|
("List", "(:forall α (:sort (+ u 1)) (:sort (+ u 1)))")
|
||||||
|
]
|
||||||
|
runMetaMSeq env $ entries.foldlM (λ suites (symbol, target) => do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let expr := env.find? symbol.toName |>.get! |>.type
|
||||||
|
let test := LSpec.check symbol ((← serializeExpressionSexp expr) = target)
|
||||||
|
return LSpec.TestSeq.append suites test) LSpec.TestSeq.done
|
||||||
|
|
||||||
|
def test_sexp_of_elab (env: Environment): IO LSpec.TestSeq := do
|
||||||
|
let entries: List (String × (List Name) × String) := [
|
||||||
|
("λ x: Nat × Bool => x.1", [], "(:lambda x ((:c Prod) (:c Nat) (:c Bool)) ((:c Prod.fst) (:c Nat) (:c Bool) 0))"),
|
||||||
|
("λ x: Array Nat => x.data", [], "(:lambda x ((:c Array) (:c Nat)) ((:c Array.data) (:c Nat) 0))"),
|
||||||
|
("λ {α: Sort (u + 1)} => List α", [`u], "(:lambda α (:sort (+ u 1)) ((:c List) 0) :i)"),
|
||||||
|
("λ {α} => List α", [], "(:lambda α (:sort (+ (:mv _uniq.4) 1)) ((:c List) 0) :i)"),
|
||||||
|
("(2: Nat) <= (5: Nat)", [], "((:c LE.le) (:mv _uniq.18) (:mv _uniq.19) ((:c OfNat.ofNat) (:mv _uniq.4) (:lit 2) (:mv _uniq.5)) ((:c OfNat.ofNat) (:mv _uniq.14) (:lit 5) (:mv _uniq.15)))"),
|
||||||
|
]
|
||||||
|
entries.foldlM (λ suites (source, levels, target) =>
|
||||||
|
let termElabM := do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let s ← match parseTerm env source with
|
||||||
|
| .ok s => pure s
|
||||||
|
| .error e => return parseFailure e
|
||||||
|
let expr ← match (← elabTerm s) with
|
||||||
|
| .ok expr => pure expr
|
||||||
|
| .error e => return elabFailure e
|
||||||
|
return LSpec.check source ((← serializeExpressionSexp expr) = target)
|
||||||
|
let metaM := (Elab.Term.withLevelNames levels termElabM).run' (ctx := defaultElabContext)
|
||||||
|
return LSpec.TestSeq.append suites (← runMetaMSeq env metaM))
|
||||||
|
LSpec.TestSeq.done
|
||||||
|
|
||||||
|
def test_sexp_of_expr (env: Environment): IO LSpec.TestSeq := do
|
||||||
|
let entries: List (Expr × String) := [
|
||||||
|
(.lam `p (.sort .zero)
|
||||||
|
(.lam `q (.sort .zero)
|
||||||
|
(.lam `k (mkApp2 (.const `And []) (.bvar 1) (.bvar 0))
|
||||||
|
(.proj `And 1 (.bvar 0))
|
||||||
|
.default)
|
||||||
|
.implicit)
|
||||||
|
.implicit,
|
||||||
|
"(:lambda p (:sort 0) (:lambda q (:sort 0) (:lambda k ((:c And) 1 0) ((:c And.right) _ _ 0)) :i) :i)"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
let termElabM: Elab.TermElabM LSpec.TestSeq := entries.foldlM (λ suites (expr, target) => do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let testCaseName := target.take 10
|
||||||
|
let test := LSpec.check testCaseName ((← serializeExpressionSexp expr) = target)
|
||||||
|
return LSpec.TestSeq.append suites test) LSpec.TestSeq.done
|
||||||
|
runMetaMSeq env $ termElabM.run' (ctx := defaultElabContext)
|
||||||
|
|
||||||
|
-- Instance parsing
|
||||||
|
def test_instance (env: Environment): IO LSpec.TestSeq :=
|
||||||
|
runMetaMSeq env do
|
||||||
|
let env ← MonadEnv.getEnv
|
||||||
|
let source := "λ x y: Nat => HAdd.hAdd Nat Nat Nat (instHAdd Nat instAddNat) x y"
|
||||||
|
let s := parseTerm env source |>.toOption |>.get!
|
||||||
|
let _expr := (← runTermElabMInMeta <| elabTerm s) |>.toOption |>.get!
|
||||||
|
return LSpec.TestSeq.done
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("serializeName", do pure test_serializeName),
|
||||||
|
("Expression binder", test_expr_to_binder env),
|
||||||
|
("Sexp from symbol", test_sexp_of_symbol env),
|
||||||
|
("Sexp from elaborated expr", test_sexp_of_elab env),
|
||||||
|
("Sexp from expr", test_sexp_of_expr env),
|
||||||
|
("Instance", test_instance env),
|
||||||
|
]
|
||||||
|
|
||||||
|
end Pantograph.Test.Delate
|
|
@ -0,0 +1,122 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Pantograph.Environment
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
namespace Pantograph.Test.Environment
|
||||||
|
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
deriving instance DecidableEq, Repr for Protocol.InductInfo
|
||||||
|
deriving instance DecidableEq, Repr for Protocol.ConstructorInfo
|
||||||
|
deriving instance DecidableEq, Repr for Protocol.RecursorRule
|
||||||
|
deriving instance DecidableEq, Repr for Protocol.RecursorInfo
|
||||||
|
deriving instance DecidableEq, Repr for Protocol.EnvInspectResult
|
||||||
|
|
||||||
|
def test_catalog: IO LSpec.TestSeq := do
|
||||||
|
let env: Environment ← importModules
|
||||||
|
(imports := #[`Init])
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
let inner: CoreM LSpec.TestSeq := do
|
||||||
|
let catalogResult ← Environment.catalog {}
|
||||||
|
let symbolsWithNum := env.constants.fold (init := #[]) (λ acc name info =>
|
||||||
|
match (Environment.toFilteredSymbol name info).isSome && (name matches .num _ _) with
|
||||||
|
| false => acc
|
||||||
|
| true => acc.push name
|
||||||
|
)
|
||||||
|
return LSpec.check "No num symbols" (symbolsWithNum.size == 0)
|
||||||
|
runCoreMSeq env inner
|
||||||
|
|
||||||
|
def test_symbol_visibility: IO LSpec.TestSeq := do
|
||||||
|
let entries: List (Name × Bool) := [
|
||||||
|
("Nat.add_comm".toName, false),
|
||||||
|
("foo.bla.Init.Data.List.Basic.2.1.Init.Lean.Expr._hyg.4".toName, true),
|
||||||
|
("Init.Data.Nat.Basic._auxLemma.4".toName, true),
|
||||||
|
]
|
||||||
|
let suite := entries.foldl (λ suites (symbol, target) =>
|
||||||
|
let test := LSpec.check symbol.toString ((Environment.isNameInternal symbol) == target)
|
||||||
|
LSpec.TestSeq.append suites test) LSpec.TestSeq.done
|
||||||
|
return suite
|
||||||
|
|
||||||
|
inductive ConstantCat where
|
||||||
|
| induct (info: Protocol.InductInfo)
|
||||||
|
| ctor (info: Protocol.ConstructorInfo)
|
||||||
|
| recursor (info: Protocol.RecursorInfo)
|
||||||
|
|
||||||
|
def test_inspect: IO LSpec.TestSeq := do
|
||||||
|
let env: Environment ← importModules
|
||||||
|
(imports := #[`Init])
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
let testCases: List (String × ConstantCat) := [
|
||||||
|
("Or", ConstantCat.induct {
|
||||||
|
numParams := 2,
|
||||||
|
numIndices := 0,
|
||||||
|
all := #["Or"],
|
||||||
|
ctors := #["Or.inl", "Or.inr"],
|
||||||
|
}),
|
||||||
|
("Except.ok", ConstantCat.ctor {
|
||||||
|
induct := "Except",
|
||||||
|
cidx := 1,
|
||||||
|
numParams := 2,
|
||||||
|
numFields := 1,
|
||||||
|
}),
|
||||||
|
("Eq.rec", ConstantCat.recursor {
|
||||||
|
all := #["Eq"],
|
||||||
|
numParams := 2,
|
||||||
|
numIndices := 1,
|
||||||
|
numMotives := 1,
|
||||||
|
numMinors := 1,
|
||||||
|
rules := #[{ ctor := "Eq.refl", nFields := 0, rhs := { pp? := .some "fun {α} a motive refl => refl" } }]
|
||||||
|
k := true,
|
||||||
|
}),
|
||||||
|
("ForM.rec", ConstantCat.recursor {
|
||||||
|
all := #["ForM"],
|
||||||
|
numParams := 3,
|
||||||
|
numIndices := 0,
|
||||||
|
numMotives := 1,
|
||||||
|
numMinors := 1,
|
||||||
|
rules := #[{ ctor := "ForM.mk", nFields := 1, rhs := { pp? := .some "fun m γ α motive mk forM => mk forM" } }]
|
||||||
|
k := false,
|
||||||
|
})
|
||||||
|
]
|
||||||
|
let inner: CoreM LSpec.TestSeq := do
|
||||||
|
testCases.foldlM (λ acc (name, cat) => do
|
||||||
|
let args: Protocol.EnvInspect := { name := name }
|
||||||
|
let result ← match ← Environment.inspect args (options := {}) with
|
||||||
|
| .ok result => pure $ result
|
||||||
|
| .error e => panic! s!"Error: {e.desc}"
|
||||||
|
let p := match cat with
|
||||||
|
| .induct info => LSpec.test name (result.inductInfo? == .some info)
|
||||||
|
| .ctor info => LSpec.test name (result.constructorInfo? == .some info)
|
||||||
|
| .recursor info => LSpec.test name (result.recursorInfo? == .some info)
|
||||||
|
return LSpec.TestSeq.append acc p
|
||||||
|
) LSpec.TestSeq.done
|
||||||
|
runCoreMSeq env inner
|
||||||
|
|
||||||
|
def test_symbol_location : TestT IO Unit := do
|
||||||
|
let env: Environment ← importModules
|
||||||
|
(imports := #[`Init])
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
addTest $ ← runTestCoreM env do
|
||||||
|
let .ok result ← Environment.inspect { name := "Nat.le_of_succ_le", source? := .some true } (options := {}) | fail "Inspect failed"
|
||||||
|
checkEq "module" result.module? <| .some "Init.Data.Nat.Basic"
|
||||||
|
|
||||||
|
-- Extraction of source doesn't work for symbols in `Init` for some reason
|
||||||
|
checkTrue "file" result.sourceUri?.isNone
|
||||||
|
checkEq "pos" (result.sourceStart?.map (·.column)) <| .some 0
|
||||||
|
checkEq "pos" (result.sourceEnd?.map (·.column)) <| .some 88
|
||||||
|
|
||||||
|
def suite: List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("Catalog", test_catalog),
|
||||||
|
("Symbol Visibility", test_symbol_visibility),
|
||||||
|
("Inspect", test_inspect),
|
||||||
|
("Symbol Location", runTest test_symbol_location),
|
||||||
|
]
|
||||||
|
|
||||||
|
end Pantograph.Test.Environment
|
|
@ -0,0 +1,248 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph
|
||||||
|
import Repl
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean Pantograph
|
||||||
|
namespace Pantograph.Test.Frontend
|
||||||
|
|
||||||
|
def collectSorrysFromSource (source: String) (options : Frontend.GoalCollectionOptions := {})
|
||||||
|
: MetaM (List GoalState) := do
|
||||||
|
let filename := "<anonymous>"
|
||||||
|
let (context, state) ← do Frontend.createContextStateFromFile source filename (← getEnv) {}
|
||||||
|
let m := Frontend.mapCompilationSteps λ step => do
|
||||||
|
return (step.before, ← Frontend.collectSorrys step options)
|
||||||
|
let li ← m.run context |>.run' state
|
||||||
|
let goalStates ← li.filterMapM λ (env, sorrys) => withEnv env do
|
||||||
|
if sorrys.isEmpty then
|
||||||
|
return .none
|
||||||
|
let { state, .. } ← Frontend.sorrysToGoalState sorrys
|
||||||
|
return .some state
|
||||||
|
return goalStates
|
||||||
|
|
||||||
|
def test_multiple_sorrys_in_proof : TestT MetaM Unit := do
|
||||||
|
let sketch := "
|
||||||
|
theorem plus_n_Sm_proved_formal_sketch : ∀ n m : Nat, n + (m + 1) = (n + m) + 1 := by
|
||||||
|
have h_nat_add_succ: ∀ n m : Nat, n = m := sorry
|
||||||
|
sorry
|
||||||
|
"
|
||||||
|
let goalStates ← (collectSorrysFromSource sketch).run' {}
|
||||||
|
let [goalState] := goalStates | panic! "Incorrect number of states"
|
||||||
|
addTest $ LSpec.check "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize) = #[
|
||||||
|
{
|
||||||
|
target := { pp? := "∀ (n m : Nat), n = m" },
|
||||||
|
vars := #[
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target := { pp? := "∀ (n m : Nat), n + (m + 1) = n + m + 1" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "h_nat_add_succ",
|
||||||
|
type? := .some { pp? := "∀ (n m : Nat), n = m" },
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_sorry_in_middle: TestT MetaM Unit := do
|
||||||
|
let sketch := "
|
||||||
|
example : ∀ (n m: Nat), n + m = m + n := by
|
||||||
|
intros n m
|
||||||
|
sorry
|
||||||
|
"
|
||||||
|
let goalStates ← (collectSorrysFromSource sketch).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
addTest $ LSpec.check "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize) = #[
|
||||||
|
{
|
||||||
|
target := { pp? := "n + m = m + n" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "n",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "m",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_sorry_in_induction : TestT MetaM Unit := do
|
||||||
|
let sketch := "
|
||||||
|
example : ∀ (n m: Nat), n + m = m + n := by
|
||||||
|
intros n m
|
||||||
|
induction n with
|
||||||
|
| zero =>
|
||||||
|
have h1 : 0 + m = m := sorry
|
||||||
|
sorry
|
||||||
|
| succ n ih =>
|
||||||
|
have h2 : n + m = m := sorry
|
||||||
|
sorry
|
||||||
|
"
|
||||||
|
let goalStates ← (collectSorrysFromSource sketch).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
addTest $ LSpec.check "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize) = #[
|
||||||
|
{
|
||||||
|
target := { pp? := "0 + m = m" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "m",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userName? := .some "zero",
|
||||||
|
target := { pp? := "0 + m = m + 0" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "m",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "h1",
|
||||||
|
type? := .some { pp? := "0 + m = m" },
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target := { pp? := "n + m = m" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "m",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "n",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "ih",
|
||||||
|
type? := .some { pp? := "n + m = m + n" },
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userName? := .some "succ",
|
||||||
|
target := { pp? := "n + 1 + m = m + (n + 1)" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "m",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "n",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}, {
|
||||||
|
userName := "ih",
|
||||||
|
type? := .some { pp? := "n + m = m + n" },
|
||||||
|
}, {
|
||||||
|
userName := "h2",
|
||||||
|
type? := .some { pp? := "n + m = m" },
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_sorry_in_coupled: TestT MetaM Unit := do
|
||||||
|
let sketch := "
|
||||||
|
example : ∀ (y: Nat), ∃ (x: Nat), y + 1 = x := by
|
||||||
|
intro y
|
||||||
|
apply Exists.intro
|
||||||
|
case h => sorry
|
||||||
|
case w => sorry
|
||||||
|
"
|
||||||
|
let goalStates ← (collectSorrysFromSource sketch).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
addTest $ LSpec.check "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize) = #[
|
||||||
|
{
|
||||||
|
target := { pp? := "y + 1 = ?w" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "y",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userName? := .some "w",
|
||||||
|
target := { pp? := "Nat" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "y",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_environment_capture: TestT MetaM Unit := do
|
||||||
|
let sketch := "
|
||||||
|
def mystery (n: Nat) := n + 1
|
||||||
|
|
||||||
|
example (n: Nat) : mystery n + 1 = n + 2 := sorry
|
||||||
|
"
|
||||||
|
let goalStates ← (collectSorrysFromSource sketch).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
addTest $ LSpec.check "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize) = #[
|
||||||
|
{
|
||||||
|
target := { pp? := "mystery n + 1 = n + 2" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "n",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_capture_type_mismatch : TestT MetaM Unit := do
|
||||||
|
let input := "
|
||||||
|
def mystery (k: Nat) : Nat := true
|
||||||
|
"
|
||||||
|
let options := { collectTypeErrors := true }
|
||||||
|
let goalStates ← (collectSorrysFromSource input options).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
checkEq "goals" ((← goalState.serializeGoals).map (·.devolatilize)) #[
|
||||||
|
{
|
||||||
|
target := { pp? := "Nat" },
|
||||||
|
vars := #[{
|
||||||
|
userName := "k",
|
||||||
|
type? := .some { pp? := "Nat" },
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_capture_type_mismatch_in_binder : TestT MetaM Unit := do
|
||||||
|
let input := "
|
||||||
|
example (p: Prop) (h: (∀ (x: Prop), Nat) → p): p := h (λ (y: Nat) => 5)
|
||||||
|
"
|
||||||
|
let options := { collectTypeErrors := true }
|
||||||
|
let goalStates ← (collectSorrysFromSource input options).run' {}
|
||||||
|
let [goalState] := goalStates | panic! s!"Incorrect number of states: {goalStates.length}"
|
||||||
|
checkEq "goals" ((← goalState.serializeGoals (options := {})).map (·.devolatilize)) #[
|
||||||
|
]
|
||||||
|
|
||||||
|
def collectNewConstants (source: String) : MetaM (List (List Name)) := do
|
||||||
|
let filename := "<anonymous>"
|
||||||
|
let (context, state) ← do Frontend.createContextStateFromFile source filename (← getEnv) {}
|
||||||
|
let m := Frontend.mapCompilationSteps λ step => do
|
||||||
|
Frontend.collectNewDefinedConstants step
|
||||||
|
m.run context |>.run' state
|
||||||
|
|
||||||
|
def test_collect_one_constant : TestT MetaM Unit := do
|
||||||
|
let input := "
|
||||||
|
def mystery : Nat := 123
|
||||||
|
"
|
||||||
|
let names ← collectNewConstants input
|
||||||
|
checkEq "constants" names [[`mystery]]
|
||||||
|
def test_collect_one_theorem : TestT MetaM Unit := do
|
||||||
|
let input := "
|
||||||
|
theorem mystery [SizeOf α] (as : List α) (i : Fin as.length) : sizeOf (as.get i) < sizeOf as := by
|
||||||
|
match as, i with
|
||||||
|
| a::as, ⟨0, _⟩ => simp_arith [get]
|
||||||
|
| a::as, ⟨i+1, h⟩ =>
|
||||||
|
have ih := sizeOf_get as ⟨i, Nat.le_of_succ_le_succ h⟩
|
||||||
|
apply Nat.lt_trans ih
|
||||||
|
simp_arith
|
||||||
|
"
|
||||||
|
let names ← collectNewConstants input
|
||||||
|
checkEq "constants" names [[`mystery]]
|
||||||
|
|
||||||
|
def suite (env : Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests := [
|
||||||
|
("multiple_sorrys_in_proof", test_multiple_sorrys_in_proof),
|
||||||
|
("sorry_in_middle", test_sorry_in_middle),
|
||||||
|
("sorry_in_induction", test_sorry_in_induction),
|
||||||
|
("sorry_in_coupled", test_sorry_in_coupled),
|
||||||
|
("environment_capture", test_environment_capture),
|
||||||
|
("capture_type_mismatch", test_capture_type_mismatch),
|
||||||
|
--("capture_type_mismatch_in_binder", test_capture_type_mismatch_in_binder),
|
||||||
|
("collect_one_constant", test_collect_one_constant),
|
||||||
|
("collect_one_theorem", test_collect_one_theorem),
|
||||||
|
]
|
||||||
|
tests.map (fun (name, test) => (name, runMetaMSeq env $ runTest test))
|
||||||
|
|
||||||
|
end Pantograph.Test.Frontend
|
|
@ -0,0 +1,263 @@
|
||||||
|
/- Integration test for the REPL
|
||||||
|
-/
|
||||||
|
import LSpec
|
||||||
|
import Pantograph
|
||||||
|
import Repl
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Integration
|
||||||
|
open Pantograph.Repl
|
||||||
|
|
||||||
|
def step { α } [Lean.ToJson α] (cmd: String) (payload: List (String × Lean.Json))
|
||||||
|
(expected: α) (name? : Option String := .none): MainM LSpec.TestSeq := do
|
||||||
|
let payload := Lean.Json.mkObj payload
|
||||||
|
let name := name?.getD s!"{cmd} {payload.compress}"
|
||||||
|
let result ← Repl.execute { cmd, payload }
|
||||||
|
return LSpec.test name (toString result = toString (Lean.toJson expected))
|
||||||
|
|
||||||
|
abbrev Test := List (MainM LSpec.TestSeq)
|
||||||
|
|
||||||
|
def test_elab : Test :=
|
||||||
|
[
|
||||||
|
step "expr.echo"
|
||||||
|
[("expr", .str "λ {α : Sort (u + 1)} => List α"), ("levels", .arr #["u"])]
|
||||||
|
(Lean.toJson ({
|
||||||
|
type := { pp? := .some "{α : Type u} → Type u" },
|
||||||
|
expr := { pp? := .some "fun {α} => List α" }
|
||||||
|
}: Protocol.ExprEchoResult)),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_option_modify : Test :=
|
||||||
|
let pp? := Option.some "∀ (n : Nat), n + 1 = n.succ"
|
||||||
|
let sexp? := Option.some "(:forall n (:c Nat) ((:c Eq) (:c Nat) ((:c HAdd.hAdd) (:c Nat) (:c Nat) (:c Nat) ((:c instHAdd) (:c Nat) (:c instAddNat)) 0 ((:c OfNat.ofNat) (:c Nat) (:lit 1) ((:c instOfNatNat) (:lit 1)))) ((:c Nat.succ) 0)))"
|
||||||
|
let module? := Option.some "Init.Data.Nat.Basic"
|
||||||
|
let options: Protocol.Options := {}
|
||||||
|
[
|
||||||
|
step "env.inspect" [("name", .str "Nat.add_one")]
|
||||||
|
({ type := { pp? }, module? }: Protocol.EnvInspectResult),
|
||||||
|
step "options.set" [("printExprAST", .bool true)]
|
||||||
|
({ }: Protocol.OptionsSetResult),
|
||||||
|
step "env.inspect" [("name", .str "Nat.add_one")]
|
||||||
|
({ type := { pp?, sexp? }, module? }: Protocol.EnvInspectResult),
|
||||||
|
step "options.print" []
|
||||||
|
({ options with printExprAST := true }: Protocol.Options),
|
||||||
|
]
|
||||||
|
def test_malformed_command : Test :=
|
||||||
|
let invalid := "invalid"
|
||||||
|
[
|
||||||
|
step invalid [("name", .str "Nat.add_one")]
|
||||||
|
({ error := "command", desc := s!"Unknown command {invalid}" }: Protocol.InteractionError)
|
||||||
|
(name? := .some "Invalid Command"),
|
||||||
|
step "expr.echo" [(invalid, .str "Random garbage data")]
|
||||||
|
({ error := "command", desc := s!"Unable to parse json: Pantograph.Protocol.ExprEcho.expr: String expected" }:
|
||||||
|
Protocol.InteractionError)
|
||||||
|
(name? := .some "JSON Deserialization")
|
||||||
|
]
|
||||||
|
def test_tactic : Test :=
|
||||||
|
let goal1: Protocol.Goal := {
|
||||||
|
name := "_uniq.11",
|
||||||
|
target := { pp? := .some "∀ (q : Prop), x ∨ q → q ∨ x" },
|
||||||
|
vars := #[{ name := "_uniq.10", userName := "x", type? := .some { pp? := .some "Prop" }}],
|
||||||
|
}
|
||||||
|
let goal2: Protocol.Goal := {
|
||||||
|
name := "_uniq.17",
|
||||||
|
target := { pp? := .some "x ∨ y → y ∨ x" },
|
||||||
|
vars := #[
|
||||||
|
{ name := "_uniq.10", userName := "x", type? := .some { pp? := .some "Prop" }},
|
||||||
|
{ name := "_uniq.16", userName := "y", type? := .some { pp? := .some "Prop" }}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
[
|
||||||
|
step "goal.start" [("expr", .str "∀ (p q: Prop), p ∨ q → q ∨ p")]
|
||||||
|
({ stateId := 0, root := "_uniq.9" }: Protocol.GoalStartResult),
|
||||||
|
step "goal.tactic" [("stateId", .num 0), ("goalId", .num 0), ("tactic", .str "intro x")]
|
||||||
|
({ nextStateId? := .some 1, goals? := #[goal1], }: Protocol.GoalTacticResult),
|
||||||
|
step "goal.print" [("stateId", .num 1), ("parentExpr", .bool true), ("rootExpr", .bool true)]
|
||||||
|
({ parent? := .some { pp? := .some "fun x => ?m.11" }, }: Protocol.GoalPrintResult),
|
||||||
|
step "goal.tactic" [("stateId", .num 1), ("goalId", .num 0), ("tactic", .str "intro y")]
|
||||||
|
({ nextStateId? := .some 2, goals? := #[goal2], }: Protocol.GoalTacticResult),
|
||||||
|
]
|
||||||
|
def test_automatic_mode (automatic: Bool): Test :=
|
||||||
|
let varsPQ := #[
|
||||||
|
{ name := "_uniq.10", userName := "p", type? := .some { pp? := .some "Prop" }},
|
||||||
|
{ name := "_uniq.13", userName := "q", type? := .some { pp? := .some "Prop" }}
|
||||||
|
]
|
||||||
|
let goal1: Protocol.Goal := {
|
||||||
|
name := "_uniq.17",
|
||||||
|
target := { pp? := .some "q ∨ p" },
|
||||||
|
vars := varsPQ ++ #[
|
||||||
|
{ name := "_uniq.16", userName := "h", type? := .some { pp? := .some "p ∨ q" }}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
let goal2l: Protocol.Goal := {
|
||||||
|
name := "_uniq.61",
|
||||||
|
userName? := .some "inl",
|
||||||
|
target := { pp? := .some "q ∨ p" },
|
||||||
|
vars := varsPQ ++ #[
|
||||||
|
{ name := "_uniq.49", userName := "h✝", type? := .some { pp? := .some "p" }, isInaccessible := true}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
let goal2r: Protocol.Goal := {
|
||||||
|
name := "_uniq.74",
|
||||||
|
userName? := .some "inr",
|
||||||
|
target := { pp? := .some "q ∨ p" },
|
||||||
|
vars := varsPQ ++ #[
|
||||||
|
{ name := "_uniq.62", userName := "h✝", type? := .some { pp? := .some "q" }, isInaccessible := true}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
let goal3l: Protocol.Goal := {
|
||||||
|
name := "_uniq.80",
|
||||||
|
userName? := .some "inl.h",
|
||||||
|
target := { pp? := .some "p" },
|
||||||
|
vars := varsPQ ++ #[
|
||||||
|
{ name := "_uniq.49", userName := "h✝", type? := .some { pp? := .some "p" }, isInaccessible := true}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
[
|
||||||
|
step "options.set" [("automaticMode", .bool automatic)]
|
||||||
|
({}: Protocol.OptionsSetResult),
|
||||||
|
step "goal.start" [("expr", .str "∀ (p q: Prop), p ∨ q → q ∨ p")]
|
||||||
|
({ stateId := 0, root := "_uniq.9" }: Protocol.GoalStartResult),
|
||||||
|
step "goal.tactic" [("stateId", .num 0), ("goalId", .num 0), ("tactic", .str "intro p q h")]
|
||||||
|
({ nextStateId? := .some 1, goals? := #[goal1], }: Protocol.GoalTacticResult),
|
||||||
|
step "goal.tactic" [("stateId", .num 1), ("goalId", .num 0), ("tactic", .str "cases h")]
|
||||||
|
({ nextStateId? := .some 2, goals? := #[goal2l, goal2r], }: Protocol.GoalTacticResult),
|
||||||
|
let goals? := if automatic then #[goal3l, goal2r] else #[goal3l]
|
||||||
|
step "goal.tactic" [("stateId", .num 2), ("goalId", .num 0), ("tactic", .str "apply Or.inr")]
|
||||||
|
({ nextStateId? := .some 3, goals?, }: Protocol.GoalTacticResult),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_env_add_inspect : Test :=
|
||||||
|
let name1 := "Pantograph.mystery"
|
||||||
|
let name2 := "Pantograph.mystery2"
|
||||||
|
[
|
||||||
|
step "env.add"
|
||||||
|
[
|
||||||
|
("name", .str name1),
|
||||||
|
("type", .str "Prop → Prop → Prop"),
|
||||||
|
("value", .str "λ (a b: Prop) => Or a b"),
|
||||||
|
("isTheorem", .bool false)
|
||||||
|
]
|
||||||
|
({}: Protocol.EnvAddResult),
|
||||||
|
step "env.inspect" [("name", .str name1)]
|
||||||
|
({
|
||||||
|
value? := .some { pp? := .some "fun a b => a ∨ b" },
|
||||||
|
type := { pp? := .some "Prop → Prop → Prop" },
|
||||||
|
}:
|
||||||
|
Protocol.EnvInspectResult),
|
||||||
|
step "env.add"
|
||||||
|
[
|
||||||
|
("name", .str name2),
|
||||||
|
("type", .str "Nat → Int"),
|
||||||
|
("value", .str "λ (a: Nat) => a + 1"),
|
||||||
|
("isTheorem", .bool false)
|
||||||
|
]
|
||||||
|
({}: Protocol.EnvAddResult),
|
||||||
|
step "env.inspect" [("name", .str name2)]
|
||||||
|
({
|
||||||
|
value? := .some { pp? := .some "fun a => ↑a + 1" },
|
||||||
|
type := { pp? := .some "Nat → Int" },
|
||||||
|
}:
|
||||||
|
Protocol.EnvInspectResult)
|
||||||
|
]
|
||||||
|
|
||||||
|
example : ∀ (p: Prop), p → p := by
|
||||||
|
intro p h
|
||||||
|
exact h
|
||||||
|
|
||||||
|
def test_frontend_process : Test :=
|
||||||
|
[
|
||||||
|
let file := "example : ∀ (p q: Prop), p → p ∨ q := by\n intro p q h\n exact Or.inl h"
|
||||||
|
let goal1 := "p q : Prop\nh : p\n⊢ p ∨ q"
|
||||||
|
step "frontend.process"
|
||||||
|
[
|
||||||
|
("file", .str file),
|
||||||
|
("invocations", .bool true),
|
||||||
|
("sorrys", .bool false),
|
||||||
|
("typeErrorsAsGoals", .bool false),
|
||||||
|
("newConstants", .bool false),
|
||||||
|
]
|
||||||
|
({
|
||||||
|
units := [{
|
||||||
|
boundary := (0, file.utf8ByteSize),
|
||||||
|
invocations? := .some [
|
||||||
|
{
|
||||||
|
goalBefore := "⊢ ∀ (p q : Prop), p → p ∨ q",
|
||||||
|
goalAfter := goal1,
|
||||||
|
tactic := "intro p q h",
|
||||||
|
usedConstants := #[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
goalBefore := goal1 ,
|
||||||
|
goalAfter := "",
|
||||||
|
tactic := "exact Or.inl h",
|
||||||
|
usedConstants := #["Or.inl"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
}: Protocol.FrontendProcessResult),
|
||||||
|
]
|
||||||
|
|
||||||
|
example : 1 + 2 = 3 := rfl
|
||||||
|
example (p: Prop): p → p := by simp
|
||||||
|
|
||||||
|
def test_frontend_process_sorry : Test :=
|
||||||
|
let solved := "example : 1 + 2 = 3 := rfl\n"
|
||||||
|
let withSorry := "example (p: Prop): p → p := sorry"
|
||||||
|
[
|
||||||
|
let file := s!"{solved}{withSorry}"
|
||||||
|
let goal1: Protocol.Goal := {
|
||||||
|
name := "_uniq.6",
|
||||||
|
target := { pp? := .some "p → p" },
|
||||||
|
vars := #[{ name := "_uniq.4", userName := "p", type? := .some { pp? := .some "Prop" }}],
|
||||||
|
}
|
||||||
|
step "frontend.process"
|
||||||
|
[
|
||||||
|
("file", .str file),
|
||||||
|
("invocations", .bool false),
|
||||||
|
("sorrys", .bool true),
|
||||||
|
("typeErrorsAsGoals", .bool false),
|
||||||
|
("newConstants", .bool false),
|
||||||
|
]
|
||||||
|
({
|
||||||
|
units := [{
|
||||||
|
boundary := (0, solved.utf8ByteSize),
|
||||||
|
}, {
|
||||||
|
boundary := (solved.utf8ByteSize, solved.utf8ByteSize + withSorry.utf8ByteSize),
|
||||||
|
goalStateId? := .some 0,
|
||||||
|
goals? := .some #[goal1],
|
||||||
|
goalSrcBoundaries? := .some #[(57, 62)],
|
||||||
|
messages := #["<anonymous>:2:0: warning: declaration uses 'sorry'\n"],
|
||||||
|
}],
|
||||||
|
}: Protocol.FrontendProcessResult),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def runTest (env: Lean.Environment) (steps: Test): IO LSpec.TestSeq := do
|
||||||
|
-- Setup the environment for execution
|
||||||
|
let context: Context := {
|
||||||
|
imports := ["Init"]
|
||||||
|
}
|
||||||
|
let commands: MainM LSpec.TestSeq :=
|
||||||
|
steps.foldlM (λ suite step => do
|
||||||
|
let result ← step
|
||||||
|
return suite ++ result) LSpec.TestSeq.done
|
||||||
|
runCoreMSeq env <| commands.run context |>.run' {}
|
||||||
|
|
||||||
|
|
||||||
|
def suite (env : Lean.Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests := [
|
||||||
|
("expr.echo", test_elab),
|
||||||
|
("options.set options.print", test_option_modify),
|
||||||
|
("Malformed command", test_malformed_command),
|
||||||
|
("Tactic", test_tactic),
|
||||||
|
("Manual Mode", test_automatic_mode false),
|
||||||
|
("Automatic Mode", test_automatic_mode true),
|
||||||
|
("env.add env.inspect", test_env_add_inspect),
|
||||||
|
("frontend.process invocation", test_frontend_process),
|
||||||
|
("frontend.process sorry", test_frontend_process_sorry),
|
||||||
|
]
|
||||||
|
tests.map (fun (name, test) => (name, runTest env test))
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph.Test.Integration
|
|
@ -0,0 +1,38 @@
|
||||||
|
import LSpec
|
||||||
|
import Lean
|
||||||
|
import Pantograph.Library
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Library
|
||||||
|
|
||||||
|
def test_expr_echo (env: Environment): IO LSpec.TestSeq := do
|
||||||
|
let inner: CoreM LSpec.TestSeq := do
|
||||||
|
let prop_and_proof := "⟨∀ (x: Prop), x → x, λ (x: Prop) (h: x) => h⟩"
|
||||||
|
let tests := LSpec.TestSeq.done
|
||||||
|
let echoResult ← exprEcho prop_and_proof (options := {})
|
||||||
|
let tests := tests.append (LSpec.test "fail" (echoResult.toOption == .some {
|
||||||
|
type := { pp? := "?m.2" }, expr := { pp? := "?m.3" }
|
||||||
|
}))
|
||||||
|
let echoResult ← exprEcho prop_and_proof (expectedType? := .some "Σ' p:Prop, p") (options := { printExprAST := true })
|
||||||
|
let tests := tests.append (LSpec.test "fail" (echoResult.toOption == .some {
|
||||||
|
type := {
|
||||||
|
pp? := "(p : Prop) ×' p",
|
||||||
|
sexp? := "((:c PSigma) (:sort 0) (:lambda p (:sort 0) 0))",
|
||||||
|
},
|
||||||
|
expr := {
|
||||||
|
pp? := "⟨∀ (x : Prop), x → x, fun x h => h⟩",
|
||||||
|
sexp? := "((:c PSigma.mk) (:sort 0) (:lambda p (:sort 0) 0) (:forall x (:sort 0) (:forall a 0 1)) (:lambda x (:sort 0) (:lambda h 0 0)))",
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return tests
|
||||||
|
runCoreMSeq env (options := #["pp.proofs.threshold=100"]) inner
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("expr_echo", test_expr_echo env),
|
||||||
|
]
|
||||||
|
|
||||||
|
end Pantograph.Test.Library
|
|
@ -0,0 +1,63 @@
|
||||||
|
import LSpec
|
||||||
|
import Test.Delate
|
||||||
|
import Test.Environment
|
||||||
|
import Test.Frontend
|
||||||
|
import Test.Integration
|
||||||
|
import Test.Library
|
||||||
|
import Test.Metavar
|
||||||
|
import Test.Proofs
|
||||||
|
import Test.Serial
|
||||||
|
import Test.Tactic
|
||||||
|
|
||||||
|
-- Test running infrastructure
|
||||||
|
|
||||||
|
namespace Pantograph.Test
|
||||||
|
|
||||||
|
def addPrefix (pref: String) (tests: List (String × α)): List (String × α) :=
|
||||||
|
tests.map (λ (name, x) => (pref ++ "/" ++ name, x))
|
||||||
|
|
||||||
|
/-- Runs test in parallel. Filters test name if given -/
|
||||||
|
def runTestGroup (filter: Option String) (tests: List (String × IO LSpec.TestSeq)): IO LSpec.TestSeq := do
|
||||||
|
let tests: List (String × IO LSpec.TestSeq) := match filter with
|
||||||
|
| .some filter => tests.filter (λ (name, _) => filter.isPrefixOf name)
|
||||||
|
| .none => tests
|
||||||
|
let tasks: List (String × Task _) ← tests.mapM (λ (name, task) => do
|
||||||
|
return (name, ← EIO.asTask task))
|
||||||
|
let all := tasks.foldl (λ acc (name, task) =>
|
||||||
|
let v: Except IO.Error LSpec.TestSeq := Task.get task
|
||||||
|
match v with
|
||||||
|
| .ok case => acc ++ (LSpec.group name case)
|
||||||
|
| .error e => acc ++ (expectationFailure name e.toString)
|
||||||
|
) LSpec.TestSeq.done
|
||||||
|
return all
|
||||||
|
|
||||||
|
end Pantograph.Test
|
||||||
|
|
||||||
|
open Pantograph.Test
|
||||||
|
|
||||||
|
/-- Main entry of tests; Provide an argument to filter tests by prefix -/
|
||||||
|
def main (args: List String) := do
|
||||||
|
let name_filter := args.head?
|
||||||
|
Lean.initSearchPath (← Lean.findSysroot)
|
||||||
|
let env_default: Lean.Environment ← Lean.importModules
|
||||||
|
(imports := #[`Init])
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
|
||||||
|
let suites: List (String × List (String × IO LSpec.TestSeq)) := [
|
||||||
|
("Environment", Environment.suite),
|
||||||
|
("Frontend", Frontend.suite env_default),
|
||||||
|
("Integration", Integration.suite env_default),
|
||||||
|
("Library", Library.suite env_default),
|
||||||
|
("Metavar", Metavar.suite env_default),
|
||||||
|
("Proofs", Proofs.suite env_default),
|
||||||
|
("Delate", Delate.suite env_default),
|
||||||
|
("Serial", Serial.suite env_default),
|
||||||
|
("Tactic/Assign", Tactic.Assign.suite env_default),
|
||||||
|
("Tactic/Congruence", Tactic.Congruence.suite env_default),
|
||||||
|
("Tactic/Motivated Apply", Tactic.MotivatedApply.suite env_default),
|
||||||
|
("Tactic/No Confuse", Tactic.NoConfuse.suite env_default),
|
||||||
|
("Tactic/Prograde", Tactic.Prograde.suite env_default),
|
||||||
|
]
|
||||||
|
let tests: List (String × IO LSpec.TestSeq) := suites.foldl (λ acc (name, suite) => acc ++ (addPrefix name suite)) []
|
||||||
|
LSpec.lspecIO (← runTestGroup name_filter tests)
|
|
@ -0,0 +1,280 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Metavar
|
||||||
|
open Pantograph
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
abbrev TestM := TestT $ ReaderT Protocol.Options Elab.TermElabM
|
||||||
|
|
||||||
|
-- Tests that all delay assigned mvars are instantiated
|
||||||
|
def test_instantiate_mvar: TestM Unit := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let value := "@Nat.le_trans 2 2 5 (@of_eq_true (@LE.le Nat instLENat 2 2) (@eq_true (@LE.le Nat instLENat 2 2) (@Nat.le_refl 2))) (@of_eq_true (@LE.le Nat instLENat 2 5) (@eq_true_of_decide (@LE.le Nat instLENat 2 5) (@Nat.decLe 2 5) (@Eq.refl Bool Bool.true)))"
|
||||||
|
let syn ← match parseTerm env value with
|
||||||
|
| .ok s => pure $ s
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let expr ← match ← elabTerm syn with
|
||||||
|
| .ok expr => pure $ expr
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let t ← Lean.Meta.inferType expr
|
||||||
|
addTest $ LSpec.check "typing" ((toString (← serializeExpressionSexp t)) =
|
||||||
|
"((:c LE.le) (:c Nat) (:c instLENat) ((:c OfNat.ofNat) (:mv _uniq.2) (:lit 2) (:mv _uniq.3)) ((:c OfNat.ofNat) (:mv _uniq.14) (:lit 5) (:mv _uniq.15)))")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def startProof (expr: String): TestM (Option GoalState) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let syn? := parseTerm env expr
|
||||||
|
addTest $ LSpec.check s!"Parsing {expr}" (syn?.isOk)
|
||||||
|
match syn? with
|
||||||
|
| .error error =>
|
||||||
|
IO.println error
|
||||||
|
return Option.none
|
||||||
|
| .ok syn =>
|
||||||
|
let expr? ← elabType syn
|
||||||
|
addTest $ LSpec.check s!"Elaborating" expr?.isOk
|
||||||
|
match expr? with
|
||||||
|
| .error error =>
|
||||||
|
IO.println error
|
||||||
|
return Option.none
|
||||||
|
| .ok expr =>
|
||||||
|
let goal ← GoalState.create (expr := expr)
|
||||||
|
return Option.some goal
|
||||||
|
|
||||||
|
def buildGoal (nameType: List (String × String)) (target: String) (userName?: Option String := .none): Protocol.Goal :=
|
||||||
|
{
|
||||||
|
userName?,
|
||||||
|
target := { pp? := .some target},
|
||||||
|
vars := (nameType.map fun x => ({
|
||||||
|
userName := x.fst,
|
||||||
|
type? := .some { pp? := .some x.snd },
|
||||||
|
})).toArray
|
||||||
|
}
|
||||||
|
def proofRunner (env: Lean.Environment) (tests: TestM Unit): IO LSpec.TestSeq := do
|
||||||
|
let termElabM := tests.run LSpec.TestSeq.done |>.run {} -- with default options
|
||||||
|
|
||||||
|
let coreContext: Lean.Core.Context ← createCoreContext #[]
|
||||||
|
let metaM := termElabM.run' (ctx := defaultElabContext)
|
||||||
|
let coreM := metaM.run'
|
||||||
|
match ← (coreM.run' coreContext { env := env }).toBaseIO with
|
||||||
|
| .error exception =>
|
||||||
|
return LSpec.test "Exception" (s!"internal exception #{← exception.toMessageData.toString}" = "")
|
||||||
|
| .ok (_, a) =>
|
||||||
|
return a
|
||||||
|
|
||||||
|
/-- M-coupled goals -/
|
||||||
|
def test_m_couple: TestM Unit := do
|
||||||
|
let state? ← startProof "(2: Nat) ≤ 5"
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := "apply Nat.le_trans") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "apply Nat.le_trans" ((← state1.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ ?m", .some "?m ≤ 5", .some "Nat"])
|
||||||
|
addTest $ LSpec.test "(1 root)" state1.rootExpr?.isNone
|
||||||
|
-- Set m to 3
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 2) (tactic := "exact 3") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test "(1b root)" state2.rootExpr?.isNone
|
||||||
|
let state1b ← match state2.continue state1 with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.check "exact 3" ((← state1b.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ 3", .some "3 ≤ 5"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state1b.rootExpr?.isNone
|
||||||
|
|
||||||
|
def test_m_couple_simp: TestM Unit := do
|
||||||
|
let state? ← startProof "(2: Nat) ≤ 5"
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := "apply Nat.le_trans") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let serializedState1 ← state1.serializeGoals (options := { ← read with printDependentMVars := true })
|
||||||
|
addTest $ LSpec.check "apply Nat.le_trans" (serializedState1.map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ ?m", .some "?m ≤ 5", .some "Nat"])
|
||||||
|
addTest $ LSpec.check "(metavariables)" (serializedState1.map (·.target.dependentMVars?.get!) =
|
||||||
|
#[#["_uniq.38"], #["_uniq.38"], #[]])
|
||||||
|
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 2) (tactic := "exact 2") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test "(1b root)" state2.rootExpr?.isNone
|
||||||
|
let state1b ← match state2.continue state1 with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.check "exact 2" ((← state1b.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ 2", .some "2 ≤ 5"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state1b.rootExpr?.isNone
|
||||||
|
let state3 ← match ← state1b.tacticOn (goalId := 0) (tactic := "simp") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let state4 ← match state3.continue state1b with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
let state5 ← match ← state4.tacticOn (goalId := 0) (tactic := "simp") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
|
||||||
|
state5.restoreMetaM
|
||||||
|
let root ← match state5.rootExpr? with
|
||||||
|
| .some e => pure e
|
||||||
|
| .none =>
|
||||||
|
addTest $ assertUnreachable "(5 root)"
|
||||||
|
return ()
|
||||||
|
let rootStr: String := toString (← Lean.Meta.ppExpr root)
|
||||||
|
addTest $ LSpec.check "(5 root)" (rootStr = "Nat.le_trans (of_eq_true (Init.Data.Nat.Basic._auxLemma.4 2)) (of_eq_true (eq_true_of_decide (Eq.refl true)))")
|
||||||
|
let unfoldedRoot ← unfoldAuxLemmas root
|
||||||
|
addTest $ LSpec.check "(5 root)" ((toString (← Lean.Meta.ppExpr unfoldedRoot)) =
|
||||||
|
"Nat.le_trans (of_eq_true (eq_true (Nat.le_refl 2))) (of_eq_true (eq_true_of_decide (Eq.refl true)))")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def test_proposition_generation: TestM Unit := do
|
||||||
|
let state? ← startProof "Σ' p:Prop, p"
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := "apply PSigma.mk") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "apply PSigma.mk" ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
buildGoal [] "?fst" (userName? := .some "snd"),
|
||||||
|
buildGoal [] "Prop" (userName? := .some "fst")
|
||||||
|
])
|
||||||
|
if let #[goal1, goal2] := ← state1.serializeGoals (options := { (← read) with printExprAST := true }) then
|
||||||
|
addTest $ LSpec.test "(1 reference)" (goal1.target.sexp? = .some s!"(:mv {goal2.name})")
|
||||||
|
addTest $ LSpec.test "(1 root)" state1.rootExpr?.isNone
|
||||||
|
|
||||||
|
let state2 ← match ← state1.tryAssign (state1.get! 0) (expr := "λ (x: Nat) => _") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check ":= λ (x: Nat), _" ((← state2.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "?m.29 x"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state2.rootExpr?.isNone
|
||||||
|
|
||||||
|
let assign := "Eq.refl x"
|
||||||
|
let state3 ← match ← state2.tryAssign (state2.get! 0) (expr := assign) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {assign}" ((← state3.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
addTest $ LSpec.test "(3 root)" state3.rootExpr?.isSome
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def test_partial_continuation: TestM Unit := do
|
||||||
|
let state? ← startProof "(2: Nat) ≤ 5"
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := "apply Nat.le_trans") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "apply Nat.le_trans" ((← state1.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ ?m", .some "?m ≤ 5", .some "Nat"])
|
||||||
|
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 2) (tactic := "apply Nat.succ") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "apply Nat.succ" ((← state2.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "Nat"])
|
||||||
|
|
||||||
|
-- Execute a partial continuation
|
||||||
|
let coupled_goals := state1.goals ++ state2.goals
|
||||||
|
let state1b ← match state2.resume (goals := coupled_goals) with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.check "(continue)" ((← state1b.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ Nat.succ ?m", .some "Nat.succ ?m ≤ 5", .some "Nat"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state1b.rootExpr?.isNone
|
||||||
|
|
||||||
|
-- Roundtrip
|
||||||
|
--let coupled_goals := coupled_goals.map (λ g =>
|
||||||
|
-- { name := str_to_name $ serializeName g.name (sanitize := false)})
|
||||||
|
let coupled_goals := coupled_goals.map (λ g => serializeName g.name (sanitize := false))
|
||||||
|
let coupled_goals := coupled_goals.map (λ g => { name := g.toName })
|
||||||
|
let state1b ← match state2.resume (goals := coupled_goals) with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.check "(continue)" ((← state1b.serializeGoals (options := ← read)).map (·.target.pp?) =
|
||||||
|
#[.some "2 ≤ Nat.succ ?m", .some "Nat.succ ?m ≤ 5", .some "Nat"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state1b.rootExpr?.isNone
|
||||||
|
|
||||||
|
-- Continuation should fail if the state does not exist:
|
||||||
|
match state0.resume coupled_goals with
|
||||||
|
| .error error => addTest $ LSpec.check "(continuation failure message)" (error = "Goals [_uniq.40, _uniq.41, _uniq.38, _uniq.47] are not in scope")
|
||||||
|
| .ok _ => addTest $ assertUnreachable "(continuation failure)"
|
||||||
|
-- Continuation should fail if some goals have not been solved
|
||||||
|
match state2.continue state1 with
|
||||||
|
| .error error => addTest $ LSpec.check "(continuation failure message)" (error = "Target state has unresolved goals")
|
||||||
|
| .ok _ => addTest $ assertUnreachable "(continuation failure)"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests := [
|
||||||
|
("Instantiate", test_instantiate_mvar),
|
||||||
|
("2 < 5", test_m_couple),
|
||||||
|
("2 < 5", test_m_couple_simp),
|
||||||
|
("Proposition Generation", test_proposition_generation),
|
||||||
|
("Partial Continuation", test_partial_continuation)
|
||||||
|
]
|
||||||
|
tests.map (fun (name, test) => (name, proofRunner env test))
|
||||||
|
|
||||||
|
end Pantograph.Test.Metavar
|
|
@ -0,0 +1,792 @@
|
||||||
|
/-
|
||||||
|
Tests pertaining to goals with no interdependencies
|
||||||
|
-/
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Delate
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Proofs
|
||||||
|
open Pantograph
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
inductive Start where
|
||||||
|
| copy (name: String) -- Start from some name in the environment
|
||||||
|
| expr (expr: String) -- Start from some expression
|
||||||
|
|
||||||
|
abbrev TestM := TestT $ ReaderT Protocol.Options $ Elab.TermElabM
|
||||||
|
|
||||||
|
def startProof (start: Start): TestM (Option GoalState) := do
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
match start with
|
||||||
|
| .copy name =>
|
||||||
|
let cInfo? := name.toName |> env.find?
|
||||||
|
addTest $ LSpec.check s!"Symbol exists {name}" cInfo?.isSome
|
||||||
|
match cInfo? with
|
||||||
|
| .some cInfo =>
|
||||||
|
let goal ← GoalState.create (expr := cInfo.type)
|
||||||
|
return Option.some goal
|
||||||
|
| .none =>
|
||||||
|
return Option.none
|
||||||
|
| .expr expr =>
|
||||||
|
let syn? := parseTerm env expr
|
||||||
|
addTest $ LSpec.check s!"Parsing {expr}" (syn?.isOk)
|
||||||
|
match syn? with
|
||||||
|
| .error error =>
|
||||||
|
IO.println error
|
||||||
|
return Option.none
|
||||||
|
| .ok syn =>
|
||||||
|
let expr? ← elabType syn
|
||||||
|
addTest $ LSpec.check s!"Elaborating" expr?.isOk
|
||||||
|
match expr? with
|
||||||
|
| .error error =>
|
||||||
|
IO.println error
|
||||||
|
return Option.none
|
||||||
|
| .ok expr =>
|
||||||
|
let goal ← GoalState.create (expr := expr)
|
||||||
|
return Option.some goal
|
||||||
|
|
||||||
|
def buildNamedGoal (name: String) (nameType: List (String × String)) (target: String)
|
||||||
|
(userName?: Option String := .none): Protocol.Goal :=
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
userName?,
|
||||||
|
target := { pp? := .some target},
|
||||||
|
vars := (nameType.map fun x => ({
|
||||||
|
userName := x.fst,
|
||||||
|
type? := .some { pp? := .some x.snd },
|
||||||
|
})).toArray
|
||||||
|
}
|
||||||
|
def buildGoal (nameType: List (String × String)) (target: String) (userName?: Option String := .none):
|
||||||
|
Protocol.Goal :=
|
||||||
|
{
|
||||||
|
userName?,
|
||||||
|
target := { pp? := .some target},
|
||||||
|
vars := (nameType.map fun x => ({
|
||||||
|
userName := x.fst,
|
||||||
|
type? := .some { pp? := .some x.snd },
|
||||||
|
})).toArray
|
||||||
|
}
|
||||||
|
def proofRunner (env: Lean.Environment) (tests: TestM Unit): IO LSpec.TestSeq := do
|
||||||
|
let termElabM := tests.run LSpec.TestSeq.done |>.run {} -- with default options
|
||||||
|
|
||||||
|
let coreContext: Lean.Core.Context ← createCoreContext #[]
|
||||||
|
let metaM := termElabM.run' (ctx := defaultElabContext)
|
||||||
|
let coreM := metaM.run'
|
||||||
|
match ← (coreM.run' coreContext { env := env }).toBaseIO with
|
||||||
|
| .error exception =>
|
||||||
|
return LSpec.test "Exception" (s!"internal exception #{← exception.toMessageData.toString}" = "")
|
||||||
|
| .ok (_, a) =>
|
||||||
|
return a
|
||||||
|
|
||||||
|
def test_identity: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (p: Prop), p → p")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "intro p h"
|
||||||
|
let state1 ← match ← state0.tacticOn 0 tactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let inner := "_uniq.12"
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)).map (·.name) =
|
||||||
|
#[inner])
|
||||||
|
let state1parent ← state1.withParentContext do
|
||||||
|
serializeExpressionSexp (← instantiateAll state1.parentExpr?.get!)
|
||||||
|
addTest $ LSpec.test "(1 parent)" (state1parent == s!"(:lambda p (:sort 0) (:lambda h 0 (:subst (:mv {inner}) 1 0)))")
|
||||||
|
|
||||||
|
-- Individual test cases
|
||||||
|
example: ∀ (a b: Nat), a + b = b + a := by
|
||||||
|
intro n m
|
||||||
|
rw [Nat.add_comm]
|
||||||
|
def test_nat_add_comm (manual: Bool): TestM Unit := do
|
||||||
|
let state? ← startProof <| match manual with
|
||||||
|
| false => .copy "Nat.add_comm"
|
||||||
|
| true => .expr "∀ (a b: Nat), a + b = b + a"
|
||||||
|
addTest $ LSpec.check "Start goal" state?.isSome
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn 0 "intro n m" with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "intro n m" ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("n", "Nat"), ("m", "Nat")] "n + m = m + n"])
|
||||||
|
|
||||||
|
match ← state1.tacticOn 0 "assumption" with
|
||||||
|
| .failure #[message] =>
|
||||||
|
addTest $ LSpec.check "assumption" (message = "tactic 'assumption' failed\nn m : Nat\n⊢ n + m = m + n")
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
|
||||||
|
let state2 ← match ← state1.tacticOn 0 "rw [Nat.add_comm]" with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test "rw [Nat.add_comm]" state2.goals.isEmpty
|
||||||
|
|
||||||
|
return ()
|
||||||
|
def test_delta_variable: TestM Unit := do
|
||||||
|
let options: Protocol.Options := { noRepeat := true }
|
||||||
|
let state? ← startProof <| .expr "∀ (a b: Nat), a + b = b + a"
|
||||||
|
addTest $ LSpec.check "Start goal" state?.isSome
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := "intro n") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "intro n" ((← state1.serializeGoals (parent := state0) options).map (·.devolatilize) =
|
||||||
|
#[buildGoalSelective [("n", .some "Nat")] "∀ (b : Nat), n + b = b + n"])
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 0) (tactic := "intro m") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "intro m" ((← state2.serializeGoals (parent := state1) options).map (·.devolatilize) =
|
||||||
|
#[buildGoalSelective [("n", .none), ("m", .some "Nat")] "n + m = m + n"])
|
||||||
|
return ()
|
||||||
|
where
|
||||||
|
-- Like `buildGoal` but allow certain variables to be elided.
|
||||||
|
buildGoalSelective (nameType: List (String × Option String)) (target: String): Protocol.Goal :=
|
||||||
|
{
|
||||||
|
target := { pp? := .some target},
|
||||||
|
vars := (nameType.map fun x => ({
|
||||||
|
userName := x.fst,
|
||||||
|
type? := x.snd.map (λ type => { pp? := type }),
|
||||||
|
})).toArray
|
||||||
|
}
|
||||||
|
|
||||||
|
example (w x y z : Nat) (p : Nat → Prop)
|
||||||
|
(h : p (x * y + z * w * x)) : p (x * w * z + y * x) := by
|
||||||
|
simp [Nat.add_assoc, Nat.add_comm, Nat.add_left_comm, Nat.mul_comm, Nat.mul_assoc, Nat.mul_left_comm] at *
|
||||||
|
assumption
|
||||||
|
def test_arith: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (w x y z : Nat) (p : Nat → Prop) (h : p (x * y + z * w * x)), p (x * w * z + y * x)")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "intros"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic (state1.goals.length = 1)
|
||||||
|
addTest $ LSpec.test "(1 root)" state1.rootExpr?.isNone
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 0) (tactic := "simp [Nat.add_assoc, Nat.add_comm, Nat.add_left_comm, Nat.mul_comm, Nat.mul_assoc, Nat.mul_left_comm] at *") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "simp ..." (state2.goals.length = 1)
|
||||||
|
addTest $ LSpec.check "(2 root)" state2.rootExpr?.isNone
|
||||||
|
let tactic := "assumption"
|
||||||
|
let state3 ← match ← state2.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test tactic state3.goals.isEmpty
|
||||||
|
addTest $ LSpec.check "(3 root)" state3.rootExpr?.isSome
|
||||||
|
return ()
|
||||||
|
|
||||||
|
-- Two ways to write the same theorem
|
||||||
|
example: ∀ (p q: Prop), p ∨ q → q ∨ p := by
|
||||||
|
intro p q h
|
||||||
|
cases h
|
||||||
|
apply Or.inr
|
||||||
|
assumption
|
||||||
|
apply Or.inl
|
||||||
|
assumption
|
||||||
|
example: ∀ (p q: Prop), p ∨ q → q ∨ p := by
|
||||||
|
intro p q h
|
||||||
|
cases h
|
||||||
|
. apply Or.inr
|
||||||
|
assumption
|
||||||
|
. apply Or.inl
|
||||||
|
assumption
|
||||||
|
def test_or_comm: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (p q: Prop), p ∨ q → q ∨ p")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "(0 parent)" state0.parentExpr?.isNone
|
||||||
|
addTest $ LSpec.check "(0 root)" state0.rootExpr?.isNone
|
||||||
|
|
||||||
|
let tactic := "intro p q h"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let [state1g0] := state1.goals | fail "Should have 1 goal"
|
||||||
|
let (fvP, fvQ, fvH) ← state1.withContext state1g0 do
|
||||||
|
let lctx ← getLCtx
|
||||||
|
let #[fvP, fvQ, fvH] := lctx.getFVarIds.map (toString ·.name) |
|
||||||
|
panic! "Incorrect number of decls"
|
||||||
|
pure (fvP, fvQ, fvH)
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)) =
|
||||||
|
#[{
|
||||||
|
name := state1g0.name.toString,
|
||||||
|
target := { pp? := .some "q ∨ p" },
|
||||||
|
vars := #[
|
||||||
|
{ name := fvP, userName := "p", type? := .some { pp? := .some "Prop" } },
|
||||||
|
{ name := fvQ, userName := "q", type? := .some { pp? := .some "Prop" } },
|
||||||
|
{ name := fvH, userName := "h", type? := .some { pp? := .some "p ∨ q" } }
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
addTest $ LSpec.check "(1 parent)" state1.parentExpr?.isSome
|
||||||
|
addTest $ LSpec.check "(1 root)" state1.rootExpr?.isNone
|
||||||
|
|
||||||
|
let state1parent ← state1.withParentContext do
|
||||||
|
serializeExpressionSexp (← instantiateAll state1.parentExpr?.get!)
|
||||||
|
addTest $ LSpec.test "(1 parent)" (state1parent == s!"(:lambda p (:sort 0) (:lambda q (:sort 0) (:lambda h ((:c Or) 1 0) (:subst (:mv {state1g0}) 2 1 0))))")
|
||||||
|
let tactic := "cases h"
|
||||||
|
let state2 ← match ← state1.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state2.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[branchGoal "inl" "p", branchGoal "inr" "q"])
|
||||||
|
let [state2g0, state2g1] := state2.goals |
|
||||||
|
fail s!"Should have 2 goals, but it has {state2.goals.length}"
|
||||||
|
let (caseL, caseR) := (state2g0.name.toString, state2g1.name.toString)
|
||||||
|
addTest $ LSpec.check tactic ((← state2.serializeGoals (options := ← read)).map (·.name) =
|
||||||
|
#[caseL, caseR])
|
||||||
|
addTest $ LSpec.check "(2 parent exists)" state2.parentExpr?.isSome
|
||||||
|
addTest $ LSpec.check "(2 root)" state2.rootExpr?.isNone
|
||||||
|
|
||||||
|
let state2parent ← state2.withParentContext do
|
||||||
|
serializeExpressionSexp (← instantiateAll state2.parentExpr?.get!)
|
||||||
|
let orPQ := s!"((:c Or) (:fv {fvP}) (:fv {fvQ}))"
|
||||||
|
let orQP := s!"((:c Or) (:fv {fvQ}) (:fv {fvP}))"
|
||||||
|
let motive := s!"(:lambda t {orPQ} (:forall h ((:c Eq) ((:c Or) (:fv {fvP}) (:fv {fvQ})) (:fv {fvH}) 0) {orQP}))"
|
||||||
|
let caseL := s!"(:lambda h (:fv {fvP}) (:lambda h ((:c Eq) {orPQ} (:fv {fvH}) ((:c Or.inl) (:fv {fvP}) (:fv {fvQ}) 0)) (:subst (:mv {caseL}) (:fv {fvP}) (:fv {fvQ}) 1)))"
|
||||||
|
let caseR := s!"(:lambda h (:fv {fvQ}) (:lambda h ((:c Eq) {orPQ} (:fv {fvH}) ((:c Or.inr) (:fv {fvP}) (:fv {fvQ}) 0)) (:subst (:mv {caseR}) (:fv {fvP}) (:fv {fvQ}) 1)))"
|
||||||
|
let conduit := s!"((:c Eq.refl) {orPQ} (:fv {fvH}))"
|
||||||
|
addTest $ LSpec.test "(2 parent)" (state2parent ==
|
||||||
|
s!"((:c Or.casesOn) (:fv {fvP}) (:fv {fvQ}) {motive} (:fv {fvH}) {caseL} {caseR} {conduit})")
|
||||||
|
|
||||||
|
let state3_1 ← match ← state2.tacticOn (goalId := 0) (tactic := "apply Or.inr") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let state3_1parent ← state3_1.withParentContext do
|
||||||
|
serializeExpressionSexp (← instantiateAll state3_1.parentExpr?.get!)
|
||||||
|
let [state3_1goal0] := state3_1.goals | fail "Should have 1 goal"
|
||||||
|
addTest $ LSpec.test "(3_1 parent)" (state3_1parent == s!"((:c Or.inr) (:fv {fvQ}) (:fv {fvP}) (:mv {state3_1goal0}))")
|
||||||
|
addTest $ LSpec.check "· apply Or.inr" (state3_1.goals.length = 1)
|
||||||
|
let state4_1 ← match ← state3_1.tacticOn (goalId := 0) (tactic := "assumption") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check " assumption" state4_1.goals.isEmpty
|
||||||
|
let state4_1parent ← instantiateAll state4_1.parentExpr?.get!
|
||||||
|
addTest $ LSpec.test "(4_1 parent)" state4_1parent.isFVar
|
||||||
|
addTest $ LSpec.check "(4_1 root)" state4_1.rootExpr?.isNone
|
||||||
|
let state3_2 ← match ← state2.tacticOn (goalId := 1) (tactic := "apply Or.inl") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "· apply Or.inl" (state3_2.goals.length = 1)
|
||||||
|
let state4_2 ← match ← state3_2.tacticOn (goalId := 0) (tactic := "assumption") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check " assumption" state4_2.goals.isEmpty
|
||||||
|
addTest $ LSpec.check "(4_2 root)" state4_2.rootExpr?.isNone
|
||||||
|
-- Ensure the proof can continue from `state4_2`.
|
||||||
|
let state2b ← match state4_2.continue state2 with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.test "(resume)" (state2b.goals == [state2.goals.get! 0])
|
||||||
|
let state3_1 ← match ← state2b.tacticOn (goalId := 0) (tactic := "apply Or.inr") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "· apply Or.inr" (state3_1.goals.length = 1)
|
||||||
|
let state4_1 ← match ← state3_1.tacticOn (goalId := 0) (tactic := "assumption") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check " assumption" state4_1.goals.isEmpty
|
||||||
|
addTest $ LSpec.check "(4_1 root)" state4_1.rootExpr?.isSome
|
||||||
|
|
||||||
|
return ()
|
||||||
|
where
|
||||||
|
typeProp: Protocol.Expression := { pp? := .some "Prop" }
|
||||||
|
branchGoal (caseName varName: String): Protocol.Goal := {
|
||||||
|
userName? := .some caseName,
|
||||||
|
target := { pp? := .some "q ∨ p" },
|
||||||
|
vars := #[
|
||||||
|
{ userName := "p", type? := .some typeProp },
|
||||||
|
{ userName := "q", type? := .some typeProp },
|
||||||
|
{ userName := "h✝", type? := .some { pp? := .some varName }, isInaccessible := true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
example : ∀ (a b c1 c2: Nat), (b + a) + c1 = (b + a) + c2 → (a + b) + c1 = (b + a) + c2 := by
|
||||||
|
intro a b c1 c2 h
|
||||||
|
conv =>
|
||||||
|
lhs
|
||||||
|
congr
|
||||||
|
. rw [Nat.add_comm]
|
||||||
|
. rfl
|
||||||
|
exact h
|
||||||
|
|
||||||
|
def test_conv: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (a b c1 c2: Nat), (b + a) + c1 = (b + a) + c2 → (a + b) + c1 = (b + a) + c2")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "intro a b c1 c2 h"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[interiorGoal [] "a + b + c1 = b + a + c2"])
|
||||||
|
|
||||||
|
let state2 ← match ← state1.conv (state1.get! 0) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check "conv => ..." ((← state2.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[{ interiorGoal [] "a + b + c1 = b + a + c2" with isConversion := true }])
|
||||||
|
|
||||||
|
let convTactic := "rhs"
|
||||||
|
let state3R ← match ← state2.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" {convTactic} (discard)" ((← state3R.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[{ interiorGoal [] "b + a + c2" with isConversion := true }])
|
||||||
|
|
||||||
|
let convTactic := "lhs"
|
||||||
|
let state3L ← match ← state2.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" {convTactic}" ((← state3L.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[{ interiorGoal [] "a + b + c1" with isConversion := true }])
|
||||||
|
|
||||||
|
let convTactic := "congr"
|
||||||
|
let state4 ← match ← state3L.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" {convTactic}" ((← state4.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
{ interiorGoal [] "a + b" with isConversion := true, userName? := .some "a" },
|
||||||
|
{ interiorGoal [] "c1" with isConversion := true, userName? := .some "a" }
|
||||||
|
])
|
||||||
|
|
||||||
|
let convTactic := "rw [Nat.add_comm]"
|
||||||
|
let state5_1 ← match ← state4.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" · {convTactic}" ((← state5_1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[{ interiorGoal [] "b + a" with isConversion := true, userName? := .some "a" }])
|
||||||
|
|
||||||
|
let convTactic := "rfl"
|
||||||
|
let state6_1 ← match ← state5_1.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" {convTactic}" ((← state6_1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let state4_1 ← match state6_1.continue state4 with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ expectationFailure "continue" e
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let convTactic := "rfl"
|
||||||
|
let state6 ← match ← state4_1.tacticOn (goalId := 0) convTactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!" · {convTactic}" ((← state6.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let state1_1 ← match ← state6.convExit with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "exact h"
|
||||||
|
let stateF ← match ← state1_1.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← stateF.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
where
|
||||||
|
h := "b + a + c1 = b + a + c2"
|
||||||
|
interiorGoal (free: List (String × String)) (target: String) :=
|
||||||
|
let free := [("a", "Nat"), ("b", "Nat"), ("c1", "Nat"), ("c2", "Nat"), ("h", h)] ++ free
|
||||||
|
buildGoal free target
|
||||||
|
|
||||||
|
example : ∀ (a b c d: Nat), a + b = b + c → b + c = c + d → a + b = c + d := by
|
||||||
|
intro a b c d h1 h2
|
||||||
|
calc a + b = b + c := by apply h1
|
||||||
|
_ = c + d := by apply h2
|
||||||
|
|
||||||
|
def test_calc: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (a b c d: Nat), a + b = b + c → b + c = c + d → a + b = c + d")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
let tactic := "intro a b c d h1 h2"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[interiorGoal [] "a + b = c + d"])
|
||||||
|
let pred := "a + b = b + c"
|
||||||
|
let state2 ← match ← state1.tryCalc (state1.get! 0) (pred := pred) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!"calc {pred} := _" ((← state2.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
interiorGoal [] "a + b = b + c" (.some "calc"),
|
||||||
|
interiorGoal [] "b + c = c + d"
|
||||||
|
])
|
||||||
|
addTest $ LSpec.test "(2.0 prev rhs)" (state2.calcPrevRhsOf? (state2.get! 0) |>.isNone)
|
||||||
|
addTest $ LSpec.test "(2.1 prev rhs)" (state2.calcPrevRhsOf? (state2.get! 1) |>.isSome)
|
||||||
|
|
||||||
|
let tactic := "apply h1"
|
||||||
|
let state2m ← match ← state2.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let state3 ← match state2m.continue state2 with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ expectationFailure "continue" e
|
||||||
|
return ()
|
||||||
|
let pred := "_ = c + d"
|
||||||
|
let state4 ← match ← state3.tryCalc (state3.get! 0) (pred := pred) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!"calc {pred} := _" ((← state4.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
interiorGoal [] "b + c = c + d" (.some "calc")
|
||||||
|
])
|
||||||
|
addTest $ LSpec.test "(4.0 prev rhs)" (state4.calcPrevRhsOf? (state4.get! 0) |>.isNone)
|
||||||
|
let tactic := "apply h2"
|
||||||
|
let state4m ← match ← state4.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test "(4m root)" state4m.rootExpr?.isSome
|
||||||
|
where
|
||||||
|
interiorGoal (free: List (String × String)) (target: String) (userName?: Option String := .none) :=
|
||||||
|
let free := [("a", "Nat"), ("b", "Nat"), ("c", "Nat"), ("d", "Nat"),
|
||||||
|
("h1", "a + b = b + c"), ("h2", "b + c = c + d")] ++ free
|
||||||
|
buildGoal free target userName?
|
||||||
|
|
||||||
|
def test_nat_zero_add: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (n: Nat), n + 0 = n")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
let tactic := "intro n"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("n", "Nat")] "n + 0 = n"])
|
||||||
|
let recursor := "@Nat.brecOn"
|
||||||
|
let state2 ← match ← state1.tryMotivatedApply (state1.get! 0) (recursor := recursor) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let [mvarMotive, mvarMajor, mvarInduct, mvarConduit] := state2.goals |
|
||||||
|
fail "Incorrect number of goals"
|
||||||
|
let .num _ major := mvarMajor.name | fail "Incorrect form of mvar id"
|
||||||
|
addTest $ LSpec.check s!"mapply {recursor}" ((← state2.serializeGoals (options := ← read)).map (·.devolatilizeVars) =
|
||||||
|
#[
|
||||||
|
buildNamedGoal mvarMotive.name.toString [("n", "Nat")] "Nat → Prop" (.some "motive"),
|
||||||
|
buildNamedGoal mvarMajor.name.toString [("n", "Nat")] "Nat",
|
||||||
|
buildNamedGoal mvarInduct.name.toString [("n", "Nat")] "∀ (t : Nat), Nat.below t → ?motive t",
|
||||||
|
buildNamedGoal mvarConduit.name.toString [("n", "Nat")] s!"?motive ?m.{major} = (n + 0 = n)" (.some "conduit")
|
||||||
|
])
|
||||||
|
|
||||||
|
let tactic := "exact n"
|
||||||
|
let state3b ← match ← state2.tacticOn (goalId := 1) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state3b.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
let state2b ← match state3b.continue state2 with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let tactic := "exact (λ x => x + 0 = x)"
|
||||||
|
let state3c ← match ← state2b.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state3c.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
let state2c ← match state3c.continue state2b with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let tactic := "intro t h"
|
||||||
|
let state3 ← match ← state2c.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state3.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("n", "Nat"), ("t", "Nat"), ("h", "Nat.below t")] "t + 0 = t"])
|
||||||
|
|
||||||
|
let tactic := "simp"
|
||||||
|
let state3d ← match ← state3.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let state2d ← match state3d.continue state2c with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let tactic := "rfl"
|
||||||
|
let stateF ← match ← state2d.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← stateF.serializeGoals (options := ← read)) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let expr := stateF.mctx.eAssignment.find! stateF.root
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := stateF.mctx) (e := expr)
|
||||||
|
addTest $ LSpec.check "(F root)" stateF.rootExpr?.isSome
|
||||||
|
|
||||||
|
def test_nat_zero_add_alt: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (n: Nat), n + 0 = n")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
let tactic := "intro n"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("n", "Nat")] "n + 0 = n"])
|
||||||
|
let recursor := "@Nat.brecOn"
|
||||||
|
let state2 ← match ← state1.tryMotivatedApply (state1.get! 0) (recursor := recursor) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let [mvarMotive, mvarMajor, mvarInduct, mvarConduit] := state2.goals |
|
||||||
|
fail "Incorrect number of goals"
|
||||||
|
let .num _ major := mvarMajor.name | fail "Incorrect form of mvar id"
|
||||||
|
addTest $ LSpec.check s!"mapply {recursor}" ((← state2.serializeGoals (options := ← read)).map (·.devolatilizeVars) =
|
||||||
|
#[
|
||||||
|
buildNamedGoal mvarMotive.name.toString [("n", "Nat")] "Nat → Prop" (.some "motive"),
|
||||||
|
buildNamedGoal mvarMajor.name.toString [("n", "Nat")] "Nat",
|
||||||
|
buildNamedGoal mvarInduct.name.toString [("n", "Nat")] "∀ (t : Nat), Nat.below t → ?motive t",
|
||||||
|
buildNamedGoal mvarConduit.name.toString [("n", "Nat")] s!"?motive ?m.{major} = (n + 0 = n)" (.some "conduit")
|
||||||
|
])
|
||||||
|
|
||||||
|
let tactic := "intro x"
|
||||||
|
let state3m ← match ← state2.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state3m.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("n", "Nat"), ("x", "Nat")] "Prop" (.some "motive")])
|
||||||
|
let tactic := "apply Eq"
|
||||||
|
let state3m2 ← match ← state3m.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let [eqL, eqR, eqT] := state3m2.goals | fail "Incorrect number of goals"
|
||||||
|
let [_motive, _major, _step, conduit] := state2.goals | panic! "Goals conflict"
|
||||||
|
let state2b ← match state3m2.resume [conduit] with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let cNatAdd := "(:c HAdd.hAdd) (:c Nat) (:c Nat) (:c Nat) ((:c instHAdd) (:c Nat) (:c instAddNat))"
|
||||||
|
let cNat0 := "((:c OfNat.ofNat) (:c Nat) (:lit 0) ((:c instOfNatNat) (:lit 0)))"
|
||||||
|
let fvN ← state2b.withContext conduit do
|
||||||
|
let lctx ← getLCtx
|
||||||
|
pure $ lctx.getFVarIds.get! 0 |>.name
|
||||||
|
let conduitRight := s!"((:c Eq) (:c Nat) ({cNatAdd} (:fv {fvN}) {cNat0}) (:fv {fvN}))"
|
||||||
|
let substOf (mvarId: MVarId) := s!"(:subst (:mv {mvarId.name}) (:fv {fvN}) (:mv {mvarMajor}))"
|
||||||
|
let .num _ nL := eqL.name | fail "Incorrect form of mvar id"
|
||||||
|
let .num _ nR := eqR.name | fail "Incorrect form of mvar id"
|
||||||
|
let nL' := nL + 4
|
||||||
|
let nR' := nR + 5
|
||||||
|
addTest $ LSpec.check "resume" ((← state2b.serializeGoals (options := { ← read with printExprAST := true })) =
|
||||||
|
#[
|
||||||
|
{
|
||||||
|
name := mvarConduit.name.toString,
|
||||||
|
userName? := .some "conduit",
|
||||||
|
target := {
|
||||||
|
pp? := .some s!"(?m.{nL'} ?m.{major} = ?m.{nR'} ?m.{major}) = (n + 0 = n)",
|
||||||
|
sexp? := .some s!"((:c Eq) (:sort 0) ((:c Eq) {substOf eqT} {substOf eqL} {substOf eqR}) {conduitRight})",
|
||||||
|
},
|
||||||
|
vars := #[{
|
||||||
|
name := fvN.toString,
|
||||||
|
userName := "n",
|
||||||
|
type? := .some { pp? := .some "Nat", sexp? := .some "(:c Nat)" },
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_tactic_failure_unresolved_goals : TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (p : Nat → Prop), ∃ (x : Nat), p (0 + x + 0)")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "intro p"
|
||||||
|
let state1 ← match ← state0.tacticOn 0 tactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "exact ⟨0, by simp⟩"
|
||||||
|
let .failure messages ← state1.tacticOn 0 tactic | addTest $ assertUnreachable s!"{tactic} should fail"
|
||||||
|
checkEq s!"{tactic} fails" messages #[s!"{← getFileName}:0:12: error: unsolved goals\np : Nat → Prop\n⊢ p 0\n"]
|
||||||
|
|
||||||
|
def test_tactic_failure_synthesize_placeholder : TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (p q r : Prop) (h : p → q), q ∧ r")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let tactic := "intro p q r h"
|
||||||
|
let state1 ← match ← state0.tacticOn 0 tactic with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
|
||||||
|
let iex : InternalExceptionId := { idx := 4 }
|
||||||
|
IO.println s!"{← iex.getName}"
|
||||||
|
let tactic := "simpa [h] using And.imp_left h _"
|
||||||
|
--let state2 ← match ← state1.tacticOn 0 tactic with
|
||||||
|
-- | .success state => pure state
|
||||||
|
-- | other => do
|
||||||
|
-- addTest $ assertUnreachable $ other.toString
|
||||||
|
-- return ()
|
||||||
|
|
||||||
|
-- Volatile behaviour. This easily changes across Lean versions
|
||||||
|
|
||||||
|
--checkEq tactic ((← state2.serializeGoals).map (·.devolatilize)) #[
|
||||||
|
-- buildGoal [("p", "Prop"), ("q", "Prop"), ("r", "Prop"), ("h", "p → q")] "p ∧ r"
|
||||||
|
--]
|
||||||
|
|
||||||
|
let .failure messages ← state1.tacticOn 0 tactic | addTest $ assertUnreachable s!"{tactic} should fail"
|
||||||
|
let message := s!"<Pantograph>:0:31: error: don't know how to synthesize placeholder\ncontext:\np q r : Prop\nh : p → q\n⊢ p ∧ r\n"
|
||||||
|
checkEq s!"{tactic} fails" messages #[message]
|
||||||
|
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests := [
|
||||||
|
("identity", test_identity),
|
||||||
|
("Nat.add_comm", test_nat_add_comm false),
|
||||||
|
("Nat.add_comm manual", test_nat_add_comm true),
|
||||||
|
("Nat.add_comm delta", test_delta_variable),
|
||||||
|
("arithmetic", test_arith),
|
||||||
|
("Or.comm", test_or_comm),
|
||||||
|
("conv", test_conv),
|
||||||
|
("calc", test_calc),
|
||||||
|
("Nat.zero_add", test_nat_zero_add),
|
||||||
|
("Nat.zero_add alt", test_nat_zero_add_alt),
|
||||||
|
("tactic failure with unresolved goals", test_tactic_failure_unresolved_goals),
|
||||||
|
("tactic failure with synthesize placeholder", test_tactic_failure_synthesize_placeholder),
|
||||||
|
]
|
||||||
|
tests.map (fun (name, test) => (name, proofRunner env test))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph.Test.Proofs
|
|
@ -0,0 +1,109 @@
|
||||||
|
import LSpec
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
import Pantograph.Library
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Serial
|
||||||
|
|
||||||
|
def tempPath : IO System.FilePath := do
|
||||||
|
Prod.snd <$> IO.FS.createTempFile
|
||||||
|
|
||||||
|
structure MultiState where
|
||||||
|
coreContext : Core.Context
|
||||||
|
env: Environment
|
||||||
|
|
||||||
|
abbrev TestM := TestT $ StateRefT MultiState $ IO
|
||||||
|
|
||||||
|
instance : MonadEnv TestM where
|
||||||
|
getEnv := return (← getThe MultiState).env
|
||||||
|
modifyEnv f := do modifyThe MultiState fun s => { s with env := f s.env }
|
||||||
|
|
||||||
|
def runCoreM { α } (state : Core.State) (testCoreM : TestT CoreM α) : TestM (α × Core.State) := do
|
||||||
|
let multiState ← getThe MultiState
|
||||||
|
let coreM := runTestWithResult testCoreM
|
||||||
|
match ← (coreM.run multiState.coreContext state).toBaseIO with
|
||||||
|
| .error e => do
|
||||||
|
throw $ .userError $ ← e.toMessageData.toString
|
||||||
|
| .ok ((a, tests), state') => do
|
||||||
|
set $ (← getThe LSpec.TestSeq) ++ tests
|
||||||
|
return (a, state')
|
||||||
|
|
||||||
|
def test_environment_pickling : TestM Unit := do
|
||||||
|
let coreSrc : Core.State := { env := ← getEnv }
|
||||||
|
let coreDst : Core.State := { env := ← getEnv }
|
||||||
|
|
||||||
|
let name := `mystery
|
||||||
|
let envPicklePath ← tempPath
|
||||||
|
let ((), _) ← runCoreM coreSrc do
|
||||||
|
let type: Expr := .forallE `p (.sort 0) (.forallE `h (.bvar 0) (.bvar 1) .default) .default
|
||||||
|
let value: Expr := .lam `p (.sort 0) (.lam `h (.bvar 0) (.bvar 0) .default) .default
|
||||||
|
let c := Lean.Declaration.defnDecl <| Lean.mkDefinitionValEx
|
||||||
|
(name := name)
|
||||||
|
(levelParams := [])
|
||||||
|
(type := type)
|
||||||
|
(value := value)
|
||||||
|
(hints := Lean.mkReducibilityHintsRegularEx 1)
|
||||||
|
(safety := Lean.DefinitionSafety.safe)
|
||||||
|
(all := [])
|
||||||
|
let env' ← match (← getEnv).addDecl (← getOptions) c with
|
||||||
|
| .error e => do
|
||||||
|
let error ← (e.toMessageData (← getOptions)).toString
|
||||||
|
throwError error
|
||||||
|
| .ok env' => pure env'
|
||||||
|
environmentPickle env' envPicklePath
|
||||||
|
|
||||||
|
let _ ← runCoreM coreDst do
|
||||||
|
let (env', _) ← environmentUnpickle envPicklePath
|
||||||
|
checkTrue s!"Has symbol {name}" (env'.find? name).isSome
|
||||||
|
let anotherName := `mystery2
|
||||||
|
checkTrue s!"Doesn't have symbol {anotherName}" (env'.find? anotherName).isNone
|
||||||
|
|
||||||
|
IO.FS.removeFile envPicklePath
|
||||||
|
|
||||||
|
def test_goal_state_pickling_simple : TestM Unit := do
|
||||||
|
let coreSrc : Core.State := { env := ← getEnv }
|
||||||
|
let coreDst : Core.State := { env := ← getEnv }
|
||||||
|
let statePath ← tempPath
|
||||||
|
|
||||||
|
let type: Expr := .forallE `p (.sort 0) (.forallE `h (.bvar 0) (.bvar 1) .default) .default
|
||||||
|
let stateGenerate : MetaM GoalState := runTermElabMInMeta do
|
||||||
|
GoalState.create type
|
||||||
|
|
||||||
|
let ((), _) ← runCoreM coreSrc do
|
||||||
|
let state ← stateGenerate.run'
|
||||||
|
goalStatePickle state statePath
|
||||||
|
|
||||||
|
let ((), _) ← runCoreM coreDst do
|
||||||
|
let (goalState, _) ← goalStateUnpickle statePath (← getEnv)
|
||||||
|
let metaM : MetaM (List Expr) := do
|
||||||
|
goalState.goals.mapM λ goal => goalState.withContext goal goal.getType
|
||||||
|
let types ← metaM.run'
|
||||||
|
checkTrue "Goals" $ types[0]!.equal type
|
||||||
|
|
||||||
|
IO.FS.removeFile statePath
|
||||||
|
|
||||||
|
structure Test where
|
||||||
|
name : String
|
||||||
|
routine: TestM Unit
|
||||||
|
|
||||||
|
protected def Test.run (test: Test) (env: Lean.Environment) : IO LSpec.TestSeq := do
|
||||||
|
-- Create the state
|
||||||
|
let state : MultiState := {
|
||||||
|
coreContext := ← createCoreContext #[],
|
||||||
|
env,
|
||||||
|
}
|
||||||
|
match ← ((runTest $ test.routine).run' state).toBaseIO with
|
||||||
|
| .ok e => return e
|
||||||
|
| .error e =>
|
||||||
|
return LSpec.check s!"Emitted exception: {e.toString}" (e.toString == "")
|
||||||
|
|
||||||
|
def suite (env : Lean.Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests: List Test := [
|
||||||
|
{ name := "environment_pickling", routine := test_environment_pickling, },
|
||||||
|
{ name := "goal_state_pickling_simple", routine := test_goal_state_pickling_simple, },
|
||||||
|
]
|
||||||
|
tests.map (fun test => (test.name, test.run env))
|
||||||
|
|
||||||
|
end Pantograph.Test.Serial
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Test.Tactic.Assign
|
||||||
|
import Test.Tactic.Congruence
|
||||||
|
import Test.Tactic.MotivatedApply
|
||||||
|
import Test.Tactic.NoConfuse
|
||||||
|
import Test.Tactic.Prograde
|
|
@ -0,0 +1,33 @@
|
||||||
|
import Lean.Meta
|
||||||
|
import Lean.Elab
|
||||||
|
import LSpec
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Tactic.Assign
|
||||||
|
|
||||||
|
def test_draft : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "forall (p : Prop), (p ∨ p) ∨ p"
|
||||||
|
let skeleton := "by\nhave a : p ∨ p := sorry\nsorry"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.forallTelescope expr $ λ _ body => do
|
||||||
|
let skeleton' ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := skeleton)
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalDraft skeleton'
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ g => do exprToStr (← g.getType))) = ["p ∨ p", "(p ∨ p) ∨ p"])
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("draft", test_draft),
|
||||||
|
] |>.map (λ (name, t) => (name, runTestTermElabM env t))
|
||||||
|
|
||||||
|
end Pantograph.Test.Tactic.Assign
|
|
@ -0,0 +1,88 @@
|
||||||
|
import LSpec
|
||||||
|
import Lean
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Tactic.Congruence
|
||||||
|
|
||||||
|
def test_congr_arg_list : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ {α} (l1 l2 : List α) (h: l1 = l2) => l1.reverse = l2.reverse"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let newGoals ← runTacticOnMVar Tactic.evalCongruenceArg target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ x => mvarUserNameAndType x)) =
|
||||||
|
[
|
||||||
|
(`α, "Sort ?u.30"),
|
||||||
|
(`a₁, "?α"),
|
||||||
|
(`a₂, "?α"),
|
||||||
|
(`f, "?α → List α"),
|
||||||
|
(`h, "?a₁ = ?a₂"),
|
||||||
|
(`conduit, "(?f ?a₁ = ?f ?a₂) = (l1.reverse = l2.reverse)"),
|
||||||
|
])
|
||||||
|
let f := newGoals.get! 3
|
||||||
|
let h := newGoals.get! 4
|
||||||
|
let c := newGoals.get! 5
|
||||||
|
let results ← Meta.withAssignableSyntheticOpaque do f.apply (← parseSentence "List.reverse")
|
||||||
|
addTest $ LSpec.check "apply" (results.length = 0)
|
||||||
|
addTest $ LSpec.check "h" ((← exprToStr $ ← h.getType) = "?a₁ = ?a₂")
|
||||||
|
addTest $ LSpec.check "conduit" ((← exprToStr $ ← c.getType) = "(List.reverse ?a₁ = List.reverse ?a₂) = (l1.reverse = l2.reverse)")
|
||||||
|
def test_congr_arg : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n m: Nat) (h: n = m) => n * n = m * m"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let newGoals ← runTacticOnMVar Tactic.evalCongruenceArg target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ x => mvarUserNameAndType x)) =
|
||||||
|
[
|
||||||
|
(`α, "Sort ?u.73"),
|
||||||
|
(`a₁, "?α"),
|
||||||
|
(`a₂, "?α"),
|
||||||
|
(`f, "?α → Nat"),
|
||||||
|
(`h, "?a₁ = ?a₂"),
|
||||||
|
(`conduit, "(?f ?a₁ = ?f ?a₂) = (n * n = m * m)"),
|
||||||
|
])
|
||||||
|
def test_congr_fun : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n m: Nat) => (n + m) + (n + m) = (n + m) * 2"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let newGoals ← runTacticOnMVar Tactic.evalCongruenceFun target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ x => mvarUserNameAndType x)) =
|
||||||
|
[
|
||||||
|
(`α, "Sort ?u.165"),
|
||||||
|
(`f₁, "?α → Nat"),
|
||||||
|
(`f₂, "?α → Nat"),
|
||||||
|
(`h, "?f₁ = ?f₂"),
|
||||||
|
(`a, "?α"),
|
||||||
|
(`conduit, "(?f₁ ?a = ?f₂ ?a) = (n + m + (n + m) = (n + m) * 2)"),
|
||||||
|
])
|
||||||
|
def test_congr : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (a b: Nat) => a = b"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let newGoals ← runTacticOnMVar Tactic.evalCongruence target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ x => mvarUserNameAndType x)) =
|
||||||
|
[
|
||||||
|
(`α, "Sort ?u.10"),
|
||||||
|
(`f₁, "?α → Nat"),
|
||||||
|
(`f₂, "?α → Nat"),
|
||||||
|
(`a₁, "?α"),
|
||||||
|
(`a₂, "?α"),
|
||||||
|
(`h₁, "?f₁ = ?f₂"),
|
||||||
|
(`h₂, "?a₁ = ?a₂"),
|
||||||
|
(`conduit, "(?f₁ ?a₁ = ?f₂ ?a₂) = (a = b)"),
|
||||||
|
])
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("congrArg List.reverse", test_congr_arg_list),
|
||||||
|
("congrArg", test_congr_arg),
|
||||||
|
("congrFun", test_congr_fun),
|
||||||
|
("congr", test_congr),
|
||||||
|
] |>.map (λ (name, t) => (name, runTestTermElabM env t))
|
||||||
|
|
||||||
|
end Pantograph.Test.Tactic.Congruence
|
|
@ -0,0 +1,113 @@
|
||||||
|
import LSpec
|
||||||
|
import Lean
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Tactic.MotivatedApply
|
||||||
|
|
||||||
|
def test_type_extract : TestT Elab.TermElabM Unit := do
|
||||||
|
let recursor ← parseSentence "@Nat.brecOn"
|
||||||
|
let recursorType ← Meta.inferType recursor
|
||||||
|
addTest $ LSpec.check "recursorType" ("{motive : Nat → Sort ?u.1} → (t : Nat) → ((t : Nat) → Nat.below t → motive t) → motive t" =
|
||||||
|
(← exprToStr recursorType))
|
||||||
|
let info ← match Tactic.getRecursorInformation recursorType with
|
||||||
|
| .some info => pure info
|
||||||
|
| .none => throwError "Failed to extract recursor info"
|
||||||
|
addTest $ LSpec.check "iMotive" (info.iMotive = 2)
|
||||||
|
let motiveType := info.getMotiveType
|
||||||
|
addTest $ LSpec.check "motiveType" ("Nat → Sort ?u.1" =
|
||||||
|
(← exprToStr motiveType))
|
||||||
|
|
||||||
|
def test_nat_brec_on : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n t: Nat) => n + 0 = n"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "@Nat.brecOn")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalMotivatedApply recursor
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
let test := LSpec.check "goals" ((← newGoals.mapM (λ g => do exprToStr (← g.getType))) =
|
||||||
|
[
|
||||||
|
"Nat → Prop",
|
||||||
|
"Nat",
|
||||||
|
"∀ (t : Nat), Nat.below t → ?motive t",
|
||||||
|
"?motive ?m.74 = (n + 0 = n)",
|
||||||
|
])
|
||||||
|
addTest test
|
||||||
|
|
||||||
|
def test_list_brec_on : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ {α : Type} (l: List α) => l ++ [] = [] ++ l"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "@List.brecOn")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalMotivatedApply recursor
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ g => do exprToStr (← g.getType))) =
|
||||||
|
[
|
||||||
|
"Type ?u.90",
|
||||||
|
"List ?m.92 → Prop",
|
||||||
|
"List ?m.92",
|
||||||
|
"∀ (t : List ?m.92), List.below t → ?motive t",
|
||||||
|
"?motive ?m.94 = (l ++ [] = [] ++ l)",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_partial_motive_instantiation : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n t: Nat) => n + 0 = n"
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "@Nat.brecOn")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalMotivatedApply recursor
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
let majorId := 74
|
||||||
|
addTest $ (LSpec.check "goals" ((← newGoals.mapM (λ g => do exprToStr (← g.getType))) =
|
||||||
|
[
|
||||||
|
"Nat → Prop",
|
||||||
|
"Nat",
|
||||||
|
"∀ (t : Nat), Nat.below t → ?motive t",
|
||||||
|
s!"?motive ?m.{majorId} = (n + 0 = n)",
|
||||||
|
]))
|
||||||
|
let [motive, major, step, conduit] := newGoals | panic! "Incorrect goal number"
|
||||||
|
addTest $ (LSpec.check "goal name" (major.name.toString = s!"_uniq.{majorId}"))
|
||||||
|
|
||||||
|
-- Assign motive to `λ x => x + _`
|
||||||
|
let motive_assign ← parseSentence "λ (x: Nat) => @Nat.add x + 0 = _"
|
||||||
|
motive.assign motive_assign
|
||||||
|
|
||||||
|
addTest $ ← conduit.withContext do
|
||||||
|
let t := toString (← Meta.ppExpr $ ← conduit.getType)
|
||||||
|
return LSpec.check "conduit" (t = s!"(Nat.add ?m.{majorId} + 0 = ?m.149 ?m.{majorId}) = (n + 0 = n)")
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("type_extract", test_type_extract),
|
||||||
|
("Nat.brecOn", test_nat_brec_on),
|
||||||
|
("List.brecOn", test_list_brec_on),
|
||||||
|
("Nat.brecOn partial motive instantiation", test_partial_motive_instantiation),
|
||||||
|
] |>.map (λ (name, t) => (name, runTestTermElabM env t))
|
||||||
|
|
||||||
|
end Pantograph.Test.Tactic.MotivatedApply
|
|
@ -0,0 +1,72 @@
|
||||||
|
import LSpec
|
||||||
|
import Lean
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Tactic.NoConfuse
|
||||||
|
|
||||||
|
def test_nat : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n: Nat) (h: 0 = n + 1) => False"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "h")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalNoConfuse recursor
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals" ((← newGoals.mapM (λ g => do exprToStr (← g.getType))) = [])
|
||||||
|
|
||||||
|
def test_nat_fail : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (n: Nat) (h: n = n) => False"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "h")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
try
|
||||||
|
let tactic := Tactic.evalNoConfuse recursor
|
||||||
|
let _ ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
addTest $ assertUnreachable "Tactic should fail"
|
||||||
|
catch _ =>
|
||||||
|
addTest $ LSpec.check "Tactic should fail" true
|
||||||
|
|
||||||
|
def test_list : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "λ (l: List Nat) (h: [] = 1 :: l) => False"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.lambdaTelescope expr $ λ _ body => do
|
||||||
|
let recursor ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "h")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let target ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let tactic := Tactic.evalNoConfuse recursor
|
||||||
|
let newGoals ← runTacticOnMVar tactic target.mvarId!
|
||||||
|
addTest $ LSpec.check "goals"
|
||||||
|
((← newGoals.mapM (λ g => do exprToStr (← g.getType))) = [])
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("Nat", test_nat),
|
||||||
|
("Nat fail", test_nat_fail),
|
||||||
|
("List", test_list),
|
||||||
|
] |>.map (λ (name, t) => (name, runTestTermElabM env t))
|
||||||
|
|
||||||
|
end Pantograph.Test.Tactic.NoConfuse
|
|
@ -0,0 +1,300 @@
|
||||||
|
import LSpec
|
||||||
|
import Lean
|
||||||
|
import Test.Common
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Tactic.Prograde
|
||||||
|
|
||||||
|
def test_define : TestT Elab.TermElabM Unit := do
|
||||||
|
let expr := "forall (p q : Prop) (h: p), And (Or p q) (Or p q)"
|
||||||
|
let expr ← parseSentence expr
|
||||||
|
Meta.forallTelescope expr $ λ _ body => do
|
||||||
|
let e ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := `term)
|
||||||
|
(input := "Or.inl h")
|
||||||
|
(fileName := ← getFileName) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => throwError "Failed to parse: {error}"
|
||||||
|
-- Apply the tactic
|
||||||
|
let goal ← Meta.mkFreshExprSyntheticOpaqueMVar body
|
||||||
|
let target: Expr := mkAnd
|
||||||
|
(mkOr (.fvar ⟨uniq 8⟩) (.fvar ⟨uniq 9⟩))
|
||||||
|
(mkOr (.fvar ⟨uniq 8⟩) (.fvar ⟨uniq 9⟩))
|
||||||
|
let h := .fvar ⟨uniq 8⟩
|
||||||
|
addTest $ LSpec.test "goals before" ((← toCondensedGoal goal.mvarId!).devolatilize == {
|
||||||
|
context := #[
|
||||||
|
cdeclOf `p (.sort 0),
|
||||||
|
cdeclOf `q (.sort 0),
|
||||||
|
cdeclOf `h h
|
||||||
|
],
|
||||||
|
target,
|
||||||
|
})
|
||||||
|
let tactic := Tactic.evalDefine `h2 e
|
||||||
|
let m := .mvar ⟨uniq 13⟩
|
||||||
|
let [newGoal] ← runTacticOnMVar tactic goal.mvarId! | panic! "Incorrect goal number"
|
||||||
|
addTest $ LSpec.test "goals after" ((← toCondensedGoal newGoal).devolatilize == {
|
||||||
|
context := #[
|
||||||
|
cdeclOf `p (.sort 0),
|
||||||
|
cdeclOf `q (.sort 0),
|
||||||
|
cdeclOf `h h,
|
||||||
|
{
|
||||||
|
userName := `h2,
|
||||||
|
type := mkOr h m,
|
||||||
|
value? := .some $ mkApp3 (mkConst `Or.inl) h m (.fvar ⟨uniq 10⟩)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
target,
|
||||||
|
})
|
||||||
|
let .some e ← getExprMVarAssignment? goal.mvarId! | panic! "Tactic must assign"
|
||||||
|
addTest $ LSpec.test "assign" e.isLet
|
||||||
|
|
||||||
|
def test_define_proof : TestT Elab.TermElabM Unit := do
|
||||||
|
let rootExpr ← parseSentence "∀ (p q: Prop), p → ((p ∨ q) ∨ (p ∨ q))"
|
||||||
|
let state0 ← GoalState.create rootExpr
|
||||||
|
let tactic := "intro p q h"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("p", "Prop"), ("q", "Prop"), ("h", "p")] "(p ∨ q) ∨ p ∨ q"])
|
||||||
|
|
||||||
|
let expr := "Or.inl (Or.inl h)"
|
||||||
|
let state2 ← match ← state1.tryAssign (state1.get! 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state2.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let evalBind := "y"
|
||||||
|
let evalExpr := "Or.inl h"
|
||||||
|
let state2 ← match ← state1.tryDefine (state1.get! 0) (binderName := evalBind) (expr := evalExpr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!"eval {evalBind} := {evalExpr}" ((← state2.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[{
|
||||||
|
target := { pp? := .some "(p ∨ q) ∨ p ∨ q"},
|
||||||
|
vars := #[
|
||||||
|
{ userName := "p", type? := .some { pp? := .some "Prop" } },
|
||||||
|
{ userName := "q", type? := .some { pp? := .some "Prop" } },
|
||||||
|
{ userName := "h", type? := .some { pp? := .some "p" } },
|
||||||
|
{ userName := "y",
|
||||||
|
type? := .some { pp? := .some "p ∨ ?m.25" },
|
||||||
|
value? := .some { pp? := .some "Or.inl h" },
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
|
||||||
|
let expr := "Or.inl y"
|
||||||
|
let state3 ← match ← state2.tryAssign (state2.get! 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state3.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
addTest $ LSpec.check "(3 root)" state3.rootExpr?.isSome
|
||||||
|
|
||||||
|
def fun_define_root_expr: ∀ (p: Prop), PProd (Nat → p) Unit → p := by
|
||||||
|
intro p x
|
||||||
|
apply x.fst
|
||||||
|
exact 5
|
||||||
|
|
||||||
|
def test_define_root_expr : TestT Elab.TermElabM Unit := do
|
||||||
|
--let rootExpr ← parseSentence "Nat"
|
||||||
|
--let state0 ← GoalState.create rootExpr
|
||||||
|
--let .success state1 ← state0.tacticOn (goalId := 0) "exact 5" | addTest $ assertUnreachable "exact 5"
|
||||||
|
--let .some rootExpr := state1.rootExpr? | addTest $ assertUnreachable "Root expr"
|
||||||
|
--addTest $ LSpec.check "root" ((toString $ ← Meta.ppExpr rootExpr) = "5")
|
||||||
|
let rootExpr ← parseSentence "∀ (p: Prop), PProd (Nat → p) Unit → p"
|
||||||
|
let state0 ← GoalState.create rootExpr
|
||||||
|
let tactic := "intro p x"
|
||||||
|
let .success state1 ← state0.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic
|
||||||
|
let binderName := `binder
|
||||||
|
let value := "x.fst"
|
||||||
|
let expr ← state1.goals[0]!.withContext $ strToTermSyntax value
|
||||||
|
let tacticM := Tactic.evalDefine binderName expr
|
||||||
|
let .success state2 ← state1.tryTacticM (state1.get! 0) tacticM | addTest $ assertUnreachable s!"define {binderName} := {value}"
|
||||||
|
let tactic := s!"apply {binderName}"
|
||||||
|
let .success state3 ← state2.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic
|
||||||
|
let tactic := s!"exact 5"
|
||||||
|
let .success state4 ← state3.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic
|
||||||
|
let .some rootExpr := state4.rootExpr? | addTest $ assertUnreachable "Root expr"
|
||||||
|
addTest $ LSpec.check "root" ((toString $ ← Meta.ppExpr rootExpr) = "fun p x =>\n let binder := x.fst;\n binder 5")
|
||||||
|
|
||||||
|
--set_option pp.all true
|
||||||
|
--#check @PSigma (α := Prop) (β := λ (p: Prop) => p)
|
||||||
|
--def test_define_root_expr : TestT Elab.TermElabM Unit := do
|
||||||
|
|
||||||
|
def test_have_proof : TestT Elab.TermElabM Unit := do
|
||||||
|
let rootExpr ← parseSentence "∀ (p q: Prop), p → ((p ∨ q) ∨ (p ∨ q))"
|
||||||
|
let state0 ← GoalState.create rootExpr
|
||||||
|
let tactic := "intro p q h"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[buildGoal [("p", "Prop"), ("q", "Prop"), ("h", "p")] "(p ∨ q) ∨ p ∨ q"])
|
||||||
|
|
||||||
|
let expr := "Or.inl (Or.inl h)"
|
||||||
|
let state2 ← match ← state1.tryAssign (state1.get! 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state2.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let haveBind := "y"
|
||||||
|
let haveType := "p ∨ q"
|
||||||
|
let state2 ← match ← state1.tryHave (state1.get! 0) (binderName := haveBind) (type := haveType) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!"have {haveBind}: {haveType}" ((← state2.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
buildGoal [("p", "Prop"), ("q", "Prop"), ("h", "p")] "p ∨ q",
|
||||||
|
buildGoal [("p", "Prop"), ("q", "Prop"), ("h", "p"), ("y", "p ∨ q")] "(p ∨ q) ∨ p ∨ q"
|
||||||
|
])
|
||||||
|
|
||||||
|
let expr := "Or.inl h"
|
||||||
|
let state3 ← match ← state2.tryAssign (state2.get! 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state3.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let state2b ← match state3.continue state2 with
|
||||||
|
| .ok state => pure state
|
||||||
|
| .error e => do
|
||||||
|
addTest $ assertUnreachable e
|
||||||
|
return ()
|
||||||
|
let expr := "Or.inl y"
|
||||||
|
let state4 ← match ← state2b.tryAssign (state2b.get! 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state4.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let .some rootExpr := state4.rootExpr? | addTest $ assertUnreachable "Root expr"
|
||||||
|
addTest $ LSpec.check "root" ((toString $ ← Meta.ppExpr rootExpr) = "fun p q h y => Or.inl y")
|
||||||
|
|
||||||
|
def test_let (specialized: Bool): TestT Elab.TermElabM Unit := do
|
||||||
|
let rootExpr ← parseSentence "∀ (p q: Prop), p → ((p ∨ q) ∨ (p ∨ q))"
|
||||||
|
let state0 ← GoalState.create rootExpr
|
||||||
|
let tactic := "intro a p h"
|
||||||
|
let state1 ← match ← state0.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state1.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[{
|
||||||
|
target := { pp? := .some mainTarget },
|
||||||
|
vars := interiorVars,
|
||||||
|
}])
|
||||||
|
|
||||||
|
let letType := "Nat"
|
||||||
|
let expr := s!"let b: {letType} := _; _"
|
||||||
|
let result2 ← match specialized with
|
||||||
|
| true => state1.tryLet (state1.get! 0) (binderName := "b") (type := letType)
|
||||||
|
| false => state1.tryAssign (state1.get! 0) (expr := expr)
|
||||||
|
let state2 ← match result2 with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let serializedState2 ← state2.serializeGoals
|
||||||
|
let letBindName := if specialized then "b" else "_1"
|
||||||
|
addTest $ LSpec.check expr (serializedState2.map (·.devolatilize) =
|
||||||
|
#[{
|
||||||
|
target := { pp? := .some letType },
|
||||||
|
vars := interiorVars,
|
||||||
|
userName? := .some letBindName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target := { pp? := .some mainTarget },
|
||||||
|
vars := interiorVars ++ #[{
|
||||||
|
userName := "b",
|
||||||
|
type? := .some { pp? := .some letType },
|
||||||
|
value? := .some { pp? := .some s!"?{letBindName}" },
|
||||||
|
}],
|
||||||
|
userName? := if specialized then .none else .some "_2",
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
let tactic := "exact 1"
|
||||||
|
let state3 ← match ← state2.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check tactic ((← state3.serializeGoals).map (·.devolatilize) = #[])
|
||||||
|
|
||||||
|
let state3r ← match state3.continue state2 with
|
||||||
|
| .error msg => do
|
||||||
|
addTest $ assertUnreachable $ msg
|
||||||
|
return ()
|
||||||
|
| .ok state => pure state
|
||||||
|
addTest $ LSpec.check "(continue)" ((← state3r.serializeGoals).map (·.devolatilize) =
|
||||||
|
#[
|
||||||
|
{
|
||||||
|
target := { pp? := .some mainTarget },
|
||||||
|
vars := interiorVars ++ #[{
|
||||||
|
userName := "b",
|
||||||
|
type? := .some { pp? := .some "Nat" },
|
||||||
|
value? := .some { pp? := .some "1" },
|
||||||
|
}],
|
||||||
|
userName? := if specialized then .none else .some "_2",
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
let tactic := "exact h"
|
||||||
|
match ← state3r.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .failure #[message] =>
|
||||||
|
addTest $ LSpec.check tactic (message = s!"type mismatch\n h\nhas type\n a : Prop\nbut is expected to have type\n {mainTarget} : Prop")
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
|
||||||
|
let tactic := "exact Or.inl (Or.inl h)"
|
||||||
|
let state4 ← match ← state3r.tacticOn (goalId := 0) (tactic := tactic) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.test "(4 root)" state4.rootExpr?.isSome
|
||||||
|
where
|
||||||
|
mainTarget := "(a ∨ p) ∨ a ∨ p"
|
||||||
|
interiorVars: Array Protocol.Variable := #[
|
||||||
|
{ userName := "a", type? := .some { pp? := .some "Prop" }, },
|
||||||
|
{ userName := "p", type? := .some { pp? := .some "Prop" }, },
|
||||||
|
{ userName := "h", type? := .some { pp? := .some "a" }, }
|
||||||
|
]
|
||||||
|
|
||||||
|
def suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("define", test_define),
|
||||||
|
("define proof", test_define_proof),
|
||||||
|
("define root expr", test_define_root_expr),
|
||||||
|
("have proof", test_have_proof),
|
||||||
|
("let via assign", test_let false),
|
||||||
|
("let via tryLet", test_let true),
|
||||||
|
] |>.map (λ (name, t) => (name, runTestTermElabM env t))
|
||||||
|
|
||||||
|
end Pantograph.Test.Tactic.Prograde
|
18
build.py
18
build.py
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess, shutil, os, stat
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# -- Install Panograph
|
|
||||||
# Define paths for Pantograph source and Pantograph Python interface
|
|
||||||
PATH_PANTOGRAPH = Path("./src")
|
|
||||||
PATH_PY = Path("./pantograph")
|
|
||||||
with subprocess.Popen(["lake", "build", "repl"], cwd=PATH_PANTOGRAPH) as p:
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
path_executable = PATH_PY / "pantograph-repl"
|
|
||||||
shutil.copyfile(PATH_PANTOGRAPH / ".lake/build/bin/repl", path_executable)
|
|
||||||
os.chmod(path_executable, os.stat(path_executable).st_mode | stat.S_IEXEC)
|
|
||||||
|
|
||||||
# -- Copy the Lean toolchain file to the specified path
|
|
||||||
shutil.copyfile(PATH_PANTOGRAPH / "lean-toolchain", PATH_PY / "lean-toolchain")
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="256"
|
||||||
|
height="256"
|
||||||
|
viewBox="0 0 67.733332 67.733333"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:zoom="5.1882633"
|
||||||
|
inkscape:cx="81.819286"
|
||||||
|
inkscape:cy="132.22151"
|
||||||
|
inkscape:window-width="3774"
|
||||||
|
inkscape:window-height="2126"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer2">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="33.866666,69.8437"
|
||||||
|
orientation="-1,0"
|
||||||
|
id="guide1"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-27.673679,33.866666"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide2"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="16.933333,29.94582"
|
||||||
|
orientation="-1,0"
|
||||||
|
id="guide3"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="50.799999,37.44627"
|
||||||
|
orientation="-1,0"
|
||||||
|
id="guide4"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="31.336956,16.933333"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide5"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="24.528038,25.4"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide6"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="33.866666,50.799999"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide7"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="32.770414,55.033333"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide8"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="25.347689,33.866666"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide9"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="25.347689,42.333333"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide10"
|
||||||
|
inkscape:locked="false"
|
||||||
|
inkscape:label=""
|
||||||
|
inkscape:color="rgb(0,134,229)" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer4"
|
||||||
|
inkscape:label="bg" />
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:label="Circle">
|
||||||
|
<path
|
||||||
|
id="path1"
|
||||||
|
style="fill:#666b98;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.0191989;stroke-miterlimit:3.4;fill-opacity:1"
|
||||||
|
d="M 33.866666 0.009818522 A 33.857067 33.857067 0 0 0 0.009818522 33.866666 A 33.857067 33.857067 0 0 0 33.866666 67.723514 A 33.857067 33.857067 0 0 0 67.723514 33.866666 A 33.857067 33.857067 0 0 0 33.866666 0.009818522 z M 33.866666 4.2416015 A 29.624933 29.624933 0 0 1 63.491731 33.866666 A 29.624933 29.624933 0 0 1 33.866666 63.491731 A 29.624933 29.624933 0 0 1 4.2416015 33.866666 A 29.624933 29.624933 0 0 1 33.866666 4.2416015 z " />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="Pantograph-Core">
|
||||||
|
<rect
|
||||||
|
style="fill:#666b98;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.01905;stroke-miterlimit:3.4"
|
||||||
|
id="rect8"
|
||||||
|
width="16.942858"
|
||||||
|
height="4.2257233"
|
||||||
|
x="33.866665"
|
||||||
|
y="12.7"
|
||||||
|
rx="0.58070719"
|
||||||
|
ry="0.34097314" />
|
||||||
|
<rect
|
||||||
|
style="fill:#666b98;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.01905;stroke-miterlimit:3.4"
|
||||||
|
id="rect1"
|
||||||
|
width="33.885715"
|
||||||
|
height="8.4211359"
|
||||||
|
x="16.933332"
|
||||||
|
y="42.333332"
|
||||||
|
rx="0.58070719"
|
||||||
|
ry="0.34097314" />
|
||||||
|
<path
|
||||||
|
style="fill:#666b98;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 42.338095,16.925724 -16.990406,8.474275 13.121218,16.923808 -4.602241,0.0095 -4.254289,0.0015 -8.564029,-16.934789 17.310554,-8.464751 z"
|
||||||
|
id="path10"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#666b98;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 46.53445,16.925724 26.018901,26.73418"
|
||||||
|
id="path11" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#666b98;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 21.048348,25.399999 4.352167,16.934808"
|
||||||
|
id="path12"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,59 @@
|
||||||
|
# Design Rationale
|
||||||
|
|
||||||
|
A great problem in machine learning is to use ML agents to automatically prove
|
||||||
|
mathematical theorems. This sort of proof necessarily involves *search*.
|
||||||
|
Compatibility for search is the main reason for creating Pantograph. The Lean 4
|
||||||
|
LSP interface is not conducive to search. Pantograph is designed with this in
|
||||||
|
mind. It emphasizes the difference between 3 views of a proof:
|
||||||
|
|
||||||
|
- **Presentation View**: The view of a written, polished proof. e.g. Mathlib and
|
||||||
|
math papers are almost always written in this form.
|
||||||
|
- **Search View**: The view of a proof exploration trajectory. This is not
|
||||||
|
explicitly supported by Lean LSP.
|
||||||
|
- **Kernel View**: The proof viewed as a set of metavariables.
|
||||||
|
|
||||||
|
Pantograph enables proof agents to operate on the search view.
|
||||||
|
|
||||||
|
## Name
|
||||||
|
|
||||||
|
The name Pantograph is a pun. It means two things
|
||||||
|
- A pantograph is an instrument for copying down writing. As an agent explores
|
||||||
|
the vast proof search space, Pantograph records the current state to ensure
|
||||||
|
the proof is sound.
|
||||||
|
- A pantograph is also an equipment for an electric train. It supplies power to
|
||||||
|
a locomotive. In comparison the (relatively) simple Pantograph software powers
|
||||||
|
theorem proving projects.
|
||||||
|
|
||||||
|
## Caveats and Limitations
|
||||||
|
|
||||||
|
Pantograph does not exactly mimic Lean LSP's behaviour. That would not grant the
|
||||||
|
flexibility it offers. To support tree search means Pantograph has to act
|
||||||
|
differently from Lean in some times, but never at the sacrifice of soundness.
|
||||||
|
|
||||||
|
- When Lean LSP says "don't know how to synthesize placeholder", this indicates
|
||||||
|
the human operator needs to manually move the cursor to the placeholder and
|
||||||
|
type in the correct expression. This error therefore should not halt the proof
|
||||||
|
process, and the placeholder should be turned into a goal.
|
||||||
|
- When Lean LSP says "unresolved goals", that means a proof cannot finish where
|
||||||
|
it is supposed to finish at the end of a `by` block. Pantograph will raise the
|
||||||
|
error in this case, since it indicates the termination of a proof search branch.
|
||||||
|
- `pick_goal` or `swap` will not work since they run contrary to tree search
|
||||||
|
paradigms. However, if there are tactics which perform non-trivial operations
|
||||||
|
to multiple goals at the same time, this constrain could potentially be
|
||||||
|
relaxed at a cost of great bookkeeping overhead to the user.
|
||||||
|
|
||||||
|
Pantograph cannot perform things that are inherently constrained by Lean. These
|
||||||
|
include:
|
||||||
|
|
||||||
|
- If a tactic loses track of metavariables, it will not be caught until the end
|
||||||
|
of the proof search. This is a bug in the tactic itself.
|
||||||
|
- Timeouts for executing tactics is not available. Maybe this will change in the
|
||||||
|
future.
|
||||||
|
- Interceptions of parsing errors generally cannot be turned into goals (e.g.
|
||||||
|
`def mystery : Nat := :=`) due to Lean's parsing system.
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
* [Pantograph Paper](https://arxiv.org/abs/2410.16429)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# REPL
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
See `Pantograph/Protocol.lean` for a description of the parameters and return values in JSON.
|
||||||
|
* `reset`: Delete all cached expressions and proof trees
|
||||||
|
* `stat`: Display resource usage
|
||||||
|
* `expr.echo {"expr": <expr>, "type": <optional expected type>, ["levels": [<levels>]]}`: Determine the
|
||||||
|
type of an expression and format it.
|
||||||
|
* `env.catalog`: Display a list of all safe Lean symbols in the current environment
|
||||||
|
* `env.inspect {"name": <name>, "value": <bool>}`: Show the type and package of a
|
||||||
|
given symbol; If value flag is set, the value is printed or hidden. By default
|
||||||
|
only the values of definitions are printed.
|
||||||
|
* `env.save { "path": <fileName> }`, `env.load { "path": <fileName> }`: Save/Load the
|
||||||
|
current environment to/from a file
|
||||||
|
* `options.set { key: value, ... }`: Set one or more options (not Lean options; those
|
||||||
|
have to be set via command line arguments.), for options, see `Pantograph/Protocol.lean`
|
||||||
|
|
||||||
|
One particular option for interest for machine learning researchers is the
|
||||||
|
automatic mode (flag: `"automaticMode"`). By default it is turned on, with
|
||||||
|
all goals automatically resuming. This makes Pantograph act like a gym,
|
||||||
|
with no resumption necessary to manage your goals.
|
||||||
|
* `options.print`: Display the current set of options
|
||||||
|
* `goal.start {["name": <name>], ["expr": <expr>], ["levels": [<levels>]], ["copyFrom": <symbol>]}`:
|
||||||
|
Start a new proof from a given expression or symbol
|
||||||
|
* `goal.tactic {"stateId": <id>, "goalId": <id>, ...}`: Execute a tactic string on a
|
||||||
|
given goal. The tactic is supplied as additional key-value pairs in one of the following formats:
|
||||||
|
- `{ "tactic": <tactic> }`: Execute an ordinary tactic
|
||||||
|
- `{ "expr": <expr> }`: Assign the given proof term to the current goal
|
||||||
|
- `{ "have": <expr>, "binderName": <name> }`: Execute `have` and creates a branch goal
|
||||||
|
- `{ "calc": <expr> }`: Execute one step of a `calc` tactic. Each step must
|
||||||
|
be of the form `lhs op rhs`. An `lhs` of `_` indicates that it should be set
|
||||||
|
to the previous `rhs`.
|
||||||
|
- `{ "conv": <bool> }`: Enter or exit conversion tactic mode. In the case of
|
||||||
|
exit, the goal id is ignored.
|
||||||
|
- `{ "draft": <expr> }`: Draft an expression with `sorry`s, turning them into goals. Coupling is not allowed.
|
||||||
|
* `goal.continue {"stateId": <id>, ["branch": <id>], ["goals": <names>]}`:
|
||||||
|
Execute continuation/resumption
|
||||||
|
- `{ "branch": <id> }`: Continue on branch state. The current state must have no goals.
|
||||||
|
- `{ "goals": <names> }`: Resume the given goals
|
||||||
|
* `goal.remove {"stateIds": [<id>]}"`: Drop the goal states specified in the list
|
||||||
|
* `goal.print {"stateId": <id>}"`: Print a goal state
|
||||||
|
* `goal.save { "id": <id>, "path": <fileName> }`, `goal.load { "path": <fileName> }`:
|
||||||
|
Save/Load a goal state to/from a file. The environment is not carried with the
|
||||||
|
state. The user is responsible to ensure the sender/receiver instances share
|
||||||
|
the same environment.
|
||||||
|
* `frontend.process { ["fileName": <fileName>,] ["file": <str>], invocations:
|
||||||
|
<bool>, sorrys: <bool>, typeErrorsAsGoals: <bool>, newConstants: <bool> }`:
|
||||||
|
Executes the Lean frontend on a file, collecting the tactic invocations
|
||||||
|
(`"invocations": true`), the sorrys and type errors into goal states
|
||||||
|
(`"sorrys": true`), and new constants (`"newConstants": true`). In the case of
|
||||||
|
`sorrys`, this command additionally outputs the position of each captured
|
||||||
|
`sorry`.
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
When an error pertaining to the execution of a command happens, the returning JSON structure is
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{ "error": "type", "desc": "description" }
|
||||||
|
```
|
||||||
|
Common error forms:
|
||||||
|
* `command`: Indicates malformed command structure which results from either
|
||||||
|
invalid command or a malformed JSON structure that cannot be fed to an
|
||||||
|
individual command.
|
||||||
|
* `index`: Indicates an invariant maintained by the output of one command and
|
||||||
|
input of another is broken. For example, attempting to query a symbol not
|
||||||
|
existing in the library or indexing into a non-existent proof state.
|
|
@ -1 +0,0 @@
|
||||||
/_build
|
|
|
@ -1,71 +0,0 @@
|
||||||
# Book settings
|
|
||||||
# Learn more at https://jupyterbook.org/customize/config.html
|
|
||||||
# Comprehensive example: https://github.com/executablebooks/jupyter-book/blob/master/docs/_config.yml
|
|
||||||
|
|
||||||
title: PyPantograph
|
|
||||||
author: Leni Aniva
|
|
||||||
#logo: logo.png
|
|
||||||
|
|
||||||
# Force re-execution of notebooks on each build.
|
|
||||||
# See https://jupyterbook.org/content/execute.html
|
|
||||||
execute:
|
|
||||||
execute_notebooks: 'off'
|
|
||||||
|
|
||||||
# Define the name of the latex output file for PDF builds
|
|
||||||
latex:
|
|
||||||
latex_documents:
|
|
||||||
targetname: book.tex
|
|
||||||
|
|
||||||
# Add a bibtex file so that we can create citations
|
|
||||||
#bibtex_bibfiles:
|
|
||||||
# - references.bib
|
|
||||||
|
|
||||||
# Information about where the book exists on the web
|
|
||||||
repository:
|
|
||||||
url: https://github.com/lenianiva/PyPantograph # Online location of your book
|
|
||||||
path_to_book: docs # Optional path to your book, relative to the repository root
|
|
||||||
branch: main # Which branch of the repository should be used when creating links (optional)
|
|
||||||
|
|
||||||
# Add GitHub buttons to your book
|
|
||||||
# See https://jupyterbook.org/customize/config.html#add-a-link-to-your-repository
|
|
||||||
html:
|
|
||||||
use_issues_button: true
|
|
||||||
use_repository_button: true
|
|
||||||
|
|
||||||
sphinx:
|
|
||||||
config:
|
|
||||||
intersphinx_mapping:
|
|
||||||
ebp:
|
|
||||||
- "https://executablebooks.org/en/latest/"
|
|
||||||
- null
|
|
||||||
myst-parser:
|
|
||||||
- "https://myst-parser.readthedocs.io/en/latest/"
|
|
||||||
- null
|
|
||||||
myst-nb:
|
|
||||||
- "https://myst-nb.readthedocs.io/en/latest/"
|
|
||||||
- null
|
|
||||||
sphinx:
|
|
||||||
- "https://www.sphinx-doc.org/en/master"
|
|
||||||
- null
|
|
||||||
nbformat:
|
|
||||||
- "https://nbformat.readthedocs.io/en/latest"
|
|
||||||
- null
|
|
||||||
sd:
|
|
||||||
- "https://sphinx-design.readthedocs.io/en/latest"
|
|
||||||
- null
|
|
||||||
sphinxproof:
|
|
||||||
- "https://sphinx-proof.readthedocs.io/en/latest/"
|
|
||||||
- null
|
|
||||||
hoverxref_intersphinx:
|
|
||||||
- "sphinxproof"
|
|
||||||
mathjax3_config:
|
|
||||||
tex:
|
|
||||||
macros:
|
|
||||||
"N": "\\mathbb{N}"
|
|
||||||
"floor": ["\\lfloor#1\\rfloor", 1]
|
|
||||||
"bmat": ["\\left[\\begin{array}"]
|
|
||||||
"emat": ["\\end{array}\\right]"]
|
|
||||||
|
|
||||||
extra_extensions:
|
|
||||||
- sphinx.ext.intersphinx
|
|
||||||
- sphinx.ext.autodoc
|
|
|
@ -1,16 +0,0 @@
|
||||||
format: jb-book
|
|
||||||
root: intro
|
|
||||||
parts:
|
|
||||||
- caption: Features
|
|
||||||
chapters:
|
|
||||||
- file: setup
|
|
||||||
- file: goal
|
|
||||||
- file: agent-search
|
|
||||||
- file: data
|
|
||||||
- file: drafting
|
|
||||||
- caption: API Documentation
|
|
||||||
chapters:
|
|
||||||
- file: api-server
|
|
||||||
- file: api-search
|
|
||||||
- file: api-expr
|
|
||||||
- file: api-data
|
|
|
@ -1,155 +0,0 @@
|
||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "ec3abb52-d7cd-471f-b3b7-2d9681c79360",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"# Search\n",
|
|
||||||
"\n",
|
|
||||||
"Pantograph supports basic proof search. In this case, Pantograph treats goals as nodes on an and-or tree. The user supplies an agent which should provide two functions:\n",
|
|
||||||
"\n",
|
|
||||||
"1. *Tactic*: Which tactic should be used on a goal?\n",
|
|
||||||
"2. *Guidance*: What is the search priority on a goal?\n",
|
|
||||||
"\n",
|
|
||||||
"The user agent should inherit from `pantograph.search.Agent`. Here is a brute force agent example:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"id": "959458f5-02e4-4f73-ae28-16a756aebed9",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from typing import Optional\n",
|
|
||||||
"import collections\n",
|
|
||||||
"from pantograph import Server\n",
|
|
||||||
"from pantograph.search import Agent\n",
|
|
||||||
"from pantograph.expr import GoalState, Tactic"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 2,
|
|
||||||
"id": "8b402602-3ae5-43e4-9a62-2fa9e2c039fa",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"class DumbAgent(Agent):\n",
|
|
||||||
"\n",
|
|
||||||
" def __init__(self):\n",
|
|
||||||
" super().__init__()\n",
|
|
||||||
"\n",
|
|
||||||
" self.goal_tactic_id_map = collections.defaultdict(lambda : 0)\n",
|
|
||||||
" self.intros = [\n",
|
|
||||||
" \"intro\",\n",
|
|
||||||
" ]\n",
|
|
||||||
" self.tactics = [\n",
|
|
||||||
" \"intro h\",\n",
|
|
||||||
" \"cases h\",\n",
|
|
||||||
" \"apply Or.inl\",\n",
|
|
||||||
" \"apply Or.inr\",\n",
|
|
||||||
" ]\n",
|
|
||||||
" self.no_space_tactics = [\n",
|
|
||||||
" \"assumption\",\n",
|
|
||||||
" ]\n",
|
|
||||||
"\n",
|
|
||||||
" def next_tactic(\n",
|
|
||||||
" self,\n",
|
|
||||||
" state: GoalState,\n",
|
|
||||||
" goal_id: int,\n",
|
|
||||||
" ) -> Optional[Tactic]:\n",
|
|
||||||
" key = (state.state_id, goal_id)\n",
|
|
||||||
" i = self.goal_tactic_id_map[key]\n",
|
|
||||||
"\n",
|
|
||||||
" target = state.goals[goal_id].target\n",
|
|
||||||
" if target.startswith('∀'):\n",
|
|
||||||
" tactics = self.intros\n",
|
|
||||||
" elif ' ' in target:\n",
|
|
||||||
" tactics = self.tactics\n",
|
|
||||||
" else:\n",
|
|
||||||
" tactics = self.no_space_tactics\n",
|
|
||||||
"\n",
|
|
||||||
" if i >= len(tactics):\n",
|
|
||||||
" return None\n",
|
|
||||||
"\n",
|
|
||||||
" self.goal_tactic_id_map[key] = i + 1\n",
|
|
||||||
" return tactics[i]"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "665db9d0-5fff-4b26-9cea-32d06a6e1e04",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Execute the search with `agent.search`."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 3,
|
|
||||||
"id": "1c7961d1-b1fa-498c-ab75-16feb784ca2c",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"SearchResult(n_goals_root=1, duration=0.7717759609222412, success=True, steps=16)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 3,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"server = Server()\n",
|
|
||||||
"agent = DumbAgent()\n",
|
|
||||||
"goal_state = server.goal_start(\"∀ (p q: Prop), Or p q -> Or q p\")\n",
|
|
||||||
"agent.search(server=server, goal_state=goal_state, verbose=False)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "141e0116-cbb6-4957-aaea-2a1100f80ece",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"## Automatic and Manual Modes\n",
|
|
||||||
"\n",
|
|
||||||
"The agent chooses one goal and executes a tactic on this goal. What happens to the other goals that are not chosen? By default, the server runs in automatic mode. In automatic mode, all other goals are automatically inherited by a child state, so a user agent could declare a proof finished when there are no more goals remaining in the current goal state.\n",
|
|
||||||
"\n",
|
|
||||||
"Some users may wish to handle sibling goals manually. For example, Aesop's treatment of metavariable coupling is not automatic. To do this, pass the flag `options={ \"automaticMode\" : False }` to the `Server` constructor."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "2090e538-d196-4923-937c-b83fedf1d9a2",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
Data
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: pantograph.data
|
|
||||||
:members:
|
|
|
@ -1,8 +0,0 @@
|
||||||
Expr
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: pantograph.expr
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autodata:: pantograph.expr.Expr
|
|
||||||
.. autodata:: pantograph.expr.Tactic
|
|
|
@ -1,5 +0,0 @@
|
||||||
Search
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: pantograph.search
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
Server
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: pantograph.server
|
|
||||||
:members:
|
|
244
docs/data.ipynb
244
docs/data.ipynb
|
@ -1,244 +0,0 @@
|
||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "fe7a3037-5c49-4097-9a5d-575b958cc7f8",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"# Data Extraction"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"id": "fc68ad1d-e64c-48b7-9461-50d872d30473",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import os\n",
|
|
||||||
"from pathlib import Path\n",
|
|
||||||
"from pantograph.server import Server"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "fd13c644-d731-4f81-964e-584bbd43e51c",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"## Tactic Invocation\n",
|
|
||||||
"\n",
|
|
||||||
"Pantograph can extract tactic invocation data from a Lean file. A **tactic\n",
|
|
||||||
"invocation** is a tuple containing the before and after goal states, and the\n",
|
|
||||||
"tactic which converts the \"before\" state to the \"after\" state.\n",
|
|
||||||
"\n",
|
|
||||||
"To extract tactic invocation data, use `server.tactic_invocations(file_name)`\n",
|
|
||||||
"and supply the file name of the input Lean file."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 2,
|
|
||||||
"id": "6282dc6f-4eac-4263-8277-9d54d19ad1a5",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"$PWD: /home/aniva/Projects/atp/PyPantograph/examples/Example\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"project_path = Path(os.getcwd()).parent.resolve() / 'examples/Example'\n",
|
|
||||||
"print(f\"$PWD: {project_path}\")\n",
|
|
||||||
"server = Server(imports=['Example'], project_path=project_path)\n",
|
|
||||||
"units = server.tactic_invocations(project_path / \"Example.lean\")"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "c3c1be91-27a5-4481-b09d-a32dbb94b058",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"The function returns a list of `CompilationUnit` objects, corresponding to each compilation unit in the input Lean file. For performance reasons only the text boundaries are loaded into `CompilationUnit`s."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 3,
|
|
||||||
"id": "e994aa2b-5d5e-4f86-af6c-40e0b3a032d2",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"#0: [14,85]\n",
|
|
||||||
"/-- Ensure that Aesop is running -/\n",
|
|
||||||
"example : α → α :=\n",
|
|
||||||
" by aesop\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"#1: [85,254]\n",
|
|
||||||
"example : ∀ (p q: Prop), p ∨ q → q ∨ p := by\n",
|
|
||||||
" intro p q h\n",
|
|
||||||
" -- Here are some comments\n",
|
|
||||||
" cases h\n",
|
|
||||||
" . apply Or.inr\n",
|
|
||||||
" assumption\n",
|
|
||||||
" . apply Or.inl\n",
|
|
||||||
" assumption\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"with open(project_path / \"Example.lean\", 'rb') as f:\n",
|
|
||||||
" content = f.read()\n",
|
|
||||||
" for i, unit in enumerate(units):\n",
|
|
||||||
" print(f\"#{i}: [{unit.i_begin},{unit.i_end}]\")\n",
|
|
||||||
" unit_text = content[unit.i_begin:unit.i_end].decode('utf-8')\n",
|
|
||||||
" print(unit_text)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "52e650fc-4a87-445f-8aa8-707ed9e36c03",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Each `CompilationUnit` includes a list of `TacticInvocation`s, which contains the `.before` (corresponding to the state before the tactic), `.after` (corresponding to the state after the tactic), and `.tactic` (tactic executed) fields. "
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 4,
|
|
||||||
"id": "8e0f0def-dd3c-4550-8a7c-b4aec6c7fd7f",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"[Before]\n",
|
|
||||||
"α : Sort ?u.7\n",
|
|
||||||
"⊢ α → α\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"aesop (using [])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"for i in units[0].invocations:\n",
|
|
||||||
" print(f\"[Before]\\n{i.before}\")\n",
|
|
||||||
" print(f\"[Tactic]\\n{i.tactic} (using {i.used_constants})\")\n",
|
|
||||||
" print(f\"[After]\\n{i.after}\")"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 5,
|
|
||||||
"id": "51f5398b-5416-4dc1-81cd-6d2514758232",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"[Before]\n",
|
|
||||||
"⊢ ∀ (p q : Prop), p ∨ q → q ∨ p\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"intro p q h (using [])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h : p ∨ q\n",
|
|
||||||
"⊢ q ∨ p\n",
|
|
||||||
"[Before]\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h : p ∨ q\n",
|
|
||||||
"⊢ q ∨ p\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"cases h (using ['Eq.refl', 'Or'])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"case inl\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : p\n",
|
|
||||||
"⊢ q ∨ p\n",
|
|
||||||
"case inr p q : Prop h✝ : q ⊢ q ∨ p\n",
|
|
||||||
"[Before]\n",
|
|
||||||
"case inl\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : p\n",
|
|
||||||
"⊢ q ∨ p\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"apply Or.inr (using ['Or.inr'])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"case inl.h\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : p\n",
|
|
||||||
"⊢ p\n",
|
|
||||||
"[Before]\n",
|
|
||||||
"case inl.h\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : p\n",
|
|
||||||
"⊢ p\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"assumption (using [])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"\n",
|
|
||||||
"[Before]\n",
|
|
||||||
"case inr\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : q\n",
|
|
||||||
"⊢ q ∨ p\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"apply Or.inl (using ['Or.inl'])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"case inr.h\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : q\n",
|
|
||||||
"⊢ q\n",
|
|
||||||
"[Before]\n",
|
|
||||||
"case inr.h\n",
|
|
||||||
"p q : Prop\n",
|
|
||||||
"h✝ : q\n",
|
|
||||||
"⊢ q\n",
|
|
||||||
"[Tactic]\n",
|
|
||||||
"assumption (using [])\n",
|
|
||||||
"[After]\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"for i in units[1].invocations:\n",
|
|
||||||
" print(f\"[Before]\\n{i.before}\")\n",
|
|
||||||
" print(f\"[Tactic]\\n{i.tactic} (using {i.used_constants})\")\n",
|
|
||||||
" print(f\"[After]\\n{i.after}\")"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "aecc5260-56ad-4734-8917-3a4d92910309",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"# Drafting\n",
|
|
||||||
"\n",
|
|
||||||
"Pantograph supports drafting (technically the sketch step) from\n",
|
|
||||||
"[Draft-Sketch-Prove](https://github.com/wellecks/ntptutorial/tree/main/partII_dsp).\n",
|
|
||||||
"Pantograph's drafting feature is more powerful. At any place in the proof, you\n",
|
|
||||||
"can replace an expression with `sorry`, and the `sorry` will become a goal. Any type errors will also become goals. In order to detect whether type errors have occurred, the user can look at the messages from each compilation unit.\n",
|
|
||||||
"\n",
|
|
||||||
"At this point we must introduce the idea of compilation units. Each Lean\n",
|
|
||||||
"definition, theorem, constant, etc., is a *compilation unit*. When Pantograph\n",
|
|
||||||
"extracts data from Lean source code, it sections the data into these compilation\n",
|
|
||||||
"units.\n",
|
|
||||||
"\n",
|
|
||||||
"For example, consider this sketch produced by a language model prover:\n",
|
|
||||||
"```lean\n",
|
|
||||||
"theorem add_comm_proved_formal_sketch : ∀ n m : Nat, n + m = m + n := by\n",
|
|
||||||
" intros n m\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" have h_base: 0 + m = m := sorry\n",
|
|
||||||
" have h_symm: m + 0 = m := sorry\n",
|
|
||||||
" sorry\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" have h_inductive: n + m = m + n := sorry\n",
|
|
||||||
" have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := sorry\n",
|
|
||||||
" have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := sorry\n",
|
|
||||||
" have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := sorry\n",
|
|
||||||
" sorry\n",
|
|
||||||
"```\n",
|
|
||||||
"There are some `sorry`s that we want to solve automatically with hammer tactics. We can do this by drafting. Feeding this into the drafting feature produces one goal state (corresponding to the one compilation unit) containing as many goals as the draft has `sorry`s:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"id": "52bd153d-235c-47fa-917e-415d444867a5",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"m : Nat\n",
|
|
||||||
"⊢ 0 + m = m\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"h_base : 0 + m = m\n",
|
|
||||||
"⊢ m + 0 = m\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"h_base : 0 + m = m\n",
|
|
||||||
"h_symm : m + 0 = m\n",
|
|
||||||
"⊢ 0 + m = m + 0\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"n : Nat\n",
|
|
||||||
"ih : n + m = m + n\n",
|
|
||||||
"⊢ n + m = m + n\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"n : Nat\n",
|
|
||||||
"ih : n + m = m + n\n",
|
|
||||||
"h_inductive : n + m = m + n\n",
|
|
||||||
"⊢ m + n.succ = (m + n).succ\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"n : Nat\n",
|
|
||||||
"ih : n + m = m + n\n",
|
|
||||||
"h_inductive : n + m = m + n\n",
|
|
||||||
"h_pull_succ_out_from_right : m + n.succ = (m + n).succ\n",
|
|
||||||
"⊢ (n + m).succ = (m + n).succ\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"n : Nat\n",
|
|
||||||
"ih : n + m = m + n\n",
|
|
||||||
"h_inductive : n + m = m + n\n",
|
|
||||||
"h_pull_succ_out_from_right : m + n.succ = (m + n).succ\n",
|
|
||||||
"h_flip_n_plus_m : (n + m).succ = (m + n).succ\n",
|
|
||||||
"⊢ n.succ + m = (n + m).succ\n",
|
|
||||||
"m : Nat\n",
|
|
||||||
"n : Nat\n",
|
|
||||||
"ih : n + m = m + n\n",
|
|
||||||
"h_inductive : n + m = m + n\n",
|
|
||||||
"h_pull_succ_out_from_right : m + n.succ = (m + n).succ\n",
|
|
||||||
"h_flip_n_plus_m : (n + m).succ = (m + n).succ\n",
|
|
||||||
"h_pull_succ_out_from_left : n.succ + m = (n + m).succ\n",
|
|
||||||
"⊢ n + 1 + m = m + (n + 1)\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"from pantograph import Server\n",
|
|
||||||
"\n",
|
|
||||||
"sketch = \"\"\"\n",
|
|
||||||
"theorem add_comm_proved_formal_sketch : ∀ n m : Nat, n + m = m + n := by\n",
|
|
||||||
" -- Consider some n and m in Nats.\n",
|
|
||||||
" intros n m\n",
|
|
||||||
" -- Perform induction on n.\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" -- Base case: When n = 0, we need to show 0 + m = m + 0.\n",
|
|
||||||
" -- We have the fact 0 + m = m by the definition of addition.\n",
|
|
||||||
" have h_base: 0 + m = m := sorry\n",
|
|
||||||
" -- We also have the fact m + 0 = m by the definition of addition.\n",
|
|
||||||
" have h_symm: m + 0 = m := sorry\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" sorry\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" -- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.\n",
|
|
||||||
" -- By the inductive hypothesis, we have n + m = m + n.\n",
|
|
||||||
" have h_inductive: n + m = m + n := sorry\n",
|
|
||||||
" -- 1. Note we start with: Nat.succ n + m = m + Nat.succ n, so, pull the succ out from m + Nat.succ n on the right side from the addition using addition facts Nat.add_succ.\n",
|
|
||||||
" have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := sorry\n",
|
|
||||||
" -- 2. then to flip m + S n to something like S (n + m) we need to use the IH.\n",
|
|
||||||
" have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := sorry\n",
|
|
||||||
" -- 3. Now the n & m are on the correct sides Nat.succ n + m = Nat.succ (n + m), so let's use the def of addition to pull out the succ from the addition on the left using Nat.succ_add.\n",
|
|
||||||
" have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := sorry\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" sorry\n",
|
|
||||||
"\"\"\"\n",
|
|
||||||
"\n",
|
|
||||||
"server = Server()\n",
|
|
||||||
"unit, = server.load_sorry(sketch)\n",
|
|
||||||
"print(unit.goal_state)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "0d4dda56-7b7f-4c4c-b59d-af6f857d7788",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"For an in-depth example, see `experiments/dsp`."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "eaf8e506-a6d1-4e9a-ad7a-f7bbb82e01c6",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
317
docs/goal.ipynb
317
docs/goal.ipynb
|
@ -1,317 +0,0 @@
|
||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "c5106980-4850-4bea-a333-5a1b2e4d1dc5",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"# Goals and Tactics\n",
|
|
||||||
"\n",
|
|
||||||
"Executing tactics in Pantograph is simple. To start a proof, call the\n",
|
|
||||||
"`Server.goal_start` function and supply an expression."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"id": "3257de2b-41ca-4cfe-b66c-1ef4781c98b0",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"from pantograph import Server\n",
|
|
||||||
"from pantograph.expr import TacticHave, TacticCalc, TacticExpr"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 2,
|
|
||||||
"id": "6783d478-d8c7-4c4e-a56e-8170384297ef",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"server = Server()\n",
|
|
||||||
"state0 = server.goal_start(\"forall (p q: Prop), Or p q -> Or q p\")"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "bfe5a9df-33c2-4538-a9ce-fc0e02c92ff2",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"This creates a *goal state*, which consists of a finite number of goals. In this\n",
|
|
||||||
"case since it is the beginning of a state, it has only one goal."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 3,
|
|
||||||
"id": "eefc9094-9574-4f92-9aa2-c39beb85389b",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"\n",
|
|
||||||
"⊢ forall (p q: Prop), Or p q -> Or q p\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"print(state0)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "26dbe212-e09e-42dd-ab15-65ee2fba6234",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"To execute a tactic on a goal state, use `Server.goal_tactic`. This function\n",
|
|
||||||
"takes a goal id and a tactic. Most Lean tactics are strings."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 4,
|
|
||||||
"id": "c907dbb6-4d6a-4aa7-b173-60220165ba9e",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"a : Prop\n",
|
|
||||||
"⊢ ∀ (q : Prop), a ∨ q → q ∨ a\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"state1 = server.goal_tactic(state0, goal_id=0, tactic=\"intro a\")\n",
|
|
||||||
"print(state1)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "9978fdcf-a12b-4f22-9551-5e04c262e5e0",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Executing a tactic produces a new goal state. If this goal state has no goals,\n",
|
|
||||||
"the proof is complete. You can recover the usual form of a goal with `str()`"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 5,
|
|
||||||
"id": "16595c5e-2285-49d5-8340-397ad1e6c9e7",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"'a : Prop\\n⊢ ∀ (q : Prop), a ∨ q → q ∨ a'"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 5,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"str(state1.goals[0])"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "67f2a75d-6851-4393-bac9-a091400f1906",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"## Error Handling and GC\n",
|
|
||||||
"\n",
|
|
||||||
"When a tactic fails, it throws an exception:"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 6,
|
|
||||||
"id": "c9784ba2-3810-4f80-a6c4-33d5eef3003e",
|
|
||||||
"metadata": {
|
|
||||||
"scrolled": true
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"[\"tactic 'assumption' failed\\na : Prop\\n⊢ ∀ (q : Prop), a ∨ q → q ∨ a\"]\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"try:\n",
|
|
||||||
" state2 = server.goal_tactic(state1, goal_id=0, tactic=\"assumption\")\n",
|
|
||||||
" print(\"Should not reach this\")\n",
|
|
||||||
"except Exception as e:\n",
|
|
||||||
" print(e)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "1ae60d9e-8656-4f26-b495-d04bced250fc",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"A state with no goals is considered solved"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 7,
|
|
||||||
"id": "1cb96b19-d3bb-4533-abeb-a7dbc5bc8c3e",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"GoalState(state_id=5, goals=[], _sentinel=[0, 1])"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 7,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"state0 = server.goal_start(\"forall (p : Prop), p -> p\")\n",
|
|
||||||
"state1 = server.goal_tactic(state0, goal_id=0, tactic=\"intro\")\n",
|
|
||||||
"state2 = server.goal_tactic(state1, goal_id=0, tactic=\"intro h\")\n",
|
|
||||||
"state3 = server.goal_tactic(state2, goal_id=0, tactic=\"exact h\")\n",
|
|
||||||
"state3"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "a2945e71-e583-4ae0-9c0f-83035f0492f2",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"Execute `server.gc()` once in a while to delete unused goals."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 8,
|
|
||||||
"id": "d53624ff-c720-4847-98f7-28e109eb76e7",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"server.gc()"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "0b59e05e-7d8c-4fad-b8ca-375ea995ea5b",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"## Special Tactics\n",
|
|
||||||
"\n",
|
|
||||||
"Lean has special provisions for some tactics. This includes `have`, `let`,\n",
|
|
||||||
"`calc`. To execute one of these tactics, create a `TacticHave`, `TacticLet`,\n",
|
|
||||||
"`TacticCalc` instance and feed it into `server.goal_tactic`.\n",
|
|
||||||
"\n",
|
|
||||||
"Technically speaking `have` and `let` are not tactics in Lean, so their execution requires special attention."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 9,
|
|
||||||
"id": "526d620b-064f-4ec0-a7b2-6a1ef3c6f6e7",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"\n",
|
|
||||||
"⊢ 2 = 1 + 1\n",
|
|
||||||
"h : 2 = 1 + 1\n",
|
|
||||||
"⊢ 1 + 1 = 2\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"state0 = server.goal_start(\"1 + 1 = 2\")\n",
|
|
||||||
"state1 = server.goal_tactic(state0, goal_id=0, tactic=TacticHave(branch=\"2 = 1 + 1\", binder_name=\"h\"))\n",
|
|
||||||
"print(state1)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "c415d436-ed0d-475f-bf5e-b8dc63954c7e",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"The `TacticExpr` \"tactic\" parses an expression and assigns it to the current\n",
|
|
||||||
"goal. This leverages Lean's type unification system and is as expressive as\n",
|
|
||||||
"Lean expressions. Many proofs in Mathlib4 are written in a mixture of expression\n",
|
|
||||||
"and tactic forms."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 10,
|
|
||||||
"id": "e1f06441-4d77-45a7-a1c3-b800b96a8105",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"state0 = server.goal_start(\"forall (p : Prop), p -> p\")\n",
|
|
||||||
"state1 = server.goal_tactic(state0, goal_id=0, tactic=\"intro p\")\n",
|
|
||||||
"state2 = server.goal_tactic(state1, goal_id=0, tactic=TacticExpr(\"fun h => h\"))\n",
|
|
||||||
"print(state2)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"id": "d6bcd026-507b-4b1c-8dee-006df53636b0",
|
|
||||||
"metadata": {},
|
|
||||||
"source": [
|
|
||||||
"To execute the `conv` tactic, use `server.goal_conv_begin` to enter conv mode on\n",
|
|
||||||
"one goal, and use `server.goal_conv_end` to exit from conv mode. Pantograph will\n",
|
|
||||||
"provide interactive feedback upon every tactic executed in `conv` mode."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"id": "2b0b27c6-0c69-4255-aed1-c0713c227ccc",
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3 (ipykernel)",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.12.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 5
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
# Introduction
|
|
||||||
|
|
||||||
This is Pantograph, an machine-to-machine interaction interface for Lean 4.
|
|
||||||
Its main purpose is to train and evaluate theorem proving agents. The main
|
|
||||||
features are:
|
|
||||||
1. Interfacing via the Python library, REPL, or C Library
|
|
||||||
2. A mixture of expression-based and tactic-based proofs
|
|
||||||
3. Pantograph has been designed with facilitating search in mind. A theorem
|
|
||||||
proving agent can simultaneously explore multiple branches of the proof.
|
|
||||||
4. Handling of metavariable coupling
|
|
||||||
5. Reading/Adding symbols from the environment
|
|
||||||
6. Extraction of tactic training data
|
|
||||||
7. Support for drafting
|
|
||||||
|
|
||||||
## Design Rationale
|
|
||||||
|
|
||||||
The Lean 4 interface is not conducive to search. Readers familiar with Coq may
|
|
||||||
know that the Coq Serapi was superseded by CoqLSP. In the opinion of the
|
|
||||||
authors, this is a mistake. An interface conducive for human operators to write
|
|
||||||
proofs is often not an interface conductive to search.
|
|
||||||
|
|
||||||
Almost all of Pantograph's business logic is written in Lean, and Pantograph
|
|
||||||
achieves tighter coupling between the data extraction and proof search
|
|
||||||
components.
|
|
||||||
|
|
||||||
## Caveats and Limitations
|
|
||||||
|
|
||||||
Pantograph does not exactly mimic Lean LSP's behaviour. That would not grant the
|
|
||||||
flexibility it offers. To support tree search means Pantograph has to act
|
|
||||||
differently from Lean in some times, but never at the sacrifice of soundness.
|
|
||||||
|
|
||||||
- When Lean LSP says "don't know how to synthesize placeholder", this indicates
|
|
||||||
the human operator needs to manually move the cursor to the placeholder and
|
|
||||||
type in the correct expression. This error therefore should not halt the proof
|
|
||||||
process, and the placeholder should be turned into a goal.
|
|
||||||
- When Lean LSP says "unresolved goals", that means a proof cannot finish where
|
|
||||||
it is supposed to finish at the end of a `by` block. Pantograph will raise the
|
|
||||||
error in this case, since it indicates the termination of a proof search branch.
|
|
||||||
- `pick_goal` or `swap` will not work since they run contrary to tree search
|
|
||||||
paradigms. However, if there are tactics which perform non-trivial operations
|
|
||||||
to multiple goals at the same time, this constrain could potentially be
|
|
||||||
relaxed at a cost of great bookkeeping overhead to the user.
|
|
||||||
|
|
||||||
Pantograph cannot perform things that are inherently constrained by Lean. These
|
|
||||||
include:
|
|
||||||
|
|
||||||
- If a tactic loses track of metavariables, it will not be caught until the end
|
|
||||||
of the proof search. This is a bug in the tactic itself.
|
|
||||||
- Timeouts for executing tactics is not available. Maybe this will change in the
|
|
||||||
future.
|
|
||||||
- Interceptions of parsing errors generally cannot be turned into goals (e.g.
|
|
||||||
`def mystery : Nat := :=`) due to Lean's parsing system.
|
|
||||||
|
|
||||||
Each Pantograph version is anchored to a Lean version specified in
|
|
||||||
`src/lean-toolchain`. Features can be backported to older Lean versions upon
|
|
||||||
request.
|
|
||||||
|
|
||||||
## Referencing
|
|
||||||
|
|
||||||
[Paper Link](https://arxiv.org/abs/2410.16429)
|
|
||||||
|
|
||||||
```bib
|
|
||||||
@misc{pantograph,
|
|
||||||
title={Pantograph: A Machine-to-Machine Interaction Interface for Advanced Theorem Proving, High Level Reasoning, and Data Extraction in Lean 4},
|
|
||||||
author={Leni Aniva and Chuyue Sun and Brando Miranda and Clark Barrett and Sanmi Koyejo},
|
|
||||||
year={2024},
|
|
||||||
eprint={2410.16429},
|
|
||||||
archivePrefix={arXiv},
|
|
||||||
primaryClass={cs.LO},
|
|
||||||
url={https://arxiv.org/abs/2410.16429},
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,53 +0,0 @@
|
||||||
# Setup
|
|
||||||
|
|
||||||
Install `poetry`. Then, run
|
|
||||||
```sh
|
|
||||||
poetry build
|
|
||||||
```
|
|
||||||
|
|
||||||
This builds a wheel of Pantograph in `dist` which can then be installed. For
|
|
||||||
example, a downstream project could have this line in its `pyproject.toml`
|
|
||||||
|
|
||||||
```toml
|
|
||||||
pantograph = { file = "path/to/wheel/dist/pantograph-0.2.19-cp312-cp312-manylinux_2_40_x86_64.whl" }
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the examples and experiments, setup a poetry shell:
|
|
||||||
```sh
|
|
||||||
poetry install
|
|
||||||
poetry shell
|
|
||||||
```
|
|
||||||
This drops the current shell into an environment where the development packages are available.
|
|
||||||
|
|
||||||
All interactions with Lean pass through the `Server` class. Create an instance
|
|
||||||
with
|
|
||||||
```python
|
|
||||||
from pantograph import Server
|
|
||||||
server = Server()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lean Dependencies
|
|
||||||
|
|
||||||
The server created from `Server()` is sufficient for basic theorem proving tasks
|
|
||||||
reliant on Lean's `Init` library. Some users may find this insufficient and want
|
|
||||||
to use non-builtin libraries such as Aesop or Mathlib4.
|
|
||||||
|
|
||||||
To use external Lean dependencies such as
|
|
||||||
[Mathlib4](https://github.com/leanprover-community/mathlib4), Pantograph relies
|
|
||||||
on an existing Lean repository. Instructions for creating this repository can be
|
|
||||||
found [here](https://docs.lean-lang.org/lean4/doc/setup.html#lake).
|
|
||||||
|
|
||||||
After creating this initial Lean repository, execute in the repository
|
|
||||||
```sh
|
|
||||||
lake build
|
|
||||||
```
|
|
||||||
|
|
||||||
to build all files from the repository. This step is necessary after any file in
|
|
||||||
the repository is modified.
|
|
||||||
|
|
||||||
Then, feed the repository's path to the server
|
|
||||||
```python
|
|
||||||
server = Server(project_path="./path-to-lean-repo/")
|
|
||||||
```
|
|
||||||
|
|
||||||
For a complete example, see `examples/`.
|
|
|
@ -1,3 +0,0 @@
|
||||||
/build
|
|
||||||
/lakefile.olean
|
|
||||||
/lake-packages/*
|
|
|
@ -1,14 +0,0 @@
|
||||||
import Aesop
|
|
||||||
|
|
||||||
/-- Ensure that Aesop is running -/
|
|
||||||
example : α → α :=
|
|
||||||
by aesop
|
|
||||||
|
|
||||||
example : ∀ (p q: Prop), p ∨ q → q ∨ p := by
|
|
||||||
intro p q h
|
|
||||||
-- Here are some comments
|
|
||||||
cases h
|
|
||||||
. apply Or.inr
|
|
||||||
assumption
|
|
||||||
. apply Or.inl
|
|
||||||
assumption
|
|
|
@ -1,25 +0,0 @@
|
||||||
{"version": "1.1.0",
|
|
||||||
"packagesDir": ".lake/packages",
|
|
||||||
"packages":
|
|
||||||
[{"url": "https://github.com/leanprover-community/batteries",
|
|
||||||
"type": "git",
|
|
||||||
"subDir": null,
|
|
||||||
"scope": "",
|
|
||||||
"rev": "4756e0fc48acce0cc808df0ad149de5973240df6",
|
|
||||||
"name": "batteries",
|
|
||||||
"manifestFile": "lake-manifest.json",
|
|
||||||
"inputRev": "main",
|
|
||||||
"inherited": true,
|
|
||||||
"configFile": "lakefile.lean"},
|
|
||||||
{"url": "https://github.com/leanprover-community/aesop.git",
|
|
||||||
"type": "git",
|
|
||||||
"subDir": null,
|
|
||||||
"scope": "",
|
|
||||||
"rev": "28fa80508edc97d96ed6342c9a771a67189e0baa",
|
|
||||||
"name": "aesop",
|
|
||||||
"manifestFile": "lake-manifest.json",
|
|
||||||
"inputRev": "v4.12.0",
|
|
||||||
"inherited": false,
|
|
||||||
"configFile": "lakefile.toml"}],
|
|
||||||
"name": "Example",
|
|
||||||
"lakeDir": ".lake"}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import Lake
|
|
||||||
open Lake DSL
|
|
||||||
|
|
||||||
require aesop from git
|
|
||||||
"https://github.com/leanprover-community/aesop.git" @ "v4.12.0"
|
|
||||||
|
|
||||||
package Example
|
|
||||||
|
|
||||||
@[default_target]
|
|
||||||
lean_lib Example
|
|
|
@ -1 +0,0 @@
|
||||||
../../src/lean-toolchain
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Examples
|
|
||||||
|
|
||||||
For a quick introduction of the API, fire up Jupyter and open `all.ipynb`. (Did
|
|
||||||
you remember to `poetry install`?)
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
poetry run jupyter notebook
|
|
||||||
```
|
|
||||||
|
|
||||||
This example showcases how to bind library dependencies and execute the `Aesop`
|
|
||||||
tactic in Lean. First build the example project:
|
|
||||||
``` sh
|
|
||||||
pushd Example
|
|
||||||
lake build
|
|
||||||
popd
|
|
||||||
```
|
|
||||||
This would generate compiled `.olean` files. Then run one of the examples from the
|
|
||||||
project root:
|
|
||||||
``` sh
|
|
||||||
poetry run examples/aesop.py
|
|
||||||
poetry run examples/sketch.py
|
|
||||||
poetry run examples/data.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Warning: If you make modifications to any Lean files, you must re-run `lake
|
|
||||||
build`! Moreover, the version of the Lean used in the example folder (including
|
|
||||||
dependencies in `lakefile.lean` and `lean-toolchain`) **must match exactly**
|
|
||||||
with the version in `src/`!
|
|
||||||
|
|
||||||
* `aesop.py`: Example of how to use the `aesop` tactic
|
|
||||||
* `data.py`: Example of loading training data
|
|
||||||
* `sketch.py`: Example of loading a sketch
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
from pantograph.server import Server
|
|
||||||
|
|
||||||
def get_project_and_lean_path():
|
|
||||||
cwd = Path(__file__).parent.resolve() / 'Example'
|
|
||||||
p = subprocess.check_output(['lake', 'env', 'printenv', 'LEAN_PATH'], cwd=cwd)
|
|
||||||
return cwd, p
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
project_path, lean_path = get_project_and_lean_path()
|
|
||||||
print(f"$PWD: {project_path}")
|
|
||||||
print(f"$LEAN_PATH: {lean_path}")
|
|
||||||
server = Server(imports=['Example'], project_path=project_path, lean_path=lean_path)
|
|
||||||
state0 = server.goal_start("forall (p q: Prop), Or p q -> Or q p")
|
|
||||||
state1 = server.goal_tactic(state0, goal_id=0, tactic="aesop")
|
|
||||||
assert state1.is_solved
|
|
|
@ -1,36 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from pantograph.server import Server
|
|
||||||
|
|
||||||
sketch = """
|
|
||||||
theorem add_comm_proved_formal_sketch : ∀ n m : Nat, n + m = m + n := by
|
|
||||||
-- Consider some n and m in Nats.
|
|
||||||
intros n m
|
|
||||||
-- Perform induction on n.
|
|
||||||
induction n with
|
|
||||||
| zero =>
|
|
||||||
-- Base case: When n = 0, we need to show 0 + m = m + 0.
|
|
||||||
-- We have the fact 0 + m = m by the definition of addition.
|
|
||||||
have h_base: 0 + m = m := sorry
|
|
||||||
-- We also have the fact m + 0 = m by the definition of addition.
|
|
||||||
have h_symm: m + 0 = m := sorry
|
|
||||||
-- Combine facts to close goal
|
|
||||||
sorry
|
|
||||||
| succ n ih =>
|
|
||||||
-- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.
|
|
||||||
-- By the inductive hypothesis, we have n + m = m + n.
|
|
||||||
have h_inductive: n + m = m + n := sorry
|
|
||||||
-- 1. Note we start with: Nat.succ n + m = m + Nat.succ n, so, pull the succ out from m + Nat.succ n on the right side from the addition using addition facts Nat.add_succ.
|
|
||||||
have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := sorry
|
|
||||||
-- 2. then to flip m + S n to something like S (n + m) we need to use the IH.
|
|
||||||
have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := sorry
|
|
||||||
-- 3. Now the n & m are on the correct sides Nat.succ n + m = Nat.succ (n + m), so let's use the def of addition to pull out the succ from the addition on the left using Nat.succ_add.
|
|
||||||
have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := sorry
|
|
||||||
-- Combine facts to close goal
|
|
||||||
sorry
|
|
||||||
"""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
server = Server()
|
|
||||||
unit, = server.load_sorry(sketch)
|
|
||||||
print(unit.goal_state)
|
|
|
@ -1 +0,0 @@
|
||||||
/result
|
|
|
@ -1,68 +0,0 @@
|
||||||
# Lean Draft Sketch Prove (DSP)
|
|
||||||
|
|
||||||
based on Sean Welleck's DSP for Isabelle: https://github.com/wellecks/ntptutorial/tree/main/partII_dsp
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
First of all, build the experiment repo.
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
# experiments/dsp
|
|
||||||
cd lean_src_proj
|
|
||||||
lake build
|
|
||||||
```
|
|
||||||
Then run `main.py`
|
|
||||||
``` sh
|
|
||||||
python3 main.py -h
|
|
||||||
```
|
|
||||||
|
|
||||||
The main command for running DSP is `eval`. Due to the multitude of data format
|
|
||||||
out there, use the `--format` flag to specify the data format. For example,
|
|
||||||
running DSP on minif2f is:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
python3 main.py eval \
|
|
||||||
--dataset ../minif2f/valid.jsonl \
|
|
||||||
--format minif2f \
|
|
||||||
--output results-minif2f-valid
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, use `plot.py` to generate the plots
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
python3 plot.py \
|
|
||||||
--result results-minif2f-{valid,test} \
|
|
||||||
--names valid test \
|
|
||||||
--plot-output output-plot
|
|
||||||
```
|
|
||||||
|
|
||||||
## Related work
|
|
||||||
|
|
||||||
### Tony's AF
|
|
||||||
Ton'y original AF: ((Yuhuai et al.))[https://arxiv.org/abs/2205.12615]
|
|
||||||
Tony's paper improve MiniF2F from: `29.6% to 35.2%`, by `5.6%`.
|
|
||||||
|
|
||||||
Expert Iteration:
|
|
||||||
- AF used: "We explore if one can improve neural theorem provers by training the neural models on proofs of automatically translated theorems".
|
|
||||||
- they only translate **problem theorems** (nl_thm := "problem + answer") then use a prover to get the formal proof.
|
|
||||||
- ExpIt algorithn:
|
|
||||||
- `M_0 := Isabelle_Thor()`
|
|
||||||
- `Search/Prover := Best_First_Search()` # TODO recall best first search
|
|
||||||
- ExpIT.fine_tune := "train model to predict next proof_step/tactic given current proof_state and previous proof_step on successful proofs.
|
|
||||||
- i.e., `<x=(proof_state_{t}, proof_step_{t-1}), y=(proof_step_{t})>` #TODO: I think, confirm with Albert https://twitter.com/messages/1253358235-1267913180153548800
|
|
||||||
|
|
||||||
Base Model for Neural Theorem Prover (NTP):
|
|
||||||
- Thor_GPT2 := "We use a pre-trained and fine-tuned Thor based on a GPT-2 with 700M non-embedding parameters." Note: ReProver used 299M parameters enc-dec.
|
|
||||||
- fine-tuned on the PILE arxiv + github
|
|
||||||
|
|
||||||
Neural Theorem Prover (NTP) for `M_0`:
|
|
||||||
- Thor :=
|
|
||||||
- The Thor agent is fine-tuned on the PISA dataset which consists of 2.49 million proof steps from the Isabelle/HOL library.
|
|
||||||
- The model is trained with the objective to predict the next token in va proof step, given the proof state and the last proof step.
|
|
||||||
- proof step := "tactic in Isabelle" #TODO confirm with Albert https://twitter.com/messages/1253358235-1267913180153548800
|
|
||||||
|
|
||||||
Questions:
|
|
||||||
- Q1: what is this: "we perform deduplication by problem statements" when does it matter? All MATH train are unique, so why would I care about this?
|
|
||||||
|
|
||||||
Idea:
|
|
||||||
- Idea1: use the formal ground truth solution string in MATH, implement Draft Sketch Proof (DSP) for Lean4 + use some symbolic/ntp solver (hammer/tidy/ReProver)
|
|
|
@ -1,3 +0,0 @@
|
||||||
{"problem": "$ E = \\left[\\begin{array}{rr}5 & 1 \\\\ 2 & 3\\end{array}\\right]$ What is the determinant of $ E$ ?", "hints": ["The determinant of a 2x2 matrix can be computed the following way:", "$ = $", "In this specific case,", "$ = $", "$ = 13 $"]}
|
|
||||||
{"problem": "If $a + b + c = 9$, what is $7c + 7a + 7b$ ?", "hints": ["$= 7a + 7b + 7c$", "$= (7) \\cdot (a + b + c) $", "$= (7) \\cdot (9) $", "$= 63$"]}
|
|
||||||
{"problem": "Find $\\lim_{x\\to\\infty}\\dfrac{x^2-4}{\\cos(x)}$. Choose 1 answer: Choose 1 answer: (Choice A) A $4$ (Choice B) B $-2$ (Choice C) C $0$ (Choice D) D The limit doesn't exist", "hints": ["When dealing with limits that include $\\cos(x)$, it's important to remember that $\\lim_{x\\to\\infty}\\cos(x)$ doesn't exist, as $\\cos(x)$ keeps oscillating between $-1$ and $1$ forever. ${2}$ ${4}$ ${6}$ ${8}$ ${\\llap{-}4}$ ${\\llap{-}6}$ ${\\llap{-}8}$ ${2}$ $y$ $x$ $y=\\cos(x)$ This doesn't necessarily mean that our limit doesn't exist. Think what happens to $\\dfrac{x^2-4}{\\cos(x)}$ as $x$ increases towards positive infinity.", "While $x^2-4$ keeps growing boundlessly, $\\cos(x)$ oscillates from $-1$, to $0$, to $1$, to $0$, to $-1$ again. The result is a graph that goes up and down forever, with vertical asymptotes every now and then. ${5}$ ${10}$ ${15}$ ${\\llap{-}5}$ ${\\llap{-}10}$ ${\\llap{-}15}$ ${50}$ ${100}$ ${150}$ ${\\llap{-}50}$ ${\\llap{-}100}$ ${\\llap{-}150}$ $y$ $x$ $y=\\dfrac{x^2-4}{\\cos(x)}$ This limit doesn't approach any specific value as $x$ increases towards infinity.", "In conclusion, $\\lim_{x\\to\\infty}\\dfrac{x^2-4}{\\cos(x)}$ doesn't exist."]}
|
|
File diff suppressed because one or more lines are too long
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"problem": "For any natural number n, n + 0 = n.",
|
|
||||||
"level": "SF foundations level 1",
|
|
||||||
"type": "Algebra",
|
|
||||||
"solution": [
|
|
||||||
"Consider some natural number n. The proof will be by induction. Consider the base case n=0. We have 0 + 0 = 0 which holds by the definition of addion.",
|
|
||||||
"Now the inductive case we want to show n'+1 + 0 = n'+1. Assume it holds for n i.e., n' + 0 = n. So now by the we know the LHS is equal to (n'+0) + 1.",
|
|
||||||
"Then by the induction hypothesis we know the LHS simplifies to n'+1 completing the proof. Qed."
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"problem": "For any natural number n, 0 + n = n.",
|
|
||||||
"level": "SF foundations level 1",
|
|
||||||
"type": "Algebra",
|
|
||||||
"solution": "Consider some natural number n. We now want to show 0 + n = n. Using addition both sides are equal."
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"problem": "For any natural number n, 0 + n = n.",
|
|
||||||
"level": "SF foundations level 1",
|
|
||||||
"type": "Algebra",
|
|
||||||
"solution": "Consider some natural number n. We now want to show 0 + n = n. For that we use the definition of addition to get n = n and conclude both sides are equal (by reflexity)."
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"problem": "For any natural number n, 0 + n = n.",
|
|
||||||
"level": "SF foundations level 1",
|
|
||||||
"type": "Logical Foundations",
|
|
||||||
"solution": [
|
|
||||||
"Consider some natural number n. We want to show 0 + n = n. ",
|
|
||||||
"By using definition of addition on both sides, LHS and RHS are now equal, done."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"problem": "For any natural number n, n + 0 = n.",
|
|
||||||
"level": "SF foundations level 1",
|
|
||||||
"type": "Logical Foundations",
|
|
||||||
"solution": [
|
|
||||||
"Consider some natural number n. The proof will be by induction. ",
|
|
||||||
"The base case n=0, so we have 0 + 0 = 0, which holds by the definition of addion. ",
|
|
||||||
"Consider the inductive case, so we want to show (k + 1) + 0 = (k + 1) for any k < n assuming the IH holds for such k (IH: k + 0 = k). ",
|
|
||||||
"By the IH we have (k + 1) + 0 = (k + 1). ",
|
|
||||||
"By def of addition we have (k + 0) + 1 = (k + 1). ",
|
|
||||||
"By the induction hypothesis (IH) we have k + 0 = k. ",
|
|
||||||
"LHS and RHS are equal so proof is complete."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,368 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"nl_problem": "For any natural number n, n + 0 = n.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural number n. We want to show n + 0 = n. ",
|
|
||||||
"By using fats of addition on both sides, LHS and RHS are now equal, done."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem n_plus_zero : ∀ n : ℕ, n + 0 = n := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + 0 = n.\n",
|
|
||||||
"theorem n_plus_zero : ∀ n : ℕ, n + 0 = n := by\n",
|
|
||||||
" -- Consider some n in Nats.\n",
|
|
||||||
" intro n",
|
|
||||||
"-- Using facts of addition simplify n + 0 to n.\n",
|
|
||||||
" rw [Nat.add_zero]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
],
|
|
||||||
|
|
||||||
"nl_problem_proved_sketch": "For any natural number n, n + 0 = n.",
|
|
||||||
"nl_solution_proved_sketch": [
|
|
||||||
"We want to show n + 0 = n. ",
|
|
||||||
"We have the fact of addition that, n + 0 = n. ",
|
|
||||||
"Thus, the left-hand side and right-hand side are equal, which completes the proof."
|
|
||||||
],
|
|
||||||
"fl_problem_proved_sketch": "theorem n_plus_zero_proved_formal_sketch : ∀ n : ℕ, n + 0 = n := by",
|
|
||||||
"fl_solution_proved_sketch": [
|
|
||||||
"-- Prove that n + 0 = n via a formal proof sketch",
|
|
||||||
"theorem n_plus_zero_proved_formal_sketch : ∀ n : ℕ, n + 0 = n := by",
|
|
||||||
" -- We have the fact of addition n + 0 = n, use it to show left and right are equal.",
|
|
||||||
" have h_nat_add_zero: ∀ n : ℕ, n + 0 = n := Nat.add_zero",
|
|
||||||
" exact h_nat_add_zero"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution_proved_sketch": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
|
|
||||||
"nl_problem_proved_sketch_aesop": "For any natural number n, n + 0 = n.",
|
|
||||||
"nl_solution_proved_sketch_aesop": [
|
|
||||||
"We want to show n + 0 = n. ",
|
|
||||||
"We have the fact of addition that, n + 0 = n. ",
|
|
||||||
"Thus, the left-hand side and right-hand side are equal, which completes the proof."
|
|
||||||
],
|
|
||||||
"fl_problem_proved_sketch_aesop": "theorem n_plus_zero_proved_formal_sketch' : ∀ n : ℕ, n + 0 = n := by",
|
|
||||||
"fl_solution_proved_sketch_aesop": [
|
|
||||||
"-- Prove that n + 0 = n via a formal proof sketch with aesop. ",
|
|
||||||
"theorem n_plus_zero_proved_formal_sketch' : ∀ n : ℕ, n + 0 = n := by",
|
|
||||||
" -- We have the fact of addition n + 0 = n, use it to show left and right are equal. ",
|
|
||||||
" have h_nat_add_zero: ∀ n : ℕ, n + 0 = n := by aesop",
|
|
||||||
" exact h_nat_add_zero"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution_proved_sketch_aesop": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "For any natural number n, 0 + n = n.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural number n. We want to show 0 + n = n.",
|
|
||||||
"By using facts of addition and induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem zero_plus_n : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that 0 + n = n by induction",
|
|
||||||
"theorem zero_plus_n : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"-- Consider some n in Nats.",
|
|
||||||
"intro n",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: 0 + 0 = 0",
|
|
||||||
"rw [Nat.add_zero]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: assume 0 + n = n, prove 0 + succ n = succ n",
|
|
||||||
"rw [Nat.add_succ]",
|
|
||||||
"rw [ih]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
],
|
|
||||||
|
|
||||||
"nl_problem_proved_sketch": "For any natural number n, 0 + n = n.",
|
|
||||||
"nl_solution_proved_sketch": [
|
|
||||||
"We want to show 0 + n = n.",
|
|
||||||
"By using the fact of addition and performing induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem_proved_sketch": "theorem zero_plus_n_proved_formal_sketch : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"fl_solution_proved_sketch": [
|
|
||||||
"-- Prove that 0 + n = n by induction via a formal proof sketch",
|
|
||||||
"theorem zero_plus_n_proved_formal_sketch : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"-- Consider some n in Nats.",
|
|
||||||
"intro n",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: 0 + 0 = 0",
|
|
||||||
"have h_base: 0 + 0 = 0 := by rw [Nat.add_zero]",
|
|
||||||
"exact h_base",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: assume 0 + n = n, prove 0 + succ n = succ n",
|
|
||||||
"have h_inductive: 0 + Nat.succ n = Nat.succ n := by",
|
|
||||||
"rw [Nat.add_succ]",
|
|
||||||
"rw [ih]",
|
|
||||||
"exact h_inductive"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution_proved_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
],
|
|
||||||
|
|
||||||
"nl_problem_proved_sketch_aesop": "For any natural number n, 0 + n = n.",
|
|
||||||
"nl_solution_proved_sketch_aesop": [
|
|
||||||
"We want to show 0 + n = n.",
|
|
||||||
"By using the fact of addition and performing induction on n, we can prove the statement for both the base case and the inductive step using aesop."
|
|
||||||
],
|
|
||||||
|
|
||||||
"fl_problem_proved_sketch_aesop": "theorem zero_plus_n_proved_formal_sketch' : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"fl_solution_proved_sketch_aesop": [
|
|
||||||
"-- Prove that 0 + n = n by induction via a formal proof sketch with aesop.",
|
|
||||||
"theorem zero_plus_n_proved_formal_sketch' : ∀ n : ℕ, 0 + n = n := by",
|
|
||||||
"-- Consider some n in Nats.",
|
|
||||||
"intro n",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: 0 + 0 = 0",
|
|
||||||
"have h_base: 0 + 0 = 0 := by aesop",
|
|
||||||
"exact h_base",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: assume 0 + n = n, prove 0 + succ n = succ n",
|
|
||||||
"have h_inductive: 0 + Nat.succ n = Nat.succ n := by aesop",
|
|
||||||
"exact h_inductive"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution_proved_sketch_aesop": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "For any natural numbers n and m we have commutativity, n + m = m + n.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n and m. We want to show n + m = m + n.",
|
|
||||||
"By using facts of addition and induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_comm_normal : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + m = m + n",
|
|
||||||
"theorem add_comm_normal : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"-- Consider some n and m in Nats.",
|
|
||||||
"intros n m",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + m = m + 0.",
|
|
||||||
"-- Using the definition of addition, 0 + m = m and m + 0 = m.",
|
|
||||||
"rw [Nat.zero_add, Nat.add_zero]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.",
|
|
||||||
"-- We use the fact n + (m + 1) = (n + m) + 1.",
|
|
||||||
"have plus_n_Sm_normal: ∀ n m : ℕ, n + (m + 1) = (n + m) + 1 := by",
|
|
||||||
" intros n m",
|
|
||||||
" rw [Nat.add_succ]",
|
|
||||||
"-- Apply the fact to rewrite succ n + m = (n + m) + 1.",
|
|
||||||
"rw [Nat.add_succ, Nat.add_zero]",
|
|
||||||
"rw [← ih]",
|
|
||||||
"rw [Nat.succ_add]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "For any natural numbers n and m, n + m = m + n.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n and m. We want to show n + m = m + n.",
|
|
||||||
"By using the fact of addition and performing induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_comm_proved_formal_sketch : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + m = m + n via a formal proof sketch",
|
|
||||||
"theorem add_comm_proved_formal_sketch : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"-- Consider some n and m in Nats.",
|
|
||||||
"intros n m",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + m = m + 0.",
|
|
||||||
"-- We have the fact 0 + m = m by the definition of addition.",
|
|
||||||
"have h_base: 0 + m = m := Nat.zero_add m",
|
|
||||||
"-- We also have the fact m + 0 = m by the definition of addition.",
|
|
||||||
"have h_symm: m + 0 = m := Nat.add_zero m",
|
|
||||||
"-- Combining these, we get 0 + m = m + 0.",
|
|
||||||
"rw [h_base, h_symm]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.",
|
|
||||||
"-- By the inductive hypothesis, we have n + m = m + n.",
|
|
||||||
"have h_inductive: n + m = m + n := ih",
|
|
||||||
"-- proof is:",
|
|
||||||
"-- We eventually want to flip n + m and simplify to make both sides the same. Thus,",
|
|
||||||
"-- 1. Note we start with: Nat.succ n + m = m + Nat.succ n, so, pull the succ out from m + Nat.succ n on the right side from the addition using addition facts Nat.add_succ.",
|
|
||||||
"have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := by rw [Nat.add_succ]",
|
|
||||||
"-- 2. then to flip m + S n to something like S (n + m) we need to use the IH.",
|
|
||||||
"have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := by rw [h_inductive]",
|
|
||||||
"-- 3. Now the n & m are on the correct sides Nat.succ n + m = Nat.succ (n + m), so let's use the def of addition to pull out the succ from the addition on the left using Nat.succ_add.",
|
|
||||||
"have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := by rw [Nat.succ_add]",
|
|
||||||
"-- Combining these, we get succ n + m = m + succ n.",
|
|
||||||
"rw [h_pull_succ_out_from_right, ←h_flip_n_plus_m, h_pull_succ_out_from_left]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "For any natural numbers n and m, n + m = m + n.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n and m. We want to show n + m = m + n.",
|
|
||||||
"By using the fact of addition and performing induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_comm_proved_formal_sketch_aesop : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + m = m + n via a formal proof sketch with aesop.",
|
|
||||||
"theorem add_comm_proved_formal_sketch_aesop : ∀ n m : ℕ, n + m = m + n := by",
|
|
||||||
"-- Consider some n and m in Nats.",
|
|
||||||
"intros n m",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + m = m + 0.",
|
|
||||||
"-- We have the fact 0 + m = m by the definition of addition.",
|
|
||||||
"have h_base: 0 + m = m := by aesop",
|
|
||||||
"-- We also have the fact m + 0 = m by the definition of addition.",
|
|
||||||
"have h_symm: m + 0 = m := by aesop",
|
|
||||||
"-- Combining these, we get 0 + m = m + 0.",
|
|
||||||
"rw [h_base, h_symm]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.",
|
|
||||||
"-- By the inductive hypothesis, we have n + m = m + n.",
|
|
||||||
"have h_inductive: n + m = m + n := by aesop",
|
|
||||||
"-- proof is:",
|
|
||||||
"-- We eventually want to flip n + m and simplify to make both sides the same. Thus,",
|
|
||||||
"-- 1. Note we start with: Nat.succ n + m = m + Nat.succ n, so, pull the succ out from m + Nat.succ n on the right side from the addition using addition facts Nat.add_succ.",
|
|
||||||
"have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := by aesop",
|
|
||||||
"-- 2. then to flip m + S n to something like S (n + m) we need to use the IH.",
|
|
||||||
"have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := by aesop",
|
|
||||||
"-- 3. Now the n & m are on the correct sides Nat.succ n + m = Nat.succ (n + m), so let's use the def of addition to pull out the succ from the addition on the left using Nat.succ_add.",
|
|
||||||
"have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := by rw [Nat.succ_add]",
|
|
||||||
"-- Combining these, we get succ n + m = m + succ n.",
|
|
||||||
"rw [h_pull_succ_out_from_right, ←h_flip_n_plus_m, h_pull_succ_out_from_left]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "Prove that for any natural numbers n, m, and p, n + (m + p) = (n + m) + p.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n, m, and p. We want to show n + (m + p) = (n + m) + p.",
|
|
||||||
"By using facts of addition and induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_assoc_normal : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + (m + p) = (n + m) + p",
|
|
||||||
"theorem add_assoc_normal : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"-- Consider some n, m, and p in Nats.",
|
|
||||||
"intros n m p",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + (m + p) = (0 + m) + p.",
|
|
||||||
"-- Using the definition of addition, 0 + (m + p) = m + p and (0 + m) + p = m + p.",
|
|
||||||
"rw [Nat.zero_add, Nat.zero_add]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + (m + p) = (n + m) + p, we need to show succ n + (m + p) = (succ n + m) + p.",
|
|
||||||
"-- proof strategy is, we move succ n out (or in) enough times then use the IH until both sides are the same.",
|
|
||||||
"-- 1. let's start by pulling out the succ from the left side and have the entire addition inside the succ.",
|
|
||||||
"rw [Nat.succ_add]",
|
|
||||||
"-- 2. Now that we have the IH hypothesis appearing inside the left, let's apply it so we have n + (m + p) = (n + m) + p.",
|
|
||||||
"rw [ih]",
|
|
||||||
"-- 3. Now that the parentheses (apps of plus) are in the right place for both sides, push the succ on the left twice so both terms are the same.",
|
|
||||||
"rw [← Nat.succ_add, ← Nat.succ_add]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "Prove that for any natural numbers n, m, and p, n + (m + p) = (n + m) + p.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n, m, and p. We want to show n + (m + p) = (n + m) + p.",
|
|
||||||
"By using facts of addition and induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_assoc_proved_formal_sketch : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + (m + p) = (n + m) + p",
|
|
||||||
"theorem add_assoc_proved_formal_sketch : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"-- Consider some n, m, and p in Nats.",
|
|
||||||
"intros n m p",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + (m + p) = (0 + m) + p.",
|
|
||||||
"-- Using the definition of addition, 0 + (m + p) = m + p and (0 + m) + p = m + p.",
|
|
||||||
"rw [Nat.zero_add, Nat.zero_add]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + (m + p) = (n + m) + p, we need to show succ n + (m + p) = (succ n + m) + p.",
|
|
||||||
"-- proof strategy is, we move succ n out (or in) enough times then use the IH until both sides are the same.",
|
|
||||||
"-- 1. let's start by pulling out the succ from the left side and have the entire addition inside the succ.",
|
|
||||||
"have h_pull_add_succ_out_from_left: Nat.succ n + (m + p) = Nat.succ (n + (m + p)) := by rw [Nat.succ_add]",
|
|
||||||
"-- 2. Now that we have the IH hypothesis appearing inside the left, let's apply it so we have n + (m + p) = (n + m) + p.",
|
|
||||||
"have h_inside_left_associates: Nat.succ (n + (m + p)) = Nat.succ ((n + m) + p) := by rw [ih]",
|
|
||||||
"-- 3. Now that the parentheses (apps of plus) are in the right place for both sides, push the succ on the left twice so both terms are the same.",
|
|
||||||
"have h_push_succ_in_left_twice: Nat.succ ((n + m) + p) = ((Nat.succ n) + m) + p := by rw [← Nat.succ_add, ← Nat.succ_add]",
|
|
||||||
"-- Combining these, we get succ n + (m + p) = (succ n + m) + p.",
|
|
||||||
"rw [h_pull_add_succ_out_from_left, h_inside_left_associates, h_push_succ_in_left_twice]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": "Prove that for any natural numbers n, m, and p, n + (m + p) = (n + m) + p.",
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider some natural numbers n, m, and p. We want to show n + (m + p) = (n + m) + p.",
|
|
||||||
"By using facts of addition and induction on n, we can prove the statement for both the base case and the inductive step."
|
|
||||||
],
|
|
||||||
"fl_problem": "theorem add_assoc_proved_formal_sketch_aesop : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"fl_solution": [
|
|
||||||
"-- Prove that n + (m + p) = (n + m) + p via a formal proof sketch with aesop",
|
|
||||||
"theorem add_assoc_proved_formal_sketch_aesop : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by",
|
|
||||||
"-- Consider some n, m, and p in Nats.",
|
|
||||||
"intros n m p",
|
|
||||||
"-- Perform induction on n.",
|
|
||||||
"induction n with",
|
|
||||||
"| zero =>",
|
|
||||||
"-- Base case: When n = 0, we need to show 0 + (m + p) = (0 + m) + p.",
|
|
||||||
"-- Using the definition of addition, 0 + (m + p) = m + p and (0 + m) + p = m + p.",
|
|
||||||
"rw [Nat.zero_add, Nat.zero_add]",
|
|
||||||
"| succ n ih =>",
|
|
||||||
"-- Inductive step: Assume n + (m + p) = (n + m) + p, we need to show succ n + (m + p) = (succ n + m) + p.",
|
|
||||||
"-- proof strategy is, we move succ n out (or in) enough times then use the IH until both sides are the same.",
|
|
||||||
"-- 1. let's start by pulling out the succ from the left side and have the entire addition inside the succ.",
|
|
||||||
"have h_pull_add_succ_out_from_left: Nat.succ n + (m + p) = Nat.succ (n + (m + p)) := by rw [Nat.succ_add]",
|
|
||||||
"-- 2. Now that we have the IH hypothesis appearing inside the left, let's apply it so we have n + (m + p) = (n + m) + p.",
|
|
||||||
"have h_inside_left_associates: Nat.succ (n + (m + p)) = Nat.succ ((n + m) + p) := by aesop",
|
|
||||||
"-- 3. Now that the parentheses (apps of plus) are in the right place for both sides, push the succ on the left twice so both terms are the same.",
|
|
||||||
"have h_push_succ_in_left_twice: Nat.succ ((n + m) + p) = ((Nat.succ n) + m) + p := by rw [← Nat.succ_add, ← Nat.succ_add]",
|
|
||||||
"-- Combining these, we get succ n + (m + p) = (succ n + m) + p.",
|
|
||||||
"rw [h_pull_add_succ_out_from_left, h_inside_left_associates, h_push_succ_in_left_twice]"
|
|
||||||
],
|
|
||||||
"src_header_fl_solution": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,98 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, 0 + n = n."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural number n. We will prove the statement by induction on n.",
|
|
||||||
"Base case: When n = 0, we need to show that 0 + 0 = 0. This is true by the definition of addition.",
|
|
||||||
"Inductive step: Assume that for some natural number n, 0 + n = n. We need to show that 0 + (n + 1) = (n + 1). By the definition of addition and the inductive hypothesis, we have 0 + (n + 1) = (0 + n) + 1 = n + 1. Therefore, the statement holds for n + 1.",
|
|
||||||
"Thus, by induction, we have proved that for any natural number n, 0 + n = n."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural number n, and do induction on n.",
|
|
||||||
"Base case: 0 + 0 = 0 by properties of addition.",
|
|
||||||
"Inductive step we have 0 + n = n. Then 0 + (n + 1) = (0 + n) + 1 = n + 1.",
|
|
||||||
"Where, 0 + n = n by assumption,qed."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem zero_plus_n_proved_formal_sketch : ∀ n : ℕ, 0 + n = n := "],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"by\n",
|
|
||||||
" -- Consider some n in Nats.\n",
|
|
||||||
" intro n\n",
|
|
||||||
" -- Perform induction on n.\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" -- Base case: 0 + 0 = 0\n",
|
|
||||||
" have h_base: 0 + 0 = 0 := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" -- Inductive step: assume 0 + n = n, prove 0 + succ n = succ n\n",
|
|
||||||
" have h_inductive: 0 + Nat.succ n = Nat.succ n := <TODO_PROOF_OR_HAMMER>\\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "1"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, m, and p, n + (m + p) = (n + m) + p."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural numbers n, m, and p. We will prove the statement by induction on n.",
|
|
||||||
"Base case: When n = 0, we need to show that 0 + (m + p) = (0 + m) + p. By the definition of addition, we have 0 + (m + p) = m + p and (0 + m) + p = m + p. Therefore, 0 + (m + p) = (0 + m) + p.",
|
|
||||||
"Inductive step: Assume that for some natural number n, n + (m + p) = (n + m) + p. We need to show that (n + 1) + (m + p) = ((n + 1) + m) + p.",
|
|
||||||
"1. First, pull out the successor from the left side to have the entire addition inside the successor: (n + 1) + (m + p) = (n + (m + p)) + 1.",
|
|
||||||
"2. By the inductive hypothesis, we know that n + (m + p) = (n + m) + p. So we can replace n + (m + p) with (n + m) + p inside the successor: (n + (m + p)) + 1 = ((n + m) + p) + 1.",
|
|
||||||
"3. Finally, push the successor on the left twice to align both sides: ((n + m) + p) + 1 = (n + 1) + (m + p) = ((n + 1) + m) + p.",
|
|
||||||
"Thus, by induction, we have proved that for any natural numbers n, m, and p, n + (m + p) = (n + m) + p."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural numbers n, m, and p. We will do induction on n.",
|
|
||||||
"Base case: 0 + (m + p) = (0 + m) + p by properties of addition.",
|
|
||||||
"Inductive step, we have n + (m + p) = (n + m) + p. Then (n + 1) + (m + p) = (n + (m + p)) + 1 = ((n + m) + p) + 1.",
|
|
||||||
"Thus, (n + 1) + (m + p) = ((n + 1) + m) + p, qed."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem add_assoc_proved_formal_sketch : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := "],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"by\n",
|
|
||||||
" -- Consider some n, m, and p in Nats.\n",
|
|
||||||
" intros n m p\n",
|
|
||||||
" -- Perform induction on n.\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" -- Base case: When n = 0, we need to show 0 + (m + p) = (0 + m) + p.\n",
|
|
||||||
" -- We have the fact 0 + (m + p) = m + p by the definition of addition.\n",
|
|
||||||
" have h_base: 0 + (m + p) = m + p := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- We also have the fact (0 + m) + p = m + p by the definition of addition.\n",
|
|
||||||
" have h_symm: (0 + m) + p = m + p := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" -- Inductive step: Assume n + (m + p) = (n + m) + p, we need to show succ n + (m + p) = (succ n + m) + p.\n",
|
|
||||||
" -- By the inductive hypothesis, we have n + (m + p) = (n + m) + p.\n",
|
|
||||||
" have h_inductive: n + (m + p) = (n + m) + p := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 1. Let's start by pulling out the succ from left side and have the entire addition inside the succ.\n",
|
|
||||||
" have h_pull_succ_out_from_left: Nat.succ n + (m + p) = Nat.succ (n + (m + p)) := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 2. Now that we have the IH hypothesis appearing inside the left, let's apply it so we have n + (m + p) = (n + m) + p.\n",
|
|
||||||
" have h_inside_left_associates: Nat.succ (n + (m + p)) = Nat.succ ((n + m) + p) := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 3. Now that the parentheses (apps of plus) are in the right place for both sides, push the succ on the left twice so both terms are the same.\n",
|
|
||||||
" have h_push_succ_in_left_twice: Nat.succ ((n + m) + p) = ((Nat.succ n) + m) + p := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "4"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,117 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, n + 0 = n."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural number n.",
|
|
||||||
"Using the properties of addition, we know that adding zero to any number does not change the value of that number.",
|
|
||||||
"Therefore, we can conclude that n + 0 = n."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural number n.",
|
|
||||||
"From properties of addition, adding zero does not change its values.",
|
|
||||||
"Thus, n + 0 = n."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem n_plus_zero_normal : ∀ n : ℕ, n + 0 = n := "],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"by\n",
|
|
||||||
" -- We have the fact of addition n + 0 = n, use it to show left and right are equal.\n",
|
|
||||||
" have h_nat_add_zero: ∀ n : ℕ, n + 0 = n := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts with to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "0"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, n + (m + 1) = (n + m) + 1."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural numbers n and m. We want to show that n + (m + 1) = (n + m) + 1.",
|
|
||||||
"Using the properties of addition, we know that adding 1 to the sum of n and m is the same as first adding m to n and then adding 1.",
|
|
||||||
"Therefore, we can conclude that n + (m + 1) = (n + m) + 1."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural numbers n and m.",
|
|
||||||
"From properties of addition, adding 1 to the sum of n and m is the same as first adding m to n and then adding 1.",
|
|
||||||
"Thus, n + (m + 1) = (n + m) + 1."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem plus_n_Sm_proved_formal_sketch : ∀ n m : ℕ, n + (m + 1) = (n + m) + 1 := "],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"by\n",
|
|
||||||
" -- We have the fact of addition n + (m + 1) = (n + m) + 1, use it to show left and right are equal.\n",
|
|
||||||
" have h_nat_add_succ: ∀ n m : ℕ, n + (m + 1) = (n + m) + 1 := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "2"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n and m, n + m = m + n."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural numbers n and m. We will prove the statement by induction on n.",
|
|
||||||
"Base case: When n = 0, we need to show that 0 + m = m + 0. By the definition of addition, we have 0 + m = m and m + 0 = m. Therefore, 0 + m = m + 0.",
|
|
||||||
"Inductive step: Assume that for some natural number n, n + m = m + n. We need to show that (n + 1) + m = m + (n + 1).",
|
|
||||||
"1. Start by using the fact that (n + 1) + m = n + (m + 1) and m + (n + 1) = (m + n) + 1.",
|
|
||||||
"2. By the inductive hypothesis, we have n + m = m + n. So we can replace n + (m + 1) with (m + n) + 1.",
|
|
||||||
"3. Now, both sides have the same structure, showing that (n + 1) + m = m + (n + 1).",
|
|
||||||
"Thus, by induction, we have proved that for any natural numbers n and m, n + m = m + n."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural numbers n and m. We will do induction on n.",
|
|
||||||
"Base case: 0 + m = m + 0 by properties of addition.",
|
|
||||||
"Inductive step, we have n + m = m + n. Then (n + 1) + m = (n + m) + 1 = (m + n) + 1 = m + (n + 1).",
|
|
||||||
"Thus, by induction, n + m = m + n, qed."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem add_comm_proved_formal_sketch : ∀ n m : ℕ, n + m = m + n := "],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"by\n",
|
|
||||||
" -- Consider some n and m in Nats.\n",
|
|
||||||
" intros n m\n",
|
|
||||||
" -- Perform induction on n.\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" -- Base case: When n = 0, we need to show 0 + m = m + 0.\n",
|
|
||||||
" -- We have the fact 0 + m = m by the definition of addition.\n",
|
|
||||||
" have h_base: 0 + m = m := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- We also have the fact m + 0 = m by the definition of addition.\n",
|
|
||||||
" have h_symm: m + 0 = m := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" -- Inductive step: Assume n + m = m + n, we need to show succ n + m = m + succ n.\n",
|
|
||||||
" -- By the inductive hypothesis, we have n + m = m + n.\n",
|
|
||||||
" have h_inductive: n + m = m + n := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 1. Note we start with: Nat.succ n + m = m + Nat.succ n, so, pull the succ out from m + Nat.succ n on the right side from the addition using addition facts Nat.add_succ.\n",
|
|
||||||
" have h_pull_succ_out_from_right: m + Nat.succ n = Nat.succ (m + n) := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 2. then to flip m + S n to something like S (n + m) we need to use the IH.\n",
|
|
||||||
" have h_flip_n_plus_m: Nat.succ (n + m) = Nat.succ (m + n) := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- 3. Now the n & m are on the correct sides Nat.succ n + m = Nat.succ (n + m), so let's use the def of addition to pull out the succ from the addition on the left using Nat.succ_add.\n",
|
|
||||||
" have h_pull_succ_out_from_left: Nat.succ n + m = Nat.succ (n + m) := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "3"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,106 +0,0 @@
|
||||||
https://www.evernote.com/shard/s410/nl/75276202/2170cbbd-24a1-2d25-da32-bd8f3270d190?title=prompt%20for%20creating%20toy%20example
|
|
||||||
https://chatgpt.com/c/0ad32608-cbc9-4627-a705-786ed7421826
|
|
||||||
I want all final responses in this format:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, n + 0 = n."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural number n.",
|
|
||||||
"Using the properties of addition, we know that adding zero to any number does not change the value of that number.",
|
|
||||||
"Therefore, we can conclude that n + 0 = n."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural number n.",
|
|
||||||
"From properties of addition, adding zero does not change its values.",
|
|
||||||
"Thus, n + 0 = n."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem n_plus_zero_normal : ∀ n : ℕ, n + 0 = n :="],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"-- Prove that n + 0 = n via a formal proof sketch with holes to be filled\n",
|
|
||||||
"theorem n_plus_zero_proved_formal_sketch'' : ∀ n : ℕ, n + 0 = n := by\n",
|
|
||||||
" -- We have the fact of addition n + 0 = n, use it to show left and right are equal.\n",
|
|
||||||
" have h_nat_add_zero: ∀ n : ℕ, n + 0 = n := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts with to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
],
|
|
||||||
"path_2_file": "~/gold-ai-olympiad/lean_src_proj/lean_basics/basic_nats_using_mathlib_nats2_simp_no_rw.lean",
|
|
||||||
"fl_statement_idx": "0"
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"nl_problem": ["Prove that for any natural number n, 0 + n = n."],
|
|
||||||
"nl_solution": [
|
|
||||||
"Consider any natural number n. We will prove the statement by induction on n.",
|
|
||||||
"Base case: When n = 0, we need to show that 0 + 0 = 0. This is true by the definition of addition.",
|
|
||||||
"Inductive step: Assume that for some natural number n, 0 + n = n. We need to show that 0 + (n + 1) = (n + 1). By the definition of addition and the inductive hypothesis, we have 0 + (n + 1) = (0 + n) + 1 = n + 1. Therefore, the statement holds for n + 1.",
|
|
||||||
"Thus, by induction, we have proved that for any natural number n, 0 + n = n."
|
|
||||||
],
|
|
||||||
"nl_solution_sketch": [
|
|
||||||
"Consider any natural number n, and do induction on n.",
|
|
||||||
"Base case: 0 + 0 = 0 by properties of addition.",
|
|
||||||
"Inductive step we have 0 + n = n. Then 0 + (n + 1) = (0 + n) + 1 = n + 1.",
|
|
||||||
"Where, 0 + n = n by assumption,qed."
|
|
||||||
],
|
|
||||||
"fl_problem": ["theorem zero_plus_n_proved_formal_sketch : ∀ n : ℕ, 0 + n = n :="],
|
|
||||||
"fl_partial_sketch": [
|
|
||||||
"-- Prove that 0 + n = n by induction via a formal proof sketch with holes to be filled\n",
|
|
||||||
"theorem zero_plus_n_proved_formal_sketch'' : ∀ n : ℕ, 0 + n = n := by\n",
|
|
||||||
" -- Consider some n in Nats.\n",
|
|
||||||
" intro n\n",
|
|
||||||
" -- Perform induction on n.\n",
|
|
||||||
" induction n with\n",
|
|
||||||
" | zero =>\n",
|
|
||||||
" -- Base case: 0 + 0 = 0\n",
|
|
||||||
" have h_base: 0 + 0 = 0 := <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n",
|
|
||||||
" | succ n ih =>\n",
|
|
||||||
" -- Inductive step: assume 0 + n = n, prove 0 + succ n = succ n\n",
|
|
||||||
" have h_inductive: 0 + Nat.succ n = Nat.succ n := <TODO_PROOF_OR_HAMMER>\\n",
|
|
||||||
" -- Combine facts to close goal\n",
|
|
||||||
" <TODO_PROOF_OR_HAMMER>\n"
|
|
||||||
],
|
|
||||||
"src_header_fl_problem": ["import Mathlib.Data.Nat.Basic"],
|
|
||||||
"fl_header_sketch": [
|
|
||||||
"import Mathlib.Data.Nat.Basic",
|
|
||||||
"import Aesop"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
I want to translate the following formal proof (solution) in lean 4 to a natural language proof (solution) that a human would write (without lean code in it) and eventually make it into a concise nl_solution_sketch, like the following one:
|
|
||||||
```human_problem_solution_proof.json
|
|
||||||
"nl_problem": ["Let \\[f(x) = \\left\\{\n\\begin{array}{cl} ax+3, &\\text{ if }x>2, \\\\\nx-5 &\\text{ if } -2 \\le x \\le 2, \\\\\n2x-b &\\text{ if } x <-2.\n\\end{array}\n\\right.\\]Find $a+b$ if the piecewise function is continuous (which means that its graph can be drawn without lifting your pencil from the paper)."],
|
|
||||||
"nl_solution_sketch": ["For the piecewise function to be continuous, the cases must \"meet\" at $2$ and $-2$. For example, $ax+3$ and $x-5$ must be equal when $x=2$. This implies $a(2)+3=2-5$, which we solve to get $2a=-6 \\Rightarrow a=-3$. Similarly, $x-5$ and $2x-b$ must be equal when $x=-2$. Substituting, we get $-2-5=2(-2)-b$, which implies $b=3$. So $a+b=-3+3=\\boxed{0}$."]
|
|
||||||
```
|
|
||||||
This is my lean 4 fl theorem (fl problem) and fl proof (fl solution):
|
|
||||||
```
|
|
||||||
-- Prove that n + (m + p) = (n + m) + p
|
|
||||||
theorem add_assoc_proved_formal_sketch : ∀ n m p : ℕ, n + (m + p) = (n + m) + p := by
|
|
||||||
-- Consider some n, m, and p in Nats.
|
|
||||||
intros n m p
|
|
||||||
-- Perform induction on n.
|
|
||||||
induction n with
|
|
||||||
| zero =>
|
|
||||||
-- Base case: When n = 0, we need to show 0 + (m + p) = (0 + m) + p.
|
|
||||||
-- Using the definition of addition, 0 + (m + p) = m + p and (0 + m) + p = m + p.
|
|
||||||
simp [Nat.zero_add, Nat.zero_add]
|
|
||||||
| succ n ih =>
|
|
||||||
-- Inductive step: Assume n + (m + p) = (n + m) + p, we need to show succ n + (m + p) = (succ n + m) + p.
|
|
||||||
-- proof strategy is, we move succ n out (or in) enough times then use the IH until both sides are the same.
|
|
||||||
-- 1. let's start by pulling out the scc from left side and have the entire addition inside the succ.
|
|
||||||
have h_pull_add_succ_out_from_left: Nat.succ n + (m + p) = Nat.succ (n + (m + p)) := by simp [Nat.succ_add]
|
|
||||||
-- 2. Now that we have the IH hypothesis appearing inside the left, let's apply it so we have n + (m + p) = (n + m) + p.
|
|
||||||
have h_inside_left_associates: Nat.succ (n + (m + p)) = Nat.succ ((n + m) + p) := by simp [ih]
|
|
||||||
-- 3. Now that the parenthesis (apps of plus) are on the right place for both side, push the succ on the left twice so both terms are the same.
|
|
||||||
have h_push_succ_in_left_twice: Nat.succ ((n + m) + p) = ((Nat.succ n) + m) + p := by simp [Nat.succ_add, Nat.succ_add]
|
|
||||||
-- Combining these, we get succ n + (m + p) = (succ n + m) + p.
|
|
||||||
simp [h_pull_add_succ_out_from_left, h_inside_left_associates, h_push_succ_in_left_twice]
|
|
||||||
```
|
|
||||||
use the comments to translate the fl proof (solution) to natural language solution then use that to output a natural language concise sketch. Make the natural language proof (solution) sketch concise with the core elements of the solution proof. Do this by first outputting the natural language solution, distill it into a very concise proof sketch in natural language with only the core components. Output everything in a json code block please:
|
|
|
@ -1,2 +0,0 @@
|
||||||
/build
|
|
||||||
/lake-packages/*
|
|
|
@ -1,68 +0,0 @@
|
||||||
inductive UnaryNat : Type
|
|
||||||
| zero : UnaryNat
|
|
||||||
| succ : UnaryNat → UnaryNat
|
|
||||||
deriving Repr
|
|
||||||
|
|
||||||
#check UnaryNat
|
|
||||||
#check UnaryNat.zero
|
|
||||||
#check UnaryNat.succ
|
|
||||||
#check UnaryNat.succ UnaryNat.zero
|
|
||||||
|
|
||||||
-- 0
|
|
||||||
#eval UnaryNat.zero
|
|
||||||
-- 1
|
|
||||||
#eval (UnaryNat.succ UnaryNat.zero)
|
|
||||||
-- 2
|
|
||||||
#eval (UnaryNat.succ (UnaryNat.succ UnaryNat.zero))
|
|
||||||
|
|
||||||
-- open the namespace for UnaryNat
|
|
||||||
open UnaryNat
|
|
||||||
#check zero
|
|
||||||
|
|
||||||
def add_left : UnaryNat → UnaryNat → UnaryNat
|
|
||||||
| zero, n => n
|
|
||||||
| succ m', n => succ (add_left m' n)
|
|
||||||
|
|
||||||
#check add_left zero
|
|
||||||
#eval add_left zero zero
|
|
||||||
#eval add_left zero (succ zero)
|
|
||||||
#eval add_left (succ zero) zero
|
|
||||||
|
|
||||||
def add_right (m n : UnaryNat) : UnaryNat :=
|
|
||||||
match n with
|
|
||||||
| zero => m
|
|
||||||
| succ n' => succ (add_right m n')
|
|
||||||
|
|
||||||
#eval add_right zero zero
|
|
||||||
|
|
||||||
-- todo add_right == add_left
|
|
||||||
|
|
||||||
infixl:65 "+" => add_left
|
|
||||||
|
|
||||||
#eval zero + zero -- add(0, 0)
|
|
||||||
-- a + b + c -> add(add(a, b), c) or add(a, add(b, c))
|
|
||||||
|
|
||||||
theorem add_zero_is_zero : zero + zero = zero := rfl
|
|
||||||
|
|
||||||
-- 0 + n = n
|
|
||||||
theorem zero_add_n_eq_n : ∀ n : UnaryNat, zero + n = n := by
|
|
||||||
intro n
|
|
||||||
rfl
|
|
||||||
-- simp [add_left]
|
|
||||||
-- rw [add_left]
|
|
||||||
-- print the proof term for me please
|
|
||||||
#print zero_add_n_eq_n
|
|
||||||
|
|
||||||
theorem zero_add_n_eq_n' (n : UnaryNat) : zero + n = n := by rfl
|
|
||||||
#print zero_add_n_eq_n'
|
|
||||||
|
|
||||||
-- n + 0 = n
|
|
||||||
theorem n_add_zero_eq_n : ∀ n : UnaryNat, n + zero = n := by
|
|
||||||
intro n
|
|
||||||
induction n with
|
|
||||||
| zero => apply rfl
|
|
||||||
-- | succ n' ih => rw [add_left]; rw [ih]
|
|
||||||
| succ n' ih => rw [add_left, ih]
|
|
||||||
#print n_add_zero_eq_n
|
|
||||||
|
|
||||||
-- comm, assoc, distrib, etc proofs? see software foundations?
|
|
|
@ -1,107 +0,0 @@
|
||||||
---- Example: define unary natural numbers
|
|
||||||
|
|
||||||
---- define unary nats
|
|
||||||
-- define unary natural numbers
|
|
||||||
inductive UnaryNat : Type
|
|
||||||
| Zero: UnaryNat
|
|
||||||
| Succ: UnaryNat -> UnaryNat
|
|
||||||
-- make unary nats printable
|
|
||||||
deriving Repr
|
|
||||||
|
|
||||||
-- define unary natural numbers
|
|
||||||
inductive MyNat : Type
|
|
||||||
| O: MyNat
|
|
||||||
| S: MyNat -> MyNat
|
|
||||||
-- make unary nats printable
|
|
||||||
deriving Repr
|
|
||||||
----
|
|
||||||
|
|
||||||
----
|
|
||||||
-- bring contents of unary nat into scope
|
|
||||||
open UnaryNat
|
|
||||||
-- bring contents of unary nat into scope
|
|
||||||
open MyNat
|
|
||||||
----
|
|
||||||
|
|
||||||
---- check types and evals
|
|
||||||
-- check type of unary nat, zero and succ
|
|
||||||
#check UnaryNat
|
|
||||||
#check UnaryNat.Zero
|
|
||||||
#check UnaryNat.Succ
|
|
||||||
#check UnaryNat.Succ UnaryNat.Zero
|
|
||||||
#check Succ (Succ Zero)
|
|
||||||
#eval UnaryNat.Zero
|
|
||||||
#eval UnaryNat.Succ UnaryNat.Zero
|
|
||||||
#eval UnaryNat.Succ (UnaryNat.Succ UnaryNat.Zero)
|
|
||||||
#eval Succ (Succ Zero)
|
|
||||||
#check O
|
|
||||||
#eval S (S O)
|
|
||||||
----
|
|
||||||
|
|
||||||
---- define addition for unary natural numbers
|
|
||||||
-- define addition for unary natural numbers (without explicit names in function declaration)
|
|
||||||
def add_left : UnaryNat -> UnaryNat -> UnaryNat
|
|
||||||
| Zero, n => n
|
|
||||||
| Succ m, n => Succ (add_left m n)
|
|
||||||
|
|
||||||
-- define addition for unary natural numbers (with explicit names in function declaration)
|
|
||||||
def add_left' (m n : UnaryNat) : UnaryNat :=
|
|
||||||
match m with
|
|
||||||
| Zero => n
|
|
||||||
| Succ m' => Succ (add_left' m' n)
|
|
||||||
|
|
||||||
-- define addition infix notation
|
|
||||||
infixl:65 "+l" => add_left'
|
|
||||||
|
|
||||||
-- define right addition for unary natural numbers (without explicit names in function declaration)
|
|
||||||
def add_right : UnaryNat -> UnaryNat -> UnaryNat
|
|
||||||
| m, Zero => m
|
|
||||||
| m, Succ n => Succ (add_right m n)
|
|
||||||
|
|
||||||
-- define right addition for unary natural numbers (with explicit names in function declaration)
|
|
||||||
def add_right' (m n : UnaryNat) : UnaryNat :=
|
|
||||||
match n with
|
|
||||||
| Zero => m
|
|
||||||
| Succ n' => Succ (add_right' m n')
|
|
||||||
|
|
||||||
-- define right addition infix notation
|
|
||||||
infixl:65 "+r " => add_right'
|
|
||||||
---
|
|
||||||
|
|
||||||
---- evals for addition
|
|
||||||
-- eval addition for unary natural numbers left and right
|
|
||||||
#eval Zero +l Zero
|
|
||||||
#eval Zero +l (Succ Zero)
|
|
||||||
#eval (Succ Zero) +l (Succ Zero)
|
|
||||||
#eval (Succ (Succ Zero)) +r (Succ Zero)
|
|
||||||
---
|
|
||||||
|
|
||||||
---- theorem show non inductive case of addition
|
|
||||||
-- theorem left addition, 0 + n = n (not inductive proof)
|
|
||||||
theorem add_left_zero_plus_n_eq_n (n : UnaryNat) : Zero +l n = n := by rfl
|
|
||||||
-- theorem left addition, 0 + n = n (not inductive proof) with forall statements
|
|
||||||
theorem add_left_zero_plus_n_eq_n' : Zero +l n = n := by intros; rfl
|
|
||||||
theorem add_left_zero_plus_n_eq_n'' : Zero +l n = n := by
|
|
||||||
intros
|
|
||||||
rfl
|
|
||||||
-- theorem right addition, n + 0 = n (not inductive proof)
|
|
||||||
theorem add_right_n_plus_zero_eq_n (n : UnaryNat) : n +r Zero = n := by rfl
|
|
||||||
-- theorem right addition, n + 0 = n (not inductive proof) with forall statements
|
|
||||||
theorem add_right_n_plus_zero_eq_n' : n +r Zero = n := by intros; rfl
|
|
||||||
theorem add_right_n_plus_zero_eq_n'' : n +r Zero = n := by
|
|
||||||
intros
|
|
||||||
rfl
|
|
||||||
----
|
|
||||||
|
|
||||||
---- theorem show inductive case of addition
|
|
||||||
-- theorem left addition, n + 0 = n (inductive proof)
|
|
||||||
theorem add_left_n_plus_zero_eq_n (n : UnaryNat) : n +l Zero = n := by
|
|
||||||
induction n with
|
|
||||||
| Zero => rfl
|
|
||||||
| Succ n' ih => simp [add_left', ih]
|
|
||||||
-- theorem left addition, n + 0 = n (inductive proof) with forall and use inductive hypothesis explicitly
|
|
||||||
theorem add_left_n_plus_zero_eq_n' : ∀ (n : UnaryNat), n +l Zero = n := by
|
|
||||||
intros n
|
|
||||||
induction n with
|
|
||||||
| Zero => rfl
|
|
||||||
| Succ n' ih => simp [add_left']; assumption
|
|
|
@ -1,25 +0,0 @@
|
||||||
/-
|
|
||||||
f(x) = m*x + c at x=x' and anything else o.w. (e.g., x)
|
|
||||||
|
|
||||||
WTS: lim_{x -> x'} f(x) = m*x' + c
|
|
||||||
-/
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
-- Define the limit of a function at a point
|
|
||||||
def limit (f : ℝ → ℝ) (x' : ℝ) (l : ℝ) : Prop :=
|
|
||||||
∀ ε : ℝ, 0 < ε → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < abs (x - x') ∧ abs (x - x') < δ → abs (f x - l) < ε
|
|
||||||
|
|
||||||
-- Define the target function to reason about f(x) = m*x + c at x=x' and anything else o.w. (e.g., x)
|
|
||||||
noncomputable def lin (m c : ℝ) (x : ℝ) : ℝ := m*x + c
|
|
||||||
noncomputable def f (m c hole_x : ℝ) (x : ℝ) : ℝ := if x = hole_x then lin m c x else x
|
|
||||||
|
|
||||||
-- Prove the limit of a linear funtion with a hole at the point would be the lin value at the hole i.e., f(x) = m*x + c at x=x' is m*x' + c
|
|
||||||
theorem limit_of_lin_func_with_hole_eq_lin_func (m c limit_pt_x : ℝ) : limit (f m c hole_x) hole_x (lin m c hole_x) := by
|
|
||||||
unfold limit
|
|
||||||
intros ε ε_pos
|
|
||||||
-- we want 0 < | f(x) - (m*x' + c) | < ε but in format 0 < | x - x' | < δ, so "invert f on both sides and put in δ format"
|
|
||||||
-- we want 0 < | m*x + c - (m*x' + c) | < ε using def of f not at x'
|
|
||||||
-- we want 0 < |m| * | x - x' | < ε --> 0 < | x - x' | < ε / |m| so δ = ε / |m|
|
|
||||||
use ε / abs m
|
|
||||||
apply And.intro
|
|
||||||
.
|
|
|
@ -1,8 +0,0 @@
|
||||||
-- Define a singly linked list
|
|
||||||
inductive Singly_Node (α : Type) : Type
|
|
||||||
| nil : Singly_Node α
|
|
||||||
| cons : α → Singly_Node α → Singly_Node α
|
|
||||||
|
|
||||||
#check Singly_Node
|
|
||||||
#check Singly_Node.nil
|
|
||||||
#check Singly_Node.cons
|
|
|
@ -1,7 +0,0 @@
|
||||||
#
|
|
||||||
|
|
||||||
## Appendix
|
|
||||||
|
|
||||||
### Questions:
|
|
||||||
|
|
||||||
Q:
|
|
|
@ -1,128 +0,0 @@
|
||||||
/-
|
|
||||||
Task: prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides.
|
|
||||||
|
|
||||||
def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M > 0, ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
|
|
||||||
theorem one_over_x_has_vertical_asymptote_both_sides : lim_{x -> c} f(x) = +-∞
|
|
||||||
Proof:
|
|
||||||
consider any M > 0
|
|
||||||
now show: ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
So show: ∃ δ > 0, ∀ x, 0 < |x| < δ → M < |1/x|, so in particular don't forget you want -δ < x < δ when guessing δ from goal.
|
|
||||||
-- guess, delta (s.tif antecedent holds goal holds), so use goal M < |f(x)|, which is M < |1/x| ->
|
|
||||||
1. M < 1/x (so x > 0 since M>0) -> x < M^⁻¹
|
|
||||||
2. M < -1/x (so x < 0 since M>0 <-> M <-M) -> M * -x < 1 -> -x < M^⁻¹ -> -M^⁻¹ < -x
|
|
||||||
1 & 2 means -M^⁻¹ < x < M^⁻¹ <-> |x| < M^⁻¹, so choose δ = M^⁻¹
|
|
||||||
-- end guess heuristic reasoning
|
|
||||||
So continue proof by choosing δ = M^⁻¹, and then show that for all x, 0 < |x| < δ -> M < |1/x|
|
|
||||||
wts: for all x, 0 < |x| < M⁻¹ → M < |1/x|
|
|
||||||
so consider any x such that 0 < |x| < M⁻¹
|
|
||||||
I don't really know how to manipulate things freely with abs |.| so I will consider all the cases.
|
|
||||||
Hypothesis: 0 < |x| < M⁻¹ <-> either 0 < x < M⁻¹ or 0 < -x < M⁻¹ so for both cases we need to show it implies (either because x ∈ ℝ cannot satisfy both)
|
|
||||||
Goal: M < |1/x| <-> M < 1/x for positives or M < -1/x for negatives (either because 1/x ∈ ℝ cannot satisfy both)
|
|
||||||
case 1: I conjecture 0 < x < M⁻¹ -> M < 1/x
|
|
||||||
1. M < 1/x -> x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, x > 0 so 1/x > 0)
|
|
||||||
2. 0 < x < M^⁻¹ (as currently required)
|
|
||||||
case 2: I conjecture 0 < -x < M⁻¹ -> M < -1/x
|
|
||||||
1. M < -1/x -> M * -x < 1 -> -x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, -x > 0 so -1/x > 0)
|
|
||||||
2. 0 < -x < M^⁻¹ (as currently required)
|
|
||||||
Qed.
|
|
||||||
|
|
||||||
facts we will need (I think):
|
|
||||||
identity cancellation (which needs val ≠ 0)
|
|
||||||
multiply on both sides by some value and inequality doesn't change (or if it does that we show the multiplying val is negative)
|
|
||||||
simplifying 1 * val = val in either side
|
|
||||||
perhaps communtativity or/and associativity of multiplication to make sure things cancel simplifying as needed
|
|
||||||
|
|
||||||
note: to concluse p ∨ q we only need to show p or q, so we can consider them separately (if one holds you can conclude the disjoncution but also if both hold)
|
|
||||||
-/
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
-- define f(x) = 1/x = x^⁻¹ for x ≠ 0 latter for mathlib notation (less friction during proofs)
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#check f
|
|
||||||
-- note evals don't work so for unit tests we will de a theorem, due to ℝ not being "computable"
|
|
||||||
theorem f_evals_unit_test : f 2 = 1/2 := by simp [f]
|
|
||||||
#print f_evals_unit_test
|
|
||||||
|
|
||||||
-- define unbounded limit (form both sides) as a predicate (proposition)
|
|
||||||
def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < |x - c| ∧ |x - c| < δ → M < |f x|
|
|
||||||
#check unbounded_limit
|
|
||||||
#print unbounded_limit
|
|
||||||
-- note: writing everything in terms of lt since gt is written in terms of lt
|
|
||||||
|
|
||||||
-- theorem to prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides
|
|
||||||
theorem one_over_x_has_vertical_asymptote_both_sides : unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- consider some M > 0
|
|
||||||
intro M h_zero_lt_M
|
|
||||||
-- since goal doesn't have zeros, but we want to use it to match the antecedent, let's simplify the zeros by using the fact x - 0 = 0 at the goal
|
|
||||||
simp only [sub_zero]
|
|
||||||
-- guess δ = M^⁻¹ using goal i.e. M < |1/x| so M < 1/x so x < 1/M = M^⁻¹ and -M < -1/x so -x < M^⁻¹ as δ = M^⁻¹ should work
|
|
||||||
use M⁻¹
|
|
||||||
-- show 0 < δ = M^⁻¹, first deconstruct the ∧ in the goal
|
|
||||||
apply And.intro
|
|
||||||
-- show 0 < M^⁻¹
|
|
||||||
. exact inv_pos.2 h_zero_lt_M
|
|
||||||
. --introduce x and hypothesis deconstructed by and
|
|
||||||
intro x ⟨h_zero_lt_abs_x, h_x_lt_δ⟩
|
|
||||||
-- unfold abs on hypothesis and goal (since I assume it's harder to manipulate abs |.| function)
|
|
||||||
#check abs -- abs := mabs (a : α) : α := a ⊔ a⁻¹ == a -> a ⊔ -a
|
|
||||||
unfold abs at h_x_lt_δ h_zero_lt_abs_x; unfold abs
|
|
||||||
|
|
||||||
-- want to show (wts) M < |1/x|, so transform the goal to M < 1/x for x > 0 and M < -1/x for x < 0
|
|
||||||
-- transform the goal M < x⁻¹ ⊔ -x⁻¹ --> M < x⁻¹ ∨ M < -x⁻¹
|
|
||||||
#check lt_sup_iff -- lt_sup_iff : a < b ⊔ c ↔ a < b ∨ a < c
|
|
||||||
-- simp only [lt_sup_iff] -- also works
|
|
||||||
apply lt_sup_iff.mpr
|
|
||||||
-- transform hypothesis 0 < x ⊔ -x --> 0 < x ∨ 0 < -x
|
|
||||||
apply lt_sup_iff.mp at h_zero_lt_abs_x
|
|
||||||
-- transform hypothesis x ⊔ -x < M⁻¹ --> x < M⁻¹ ∧ -x < M⁻¹
|
|
||||||
#check sup_lt_iff -- sup_lt_iff : a ⊔ b < c ↔ a < c ∧ b < c
|
|
||||||
apply sup_lt_iff.mp at h_x_lt_δ
|
|
||||||
-- to try to close goal M < |1/x|, let's consider both cases by break h_zero_lt_abs_x into both cases 0 < x and 0 < -x and close goals with both cases
|
|
||||||
#check Or
|
|
||||||
#check Or.inl
|
|
||||||
cases h_zero_lt_abs_x with -- TODO: how to name hypothesis with cases in lean4
|
|
||||||
| inl h_x_pos =>
|
|
||||||
-- focus on positive target M < x⁻¹ given we are on the x > 0 case x, so also use positive hypothesis x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
apply Or.inl
|
|
||||||
apply And.left at h_x_lt_δ
|
|
||||||
-- on goal: mul right goal both sides by x (x > 0), then cancel x⁻¹ with mul x (needs x⁻¹ ≠ 0)
|
|
||||||
have h_x_ne_zero : x ≠ 0 := ne_of_gt h_x_pos
|
|
||||||
-- mul both sides by M right
|
|
||||||
#check mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
-- exact (lt_inv h_zero_lt_M h_x_pos).mpr h_x_lt_δ -- also worked!
|
|
||||||
rw [← mul_lt_mul_right h_x_pos]
|
|
||||||
nth_rewrite 2 [mul_comm]
|
|
||||||
#check mul_inv_cancel -- (h : a ≠ 0) : a * a⁻¹ = 1
|
|
||||||
rw [mul_inv_cancel h_x_ne_zero]
|
|
||||||
-- move M to the left by mul by M⁻¹ > 0 (needs M⁻¹ ≠ 0 and/or M ≠ 0)
|
|
||||||
have h_M_inv_lt_zero : 0 < M⁻¹ := inv_pos.2 h_zero_lt_M
|
|
||||||
rw [← mul_lt_mul_left h_M_inv_lt_zero]
|
|
||||||
rw [← mul_assoc]
|
|
||||||
have h_M_ne_zero : M ≠ 0 := ne_of_gt h_zero_lt_M
|
|
||||||
nth_rewrite 2 [mul_comm]; rewrite [mul_inv_cancel h_M_ne_zero]; simp
|
|
||||||
assumption
|
|
||||||
| inr h_x_neg =>
|
|
||||||
-- focus on negative target M < -x⁻¹ given we are on the x < 0 case x, so also use negative hypothesis -x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
apply Or.inr
|
|
||||||
apply And.right at h_x_lt_δ
|
|
||||||
-- pass -x⁻¹ to the left and pass M to the right
|
|
||||||
#check neg_lt -- -a < b ↔ -b < a
|
|
||||||
-- transform expression -(x⁻¹) to (-x)⁻¹
|
|
||||||
#check neg_inv -- -a⁻¹ = (-a)⁻¹
|
|
||||||
rw [neg_inv]
|
|
||||||
-- multiply both sides by -x (needs -x > 0) left
|
|
||||||
#check mul_lt_mul_left -- (a0 : 0 < a) : a * b < a * c ↔ b < c
|
|
||||||
rw [← mul_lt_mul_left h_x_neg]
|
|
||||||
-- simp only [neg_mul, neg_lt_neg_iff]
|
|
||||||
have h_neg_x_ne_zero : -x ≠ 0 := ne_of_gt h_x_neg
|
|
||||||
rw [mul_inv_cancel h_neg_x_ne_zero]
|
|
||||||
-- move M to the right of the lt by mul right by 0 < M⁻¹ (needs M ≠ 0 for inv cancelation)
|
|
||||||
have h_M_inv_lt_zero : 0 < M⁻¹ := inv_pos.mpr h_zero_lt_M
|
|
||||||
rw [← mul_lt_mul_right h_M_inv_lt_zero]
|
|
||||||
rw [mul_assoc]
|
|
||||||
have h_M_ne_zero : M ≠ 0 := ne_of_gt h_zero_lt_M
|
|
||||||
simp only [mul_inv_cancel h_M_ne_zero]
|
|
||||||
simp
|
|
||||||
assumption
|
|
|
@ -1,104 +0,0 @@
|
||||||
/-
|
|
||||||
Task: prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides.
|
|
||||||
|
|
||||||
def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M > 0, ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
|
|
||||||
theorem one_over_x_has_vertical_asymptote_both_sides : lim_{x -> c} f(x) = +-∞
|
|
||||||
Proof:
|
|
||||||
consider any M > 0
|
|
||||||
now show: ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
So show: ∃ δ > 0, ∀ x, 0 < |x| < δ → M < |1/x|, so in particular don't forget you want -δ < x < δ when guessing δ from goal.
|
|
||||||
-- guess, delta (s.tif antecedent holds goal holds), so use goal M < |f(x)|, which is M < |1/x| ->
|
|
||||||
1. M < 1/x (so x > 0 since M>0) -> x < M^⁻¹
|
|
||||||
2. M < -1/x (so x < 0 since M>0 <-> M <-M) -> M * -x < 1 -> -x < M^⁻¹ -> -M^⁻¹ < -x
|
|
||||||
1 & 2 means -M^⁻¹ < x < M^⁻¹ <-> |x| < M^⁻¹, so choose δ = M^⁻¹
|
|
||||||
-- end guess heuristic reasoning
|
|
||||||
So continue proof by choosing δ = M^⁻¹, and then show that for all x, 0 < |x| < δ -> M < |1/x|
|
|
||||||
wts: for all x, 0 < |x| < M⁻¹ → M < |1/x|
|
|
||||||
so consider any x such that 0 < |x| < M⁻¹
|
|
||||||
I don't really know how to manipulate things freely with abs |.| so I will consider all the cases.
|
|
||||||
Hypothesis: 0 < |x| < M⁻¹ <-> either 0 < x < M⁻¹ or 0 < -x < M⁻¹ so for both cases we need to show it implies (either because x ∈ ℝ cannot satisfy both)
|
|
||||||
Goal: M < |1/x| <-> M < 1/x for positives or M < -1/x for negatives (either because 1/x ∈ ℝ cannot satisfy both)
|
|
||||||
case 1: I conjecture 0 < x < M⁻¹ -> M < 1/x
|
|
||||||
1. M < 1/x -> x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, x > 0 so 1/x > 0)
|
|
||||||
2. 0 < x < M^⁻¹ (as currently required)
|
|
||||||
case 2: I conjecture 0 < -x < M⁻¹ -> M < -1/x
|
|
||||||
1. M < -1/x -> M * -x < 1 -> -x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, -x > 0 so -1/x > 0)
|
|
||||||
2. 0 < -x < M^⁻¹ (as currently required)
|
|
||||||
Qed.
|
|
||||||
|
|
||||||
facts we will need (I think):
|
|
||||||
identity cancellation (which needs val ≠ 0)
|
|
||||||
multiply on both sides by some value and inequality doesn't change (or if it does that we show the multiplying val is negative)
|
|
||||||
simplifying 1 * val = val in either side
|
|
||||||
perhaps communtativity or/and associativity of multiplication to make sure things cancel simplifying as needed
|
|
||||||
|
|
||||||
note: to concluse p ∨ q we only need to show p or q, so we can consider them separately (if one holds you can conclude the disjoncution but also if both hold)
|
|
||||||
-/
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
-- define f(x) = 1/x = x^⁻¹ for x ≠ 0 latter for mathlib notation (less friction during proofs)
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#check f
|
|
||||||
-- note evals don't work so for unit tests we will de a theorem, due to ℝ not being "computable"
|
|
||||||
theorem f_evals_unit_test : f 2 = 1/2 := by simp [f]
|
|
||||||
#print f_evals_unit_test
|
|
||||||
|
|
||||||
-- define unbounded limit (form both sides) as a predicate (proposition)
|
|
||||||
def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < |x - c| ∧ |x - c| < δ → M < |f x|
|
|
||||||
#check unbounded_limit
|
|
||||||
#print unbounded_limit
|
|
||||||
-- note: writing everything in terms of lt since gt is written in terms of lt
|
|
||||||
|
|
||||||
-- theorem to prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides
|
|
||||||
theorem one_over_x_has_vertical_asymptote_both_sides : unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- consider some M > 0
|
|
||||||
intro M h_zero_lt_M
|
|
||||||
-- since goal doesn't have zeros, but we want to use it to match the antecedent, let's simplify the zeros by using the fact x - 0 = 0 at the goal
|
|
||||||
simp only [sub_zero]
|
|
||||||
-- guess δ = M^⁻¹ using goal i.e. M < |1/x| so M < 1/x so x < 1/M = M^⁻¹ and -M < -1/x so -x < M^⁻¹ as δ = M^⁻¹ should work
|
|
||||||
use M⁻¹
|
|
||||||
-- show 0 < δ = M^⁻¹, first deconstruct the ∧ in the goal
|
|
||||||
apply And.intro
|
|
||||||
-- show 0 < M^⁻¹
|
|
||||||
. exact inv_pos.2 h_zero_lt_M
|
|
||||||
. --introduce x and hypothesis deconstructed by and
|
|
||||||
intro x ⟨h_zero_lt_abs_x, h_x_lt_δ⟩
|
|
||||||
-- unfold abs on hypothesis and goal (since I assume it's harder to manipulate abs |.| function)
|
|
||||||
#check abs -- abs := mabs (a : α) : α := a ⊔ a⁻¹ == a -> a ⊔ -a
|
|
||||||
unfold abs at h_x_lt_δ h_zero_lt_abs_x; unfold abs
|
|
||||||
|
|
||||||
-- want to show (wts) M < |1/x|, so transform the goal to M < 1/x for x > 0 and M < -1/x for x < 0
|
|
||||||
-- transform the goal M < x⁻¹ ⊔ -x⁻¹ --> M < x⁻¹ ∨ M < -x⁻¹
|
|
||||||
#check lt_sup_iff -- lt_sup_iff : a < b ⊔ c ↔ a < b ∨ a < c
|
|
||||||
-- simp only [lt_sup_iff] -- also works
|
|
||||||
apply lt_sup_iff.mpr
|
|
||||||
-- transform hypothesis 0 < x ⊔ -x --> 0 < x ∨ 0 < -x
|
|
||||||
apply lt_sup_iff.mp at h_zero_lt_abs_x
|
|
||||||
-- transform hypothesis x ⊔ -x < M⁻¹ --> x < M⁻¹ ∧ -x < M⁻¹
|
|
||||||
#check sup_lt_iff -- sup_lt_iff : a ⊔ b < c ↔ a < c ∧ b < c
|
|
||||||
apply sup_lt_iff.mp at h_x_lt_δ
|
|
||||||
-- to try to close goal M < |1/x|, let's consider both cases by break h_zero_lt_abs_x into both cases 0 < x and 0 < -x and close goals with both cases
|
|
||||||
#check Or
|
|
||||||
#check Or.inl
|
|
||||||
cases h_zero_lt_abs_x with -- TODO: how to name hypothesis with cases in lean4
|
|
||||||
| inl h_x_pos =>
|
|
||||||
-- focus on positive target M < x⁻¹ given we are on the x > 0 case x, so also use positive hypothesis x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
apply Or.inl
|
|
||||||
apply And.left at h_x_lt_δ
|
|
||||||
-- on goal: mul right goal both sides by x (x > 0), then cancel x⁻¹ with mul x (needs x⁻¹ ≠ 0)
|
|
||||||
-- mul both sides by M right
|
|
||||||
#check mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
#check lt_inv -- (ha : 0 < a) (hb : 0 < b) : a < b⁻¹ ↔ b < a⁻¹
|
|
||||||
exact (lt_inv h_zero_lt_M h_x_pos).mpr h_x_lt_δ -- also worked!
|
|
||||||
| inr h_x_neg =>
|
|
||||||
-- focus on negative target M < -x⁻¹ given we are on the x < 0 case x, so also use negative hypothesis -x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
apply Or.inr
|
|
||||||
apply And.right at h_x_lt_δ
|
|
||||||
-- on goal: mul right goal both sides by -x (x < 0), then cancel -x⁻¹ with mul -x (needs -x⁻¹ ≠ 0)
|
|
||||||
#check lt_inv -- (ha : 0 < a) (hb : 0 < b) : a < b⁻¹ ↔ b < a⁻¹
|
|
||||||
-- rewrite -x⁻¹ --> -(x⁻¹) so swamp sides of inequality using lt_inv works
|
|
||||||
rw [neg_inv]
|
|
||||||
-- have h_neg_x_inv_eq_neg_inv_x : -x⁻¹ = -(x⁻¹) := by simp
|
|
||||||
exact (lt_inv h_zero_lt_M h_x_neg).mpr h_x_lt_δ -- also worked!
|
|
|
@ -1,135 +0,0 @@
|
||||||
-- /-
|
|
||||||
-- Task: prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides.
|
|
||||||
|
|
||||||
-- def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M > 0, ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
|
|
||||||
-- theorem one_over_x_has_vertical_asymptote_both_sides : lim_{x -> c} f(x) = +-∞
|
|
||||||
-- Proof:
|
|
||||||
-- consider any M > 0
|
|
||||||
-- now show: ∃ δ > 0, ∀ x, 0 < |x - c| < δ → M < |f x|
|
|
||||||
-- So show: ∃ δ > 0, ∀ x, 0 < |x| < δ → M < |1/x|, so in particular don't forget you want -δ < x < δ when guessing δ from goal.
|
|
||||||
-- -- guess, delta (s.tif antecedent holds goal holds), so use goal M < |f(x)|, which is M < |1/x| ->
|
|
||||||
-- 1. M < 1/x (so x > 0 since M>0) -> x < M^⁻¹
|
|
||||||
-- 2. M < -1/x (so x < 0 since M>0 <-> M <-M) -> M * -x < 1 -> -x < M^⁻¹ -> -M^⁻¹ < -x
|
|
||||||
-- 1 & 2 means -M^⁻¹ < x < M^⁻¹ <-> |x| < M^⁻¹, so choose δ = M^⁻¹
|
|
||||||
-- -- end guess heuristic reasoning
|
|
||||||
-- So continue proof by choosing δ = M^⁻¹, and then show that for all x, 0 < |x| < δ -> M < |1/x|
|
|
||||||
-- wts: for all x, 0 < |x| < M⁻¹ → M < |1/x|
|
|
||||||
-- so consider any x such that 0 < |x| < M⁻¹
|
|
||||||
-- I don't really know how to manipulate things freely with abs |.| so I will consider all the cases.
|
|
||||||
-- Hypothesis: 0 < |x| < M⁻¹ <-> either 0 < x < M⁻¹ or 0 < -x < M⁻¹ so for both cases we need to show it implies (either because x ∈ ℝ cannot satisfy both)
|
|
||||||
-- Goal: M < |1/x| <-> M < 1/x for positives or M < -1/x for negatives (either because 1/x ∈ ℝ cannot satisfy both)
|
|
||||||
-- case 1: I conjecture 0 < x < M⁻¹ -> M < 1/x
|
|
||||||
-- 1. M < 1/x -> x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, x > 0 so 1/x > 0)
|
|
||||||
-- 2. 0 < x < M^⁻¹ (as currently required)
|
|
||||||
-- case 2: I conjecture 0 < -x < M⁻¹ -> M < -1/x
|
|
||||||
-- 1. M < -1/x -> M * -x < 1 -> -x < 1/M = M^⁻¹ (valid since M > 0, so 1/M > 0, -x > 0 so -1/x > 0)
|
|
||||||
-- 2. 0 < -x < M^⁻¹ (as currently required)
|
|
||||||
-- Qed.
|
|
||||||
|
|
||||||
-- facts we will need (I think):
|
|
||||||
-- identity cancellation (which needs val ≠ 0)
|
|
||||||
-- multiply on both sides by some value and inequality doesn't change (or if it does that we show the multiplying val is negative)
|
|
||||||
-- simplifying 1 * val = val in either side
|
|
||||||
-- perhaps communtativity or/and associativity of multiplication to make sure things cancel simplifying as needed
|
|
||||||
|
|
||||||
-- note: to concluse p ∨ q we only need to show p or q, so we can consider them separately (if one holds you can conclude the disjoncution but also if both hold)
|
|
||||||
-- -/
|
|
||||||
-- import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
-- -- define f(x) = 1/x = x^⁻¹ for x ≠ 0 latter for mathlib notation (less friction during proofs)
|
|
||||||
-- noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
-- #check f
|
|
||||||
-- -- note evals don't work so for unit tests we will de a theorem, due to ℝ not being "computable"
|
|
||||||
-- theorem f_evals_unit_test : f 2 = 1/2 := by simp [f]
|
|
||||||
-- #print f_evals_unit_test
|
|
||||||
|
|
||||||
-- -- define unbounded limit (form both sides) as a predicate (proposition)
|
|
||||||
-- def unbounded_limit (f : ℝ → ℝ) (c : ℝ) : Prop := ∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < |x - c| ∧ |x - c| < δ → M < |f x|
|
|
||||||
-- #check unbounded_limit
|
|
||||||
-- #print unbounded_limit
|
|
||||||
-- -- note: writing everything in terms of lt since gt is written in terms of lt
|
|
||||||
|
|
||||||
-- -- theorem to prove that f(x) = 1/x has a vertical asymptote (unbounded limit) at x = 0 from both sides
|
|
||||||
-- theorem one_over_x_has_vertical_asymptote_both_sides : unbounded_limit f 0 := by
|
|
||||||
-- unfold unbounded_limit f
|
|
||||||
-- -- consider some M > 0
|
|
||||||
-- intro M h_zero_lt_M
|
|
||||||
-- -- since goal doesn't have zeros, but we want to use it to match the antecedent, let's simplify the zeros by using the fact x - 0 = 0 at the goal
|
|
||||||
-- simp only [sub_zero]
|
|
||||||
-- -- guess δ = M^⁻¹ using goal i.e. M < |1/x| so M < 1/x so x < 1/M = M^⁻¹ and -M < -1/x so -x < M^⁻¹ as δ = M^⁻¹ should work
|
|
||||||
-- use M⁻¹
|
|
||||||
-- -- show 0 < δ = M^⁻¹, first deconstruct the ∧ in the goal
|
|
||||||
-- apply And.intro
|
|
||||||
-- -- show 0 < M^⁻¹
|
|
||||||
-- . exact inv_pos.2 h_zero_lt_M
|
|
||||||
-- . --introduce x and hypothesis deconstructed by and
|
|
||||||
-- intro x ⟨h_zero_lt_abs_x, h_x_lt_δ⟩
|
|
||||||
-- -- unfold abs on hypothesis and goal (since I assume it's harder to manipulate abs |.| function)
|
|
||||||
-- #check abs -- abs := mabs (a : α) : α := a ⊔ a⁻¹ == a -> a ⊔ -a
|
|
||||||
-- unfold abs at h_x_lt_δ h_zero_lt_abs_x; unfold abs
|
|
||||||
|
|
||||||
-- -- want to show (wts) M < |1/x|, so transform the goal to M < 1/x for x > 0 and M < -1/x for x < 0
|
|
||||||
-- -- transform the goal M < x⁻¹ ⊔ -x⁻¹ --> M < x⁻¹ ∨ M < -x⁻¹
|
|
||||||
-- #check lt_sup_iff -- lt_sup_iff : a < b ⊔ c ↔ a < b ∨ a < c
|
|
||||||
-- -- simp only [lt_sup_iff] -- also works
|
|
||||||
-- apply lt_sup_iff.mpr
|
|
||||||
-- -- transform hypothesis 0 < x ⊔ -x --> 0 < x ∨ 0 < -x
|
|
||||||
-- apply lt_sup_iff.mp at h_zero_lt_abs_x
|
|
||||||
-- -- transform hypothesis x ⊔ -x < M⁻¹ --> x < M⁻¹ ∧ -x < M⁻¹
|
|
||||||
-- #check sup_lt_iff -- sup_lt_iff : a ⊔ b < c ↔ a < c ∧ b < c
|
|
||||||
-- apply sup_lt_iff.mp at h_x_lt_δ
|
|
||||||
-- -- to try to close goal M < |1/x|, let's consider both cases by break h_zero_lt_abs_x into both cases 0 < x and 0 < -x and close goals with both cases
|
|
||||||
-- #check Or
|
|
||||||
-- #check Or.inl
|
|
||||||
-- cases h_zero_lt_abs_x
|
|
||||||
-- #check h_zero_lt_M
|
|
||||||
-- #check h✝
|
|
||||||
-- rename 0 < x h_x_pos
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- -- cases h_zero_lt_abs_x with -- TODO: how to name hypothesis with cases in lean4
|
|
||||||
-- -- | inl h_x_pos =>
|
|
||||||
-- -- -- focus on positive target M < x⁻¹ given we are on the x > 0 case x, so also use positive hypothesis x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
-- -- apply Or.inl
|
|
||||||
-- -- apply And.left at h_x_lt_δ
|
|
||||||
-- -- -- on goal: mul right goal both sides by x (x > 0), then cancel x⁻¹ with mul x (needs x⁻¹ ≠ 0)
|
|
||||||
-- -- have h_x_ne_zero : x ≠ 0 := ne_of_gt h_x_pos
|
|
||||||
-- -- -- mul both sides by M right
|
|
||||||
-- -- #check mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
-- -- -- exact (lt_inv h_zero_lt_M h_x_pos).mpr h_x_lt_δ -- also worked!
|
|
||||||
-- -- rw [← mul_lt_mul_right h_x_pos]
|
|
||||||
-- -- nth_rewrite 2 [mul_comm]
|
|
||||||
-- -- #check mul_inv_cancel -- (h : a ≠ 0) : a * a⁻¹ = 1
|
|
||||||
-- -- rw [mul_inv_cancel h_x_ne_zero]
|
|
||||||
-- -- -- move M to the left by mul by M⁻¹ > 0 (needs M⁻¹ ≠ 0 and/or M ≠ 0)
|
|
||||||
-- -- have h_M_inv_lt_zero : 0 < M⁻¹ := inv_pos.2 h_zero_lt_M
|
|
||||||
-- -- rw [← mul_lt_mul_left h_M_inv_lt_zero]
|
|
||||||
-- -- rw [← mul_assoc]
|
|
||||||
-- -- have h_M_ne_zero : M ≠ 0 := ne_of_gt h_zero_lt_M
|
|
||||||
-- -- nth_rewrite 2 [mul_comm]; rewrite [mul_inv_cancel h_M_ne_zero]; simp
|
|
||||||
-- -- assumption
|
|
||||||
-- -- | inr h_x_neg =>
|
|
||||||
-- -- -- focus on negative target M < -x⁻¹ given we are on the x < 0 case x, so also use negative hypothesis -x < M⁻¹, simplify any 1 * val = val or val * 1 = val
|
|
||||||
-- -- apply Or.inr
|
|
||||||
-- -- apply And.right at h_x_lt_δ
|
|
||||||
-- -- -- pass -x⁻¹ to the left and pass M to the right
|
|
||||||
-- -- #check neg_lt -- -a < b ↔ -b < a
|
|
||||||
-- -- -- transform expression -(x⁻¹) to (-x)⁻¹
|
|
||||||
-- -- #check neg_inv -- -a⁻¹ = (-a)⁻¹
|
|
||||||
-- -- rw [neg_inv]
|
|
||||||
-- -- -- multiply both sides by -x (needs -x > 0) left
|
|
||||||
-- -- #check mul_lt_mul_left -- (a0 : 0 < a) : a * b < a * c ↔ b < c
|
|
||||||
-- -- rw [← mul_lt_mul_left h_x_neg]
|
|
||||||
-- -- -- simp only [neg_mul, neg_lt_neg_iff]
|
|
||||||
-- -- have h_neg_x_ne_zero : -x ≠ 0 := ne_of_gt h_x_neg
|
|
||||||
-- -- rw [mul_inv_cancel h_neg_x_ne_zero]
|
|
||||||
-- -- -- move M to the right of the lt by mul right by 0 < M⁻¹ (needs M ≠ 0 for inv cancelation)
|
|
||||||
-- -- have h_M_inv_lt_zero : 0 < M⁻¹ := inv_pos.mpr h_zero_lt_M
|
|
||||||
-- -- rw [← mul_lt_mul_right h_M_inv_lt_zero]
|
|
||||||
-- -- rw [mul_assoc]
|
|
||||||
-- -- have h_M_ne_zero : M ≠ 0 := ne_of_gt h_zero_lt_M
|
|
||||||
-- -- simp only [mul_inv_cancel h_M_ne_zero]
|
|
||||||
-- -- simp
|
|
||||||
-- -- assumption
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"problem": "How many vertical asymptotes does 1/x have around 0 from the right?",
|
|
||||||
"level": "Level 2",
|
|
||||||
"type": "Algebra",
|
|
||||||
"solution": "$1/x$ goes to positive infinity from the right as $x$ goes to zero and nowhere else, so it has $\\boxed{1}$ veritcal asymptote."
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"problem": "Show that 1/x has an unbounded limit from the right as x approaches zero?",
|
|
||||||
"level": "Level 2",
|
|
||||||
"type": "Algebra",
|
|
||||||
"solution": "..."
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/-
|
|
||||||
theorem: lim_{x -> c+} f(x) = +infinity
|
|
||||||
|
|
||||||
x + infinit = +infinity
|
|
||||||
|
|
||||||
lim_{x -> c} f(x) = L
|
|
||||||
∀ ε > 0, ∃ δ > 0, 0 < |x - c| < δ → 0 < |f(x) - L| < ε
|
|
||||||
|
|
||||||
L = + infinity
|
|
||||||
consider some ε > 0
|
|
||||||
0 < |f(x) - L| < ε
|
|
||||||
0 < |f(x) - +infinity| < ε
|
|
||||||
|
|
||||||
--> this formalization doens't seem promising
|
|
||||||
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: lim_{x -> 0+} 1/x = +infinity
|
|
||||||
∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
-- unboudned limit := "for any M, there exists a sufficiently close x s.t. f(x) is strictly greater than M"
|
|
||||||
∀ M: ℝ, 0 < M, ∃ δ : ℝ, 0 < δ, ∀ x : ℝ, 0 < x - c < δ → M < f(x)
|
|
||||||
|
|
||||||
proof:
|
|
||||||
consider some M > 0 (intro M)
|
|
||||||
-- choose delta, M < f(x) --> M < 1/x --> 1/M > x --> x < M⁻¹
|
|
||||||
δ = M⁻¹
|
|
||||||
. show 0 < δ
|
|
||||||
fact M > 0 --> M⁻¹ > 0 (by lemma in lean, division by positive number)
|
|
||||||
0 < x - c -> rewrite
|
|
||||||
-> 0 < x
|
|
||||||
x - c < δ -> rewrite
|
|
||||||
-> x < M⁻¹
|
|
||||||
(WTS: M < x⁻¹)
|
|
||||||
x < M⁻¹
|
|
||||||
-- multiply both sides by x⁻¹ if x⁻¹ > 0 (lemma, have stmt)
|
|
||||||
-> 0 < x --> x⁻¹ > 0
|
|
||||||
x⁻¹ * x < M^⁻¹ * x⁻¹
|
|
||||||
by identity x⁻¹ * x = 1 of fields (lemma in lean or automation)
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
-- multiply both sides by M if M > 0
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
M * 1 < M * M⁻¹ * x⁻¹
|
|
||||||
-- identity
|
|
||||||
M < x⁻¹
|
|
||||||
Qed
|
|
||||||
|
|
||||||
-/
|
|
||||||
|
|
||||||
-- import real numbers form mathlib
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#print f
|
|
||||||
#check f
|
|
||||||
#check f 1
|
|
||||||
-- #eval f 1
|
|
||||||
-- theorem any_R : ℝ -> R := λ x : ℝ, x -- TODO
|
|
||||||
theorem unit_test_f_1 : f 1 = 1 := by simp [f]
|
|
||||||
theorem unit_test_f_2 : f 2 = 1/2 := by simp [f]
|
|
||||||
noncomputable def f' (x : ℝ) : ℝ := 1/x
|
|
||||||
theorem units_f_eq_f' : ∀ x : ℝ , f x = f' x := by simp [f, f']
|
|
||||||
#print units_f_eq_f'
|
|
||||||
|
|
||||||
-- lim_{x -> c+} f(x) = +infinity := ∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
def unbounded_limit (f : ℝ -> ℝ) (c : ℝ) : Prop :=
|
|
||||||
∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < x - c ∧ x - c < δ → M < f x
|
|
||||||
|
|
||||||
-- show 1/x is unbounded as x -> 0 (or 1/x has a veritcal asymptote at x = 0)
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- choose M : ℝ and is M > 0
|
|
||||||
intro M h_M_pos
|
|
||||||
-- choose delta = M⁻¹ by a tactic
|
|
||||||
use M⁻¹
|
|
||||||
-- deconstruct the constructor Left ∧ Right = And(Left, Right) to Left, Right using a tactic
|
|
||||||
apply And.intro
|
|
||||||
. exact (by simp [h_M_pos]) -- TODO try to find the lemma in mathlib to prove this
|
|
||||||
. intro x ⟨h_x_pos, h_x_lt_M⟩
|
|
||||||
-- rewrite x - 0 to x using a tactic for sub
|
|
||||||
rw [sub_zero] at h_x_pos h_x_lt_M
|
|
||||||
-- multiply both sides by x we know 0 < x so it should work, using a tactic rewrite
|
|
||||||
-- mul_lt_mul_left: (a0 : 0 < a) : a * b < a * c ← b < c
|
|
||||||
rw [← mul_lt_mul_left h_x_pos]
|
|
||||||
-- rewrite x * x⁻¹ = 1
|
|
||||||
-- mul_inv_cancel: a ≠ 0 → a * a⁻¹ = 1
|
|
||||||
have h_x_neq_zero: x ≠ 0 := by exact ne_of_gt h_x_pos
|
|
||||||
rw [mul_inv_cancel h_x_neq_zero]
|
|
||||||
have h_M_inv_pos: 0 < M⁻¹ := by simp [h_M_pos]
|
|
||||||
-- multiply both sides by M⁻¹ on the right
|
|
||||||
rw [← mul_lt_mul_right h_M_inv_pos]
|
|
||||||
-- rewrite 1 * M = M
|
|
||||||
rw [one_mul]
|
|
||||||
-- rewrite M * M⁻¹ = 1
|
|
||||||
-- mul_inv_cancel: a ≠ 0 → a * a⁻¹ = 1
|
|
||||||
have h_M_neq_zero: M ≠ 0 := by exact ne_of_gt h_M_pos
|
|
||||||
-- have h_M_inv: M * M⁻¹ = 1 := by rw [mul_inv_cancel h_M_neq_zero]
|
|
||||||
rw [mul_inv_cancel_right₀ h_M_neq_zero x]
|
|
||||||
assumption
|
|
|
@ -1,39 +0,0 @@
|
||||||
/-
|
|
||||||
-/
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
-- define 1/x (reciprical) for reals
|
|
||||||
noncomputable def f (x : ℝ ): ℝ := x⁻¹
|
|
||||||
#check f
|
|
||||||
|
|
||||||
-- unit test that f 1 = 1, f 2 = 1/2
|
|
||||||
theorem test_f1 : f 1 = 1 := by simp[f]
|
|
||||||
theorem test_f2 : f 2 = 2⁻¹ := by simp[f]
|
|
||||||
#print test_f1
|
|
||||||
#print test_f2
|
|
||||||
|
|
||||||
-- set_option pp.notation false
|
|
||||||
-- The limit of f x as x approaches c+ from the right is +infinity i.e., limit is unbounded from the right
|
|
||||||
-- i.e., lim_{x -> c+} f(x) = +infinity
|
|
||||||
def has_unbounded_limit_right (f: ℝ -> ℝ) (c : ℝ) : Prop :=
|
|
||||||
∀ M : ℝ, 0 < M → ∃ δ, 0 < δ ∧ ∀ x : ℝ, 0 < x - c ∧ x - c < δ → M < f x
|
|
||||||
#print has_unbounded_limit_right
|
|
||||||
|
|
||||||
theorem reciprocal_has_unbounded_limit_right : has_unbounded_limit_right f 0 := by
|
|
||||||
unfold has_unbounded_limit_right
|
|
||||||
intro M h_0_lt_M
|
|
||||||
-- select delta that works since func is 1/x then anything less than 1/M will make f x be greater than M (so it should work)
|
|
||||||
use M⁻¹
|
|
||||||
-- TODO split (what did scott want with this, read)
|
|
||||||
constructor
|
|
||||||
. rwa [inv_pos]
|
|
||||||
. -- consider any x with 0 < x - 0 < M⁻¹ but introduce both hypothesis 0 < x - 0 and x - 0 < M⁻¹
|
|
||||||
intro x ⟨h_x_pos, h_x_lt_δ⟩
|
|
||||||
-- rintro x ⟨h_x_pos, h_x_lt_δ⟩ -- TODO tomorrow, why did scott do this?
|
|
||||||
-- rewrite both hypothesis using fact m - 0 = m
|
|
||||||
rw [sub_zero] at h_x_pos h_x_lt_δ
|
|
||||||
unfold f
|
|
||||||
-- multiply both sides of h_x_lt_δ by x⁻¹ on the left using mul_lt_mul_right
|
|
||||||
rwa [propext (lt_inv h_0_lt_M h_x_pos)]
|
|
||||||
|
|
||||||
-- state p f = 1 todo: https://proofassistants.stackexchange.com/questions/3800/given-some-proposition-in-lean-4-how-do-we-state-a-theorem-saying-that-we-want
|
|
|
@ -1,99 +0,0 @@
|
||||||
/-
|
|
||||||
theorem: lim_{x -> c+} f(x) = +infinity
|
|
||||||
|
|
||||||
x + infinit = +infinity
|
|
||||||
|
|
||||||
lim_{x -> c} f(x) = L
|
|
||||||
∀ ε > 0, ∃ δ > 0, 0 < |x - c| < δ → 0 < |f(x) - L| < ε
|
|
||||||
|
|
||||||
L = + infinity
|
|
||||||
consider some ε > 0
|
|
||||||
0 < |f(x) - L| < ε
|
|
||||||
0 < |f(x) - +infinity| < ε
|
|
||||||
|
|
||||||
--> this formalization doens't seem promising
|
|
||||||
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: lim_{x -> 0+} 1/x = +infinity
|
|
||||||
∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
-- unboudned limit := "for any M, there exists a sufficiently close x s.t. f(x) is strictly greater than M"
|
|
||||||
∀ M: ℝ, 0 < M, ∃ δ : ℝ, 0 < δ, ∀ x : ℝ, 0 < x - c < δ → M < f(x)
|
|
||||||
|
|
||||||
proof:
|
|
||||||
consider some M > 0 (intro M)
|
|
||||||
-- choose delta, M < f(x) --> M < 1/x --> 1/M > x --> x < M⁻¹
|
|
||||||
δ = M⁻¹
|
|
||||||
. show 0 < δ
|
|
||||||
fact M > 0 --> M⁻¹ > 0 (by lemma in lean, division by positive number)
|
|
||||||
0 < x - c -> rewrite
|
|
||||||
-> 0 < x
|
|
||||||
x - c < δ -> rewrite
|
|
||||||
-> x < M⁻¹
|
|
||||||
(WTS: M < x⁻¹)
|
|
||||||
x < M⁻¹
|
|
||||||
-- multiply both sides by x⁻¹ if x⁻¹ > 0 (lemma, have stmt)
|
|
||||||
-> 0 < x --> x⁻¹ > 0
|
|
||||||
x⁻¹ * x < M^⁻¹ * x⁻¹
|
|
||||||
by identity x⁻¹ * x = 1 of fields (lemma in lean or automation)
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
-- multiply both sides by M if M > 0
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
M * 1 < M * M⁻¹ * x⁻¹
|
|
||||||
-- identity
|
|
||||||
M < x⁻¹
|
|
||||||
Qed
|
|
||||||
|
|
||||||
-/
|
|
||||||
|
|
||||||
-- import real numbers form mathlib
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#print f
|
|
||||||
#check f
|
|
||||||
#check f 1
|
|
||||||
-- #eval f 1
|
|
||||||
-- theorem any_R : ℝ -> R := λ x : ℝ, x -- TODO
|
|
||||||
theorem unit_test_f_1 : f 1 = 1 := by simp [f]
|
|
||||||
theorem unit_test_f_2 : f 2 = 1/2 := by simp [f]
|
|
||||||
noncomputable def f' (x : ℝ) : ℝ := 1/x
|
|
||||||
theorem units_f_eq_f' : ∀ x : ℝ , f x = f' x := by simp [f, f']
|
|
||||||
#print units_f_eq_f'
|
|
||||||
|
|
||||||
-- lim_{x -> c+} f(x) = +infinity := ∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
def unbounded_limit (f : ℝ -> ℝ) (c : ℝ) : Prop :=
|
|
||||||
∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < x - c ∧ x - c < δ → M < f x
|
|
||||||
|
|
||||||
-- show 1/x is unbounded as x -> 0 (or 1/x has a veritcal asymptote at x = 0)
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- choose M : ℝ and is M > 0
|
|
||||||
intro M h_M_pos
|
|
||||||
-- choose delta = M⁻¹ by a tactic
|
|
||||||
use M⁻¹
|
|
||||||
-- deconstruct the constructor Left ∧ Right = And(Left, Right) to Left, Right using a tactic
|
|
||||||
apply And.intro
|
|
||||||
. exact (by simp [h_M_pos]) -- TODO try to find the lemma in mathlib to prove this
|
|
||||||
. intro x ⟨h_x_pos, h_x_lt_M⟩
|
|
||||||
-- rewrite x - 0 to x using a tactic for sub
|
|
||||||
rw [sub_zero] at h_x_pos h_x_lt_M
|
|
||||||
-- using rewrite do M < x⁻¹ → M * x < x⁻¹ * x by mulitpling both sides by x on the right
|
|
||||||
-- #print mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
rw [← mul_lt_mul_right h_x_pos]
|
|
||||||
-- using rewrite let's cancel the x's i.e. x * x⁻¹ = 1 or use the multiplicatitve identity lemma
|
|
||||||
-- apply commutativity of multiplication to the end part of the equation, to goal part 2
|
|
||||||
nth_rewrite 2 [mul_comm]
|
|
||||||
-- (h : a ≠ 0) : a * a⁻¹ = 1 let define a lemma for x ≠ 0
|
|
||||||
have h_x_neq_zero: x ≠ 0 := ne_of_gt h_x_pos
|
|
||||||
rw [mul_inv_cancel h_x_neq_zero]
|
|
||||||
-- let's (left) multiply both sides by M⁻¹ then cancel the M's then simplify M⁻¹*1 = M⁻¹ the close proof
|
|
||||||
have h_M_inv_pos: 0 < M⁻¹ := by simp [h_M_pos]
|
|
||||||
rw [← mul_lt_mul_left h_M_inv_pos]
|
|
||||||
rw [mul_one]
|
|
||||||
-- rewrite M⁻¹ * M * x = M * M⁻¹ * x via associativity of multiplication
|
|
||||||
-- (a b c : G) : a * b * c = a * (b * c)
|
|
||||||
rw [← mul_assoc]
|
|
||||||
-- cancel the M's then simplify M⁻¹*1 = M⁻¹ the close proof
|
|
||||||
have h_M_neq_zero: M ≠ 0 := ne_of_gt h_M_pos
|
|
||||||
-- mul_inv_cancel : (h : a ≠ 0) : a * a⁻¹ = 1
|
|
||||||
simp [h_M_neq_zero]
|
|
||||||
assumption
|
|
|
@ -1,103 +0,0 @@
|
||||||
/-
|
|
||||||
theorem: lim_{x -> c+} f(x) = +infinity
|
|
||||||
|
|
||||||
x + infinit = +infinity
|
|
||||||
|
|
||||||
lim_{x -> c} f(x) = L
|
|
||||||
∀ ε > 0, ∃ δ > 0, 0 < |x - c| < δ → 0 < |f(x) - L| < ε
|
|
||||||
|
|
||||||
L = + infinity
|
|
||||||
consider some ε > 0
|
|
||||||
0 < |f(x) - L| < ε
|
|
||||||
0 < |f(x) - +infinity| < ε
|
|
||||||
|
|
||||||
--> this formalization doens't seem promising
|
|
||||||
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: lim_{x -> 0+} 1/x = +infinity
|
|
||||||
∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
-- unboudned limit := "for any M, there exists a sufficiently close x s.t. f(x) is strictly greater than M"
|
|
||||||
∀ M: ℝ, 0 < M, ∃ δ : ℝ, 0 < δ, ∀ x : ℝ, 0 < x - c < δ → M < f(x)
|
|
||||||
|
|
||||||
proof:
|
|
||||||
consider some M > 0 (intro M)
|
|
||||||
-- choose delta, M < f(x) --> M < 1/x --> 1/M > x --> x < M⁻¹
|
|
||||||
δ = M⁻¹
|
|
||||||
. show 0 < δ
|
|
||||||
fact M > 0 --> M⁻¹ > 0 (by lemma in lean, division by positive number)
|
|
||||||
0 < x - c -> rewrite
|
|
||||||
-> 0 < x
|
|
||||||
x - c < δ -> rewrite
|
|
||||||
-> x < M⁻¹
|
|
||||||
(WTS: M < x⁻¹)
|
|
||||||
x < M⁻¹
|
|
||||||
-- multiply both sides by x⁻¹ if x⁻¹ > 0 (lemma, have stmt)
|
|
||||||
-> 0 < x --> x⁻¹ > 0
|
|
||||||
x⁻¹ * x < M^⁻¹ * x⁻¹
|
|
||||||
by identity x⁻¹ * x = 1 of fields (lemma in lean or automation)
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
-- multiply both sides by M if M > 0
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
M * 1 < M * M⁻¹ * x⁻¹
|
|
||||||
-- identity
|
|
||||||
M < x⁻¹
|
|
||||||
Qed
|
|
||||||
|
|
||||||
-/
|
|
||||||
|
|
||||||
-- import real numbers form mathlib
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#print f
|
|
||||||
#check f
|
|
||||||
#check f 1
|
|
||||||
-- #eval f 1
|
|
||||||
-- theorem any_R : ℝ -> R := λ x : ℝ, x -- TODO
|
|
||||||
theorem unit_test_f_1 : f 1 = 1 := by simp [f]
|
|
||||||
theorem unit_test_f_2 : f 2 = 1/2 := by simp [f]
|
|
||||||
noncomputable def f' (x : ℝ) : ℝ := 1/x
|
|
||||||
theorem units_f_eq_f' : ∀ x : ℝ , f x = f' x := by simp [f, f']
|
|
||||||
#print units_f_eq_f'
|
|
||||||
|
|
||||||
-- lim_{x -> c+} f(x) = +infinity := ∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
def unbounded_limit (f : ℝ -> ℝ) (c : ℝ) : Prop :=
|
|
||||||
∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < x - c ∧ x - c < δ → M < f x
|
|
||||||
|
|
||||||
-- show 1/x is unbounded as x -> 0 (or 1/x has a veritcal asymptote at x = 0)
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- choose M : ℝ and is M > 0
|
|
||||||
intro M h_M_pos
|
|
||||||
-- choose delta = M⁻¹ by a tactic
|
|
||||||
use M⁻¹
|
|
||||||
-- deconstruct the constructor Left ∧ Right = And(Left, Right) to Left, Right using a tactic
|
|
||||||
apply And.intro
|
|
||||||
. exact (by simp [h_M_pos]) -- TODO try to find the lemma in mathlib to prove this
|
|
||||||
. intro x ⟨h_x_pos, h_x_lt_M⟩
|
|
||||||
-- rewrite x - 0 to x using a tactic for sub
|
|
||||||
rw [sub_zero] at h_x_pos h_x_lt_M
|
|
||||||
-- using rewrite do M < x⁻¹ → M * x < x⁻¹ * x by mulitpling both sides by x on the right
|
|
||||||
-- #print mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
rw [← mul_lt_mul_right h_x_pos]
|
|
||||||
-- using rewrite let's cancel the x's i.e. x * x⁻¹ = 1 or use the multiplicatitve identity lemma
|
|
||||||
-- apply commutativity of multiplication to the end part of the equation, to goal part 2
|
|
||||||
nth_rewrite 2 [mul_comm]
|
|
||||||
-- (h : a ≠ 0) : a * a⁻¹ = 1 let define a lemma for x ≠ 0
|
|
||||||
have h_x_neq_zero: x ≠ 0 := ne_of_gt h_x_pos
|
|
||||||
rw [mul_inv_cancel h_x_neq_zero]
|
|
||||||
-- let's (left) multiply both sides by M⁻¹ then cancel the M's then simplify M⁻¹*1 = M⁻¹ the close proof
|
|
||||||
have h_M_inv_pos: 0 < M⁻¹ := by simp [h_M_pos]
|
|
||||||
rw [← mul_lt_mul_left h_M_inv_pos]
|
|
||||||
rw [mul_one]
|
|
||||||
-- rewrite M⁻¹ * M * x = M * M⁻¹ * x via associativity of multiplication
|
|
||||||
-- (a b c : G) : a * b * c = a * (b * c)
|
|
||||||
rw [← mul_assoc]
|
|
||||||
-- cancel the M's then simplify M⁻¹*1 = M⁻¹ the close proof
|
|
||||||
have h_M_neq_zero: M ≠ 0 := ne_of_gt h_M_pos
|
|
||||||
-- mul_inv_cancel : (h : a ≠ 0) : a * a⁻¹ = 1
|
|
||||||
nth_rewrite 2 [mul_comm]
|
|
||||||
-- -- use mul identity (h : a ≠ 0) : a * a⁻¹ = 1 to cancel the M's
|
|
||||||
rw [mul_inv_cancel h_M_neq_zero]
|
|
||||||
rw [mul_comm]
|
|
||||||
rw [mul_one]
|
|
||||||
assumption
|
|
|
@ -1,101 +0,0 @@
|
||||||
/-
|
|
||||||
theorem: lim_{x -> c+} f(x) = +infinity
|
|
||||||
|
|
||||||
x + infinit = +infinity
|
|
||||||
|
|
||||||
lim_{x -> c} f(x) = L
|
|
||||||
∀ ε > 0, ∃ δ > 0, 0 < |x - c| < δ → 0 < |f(x) - L| < ε
|
|
||||||
|
|
||||||
L = + infinity
|
|
||||||
consider some ε > 0
|
|
||||||
0 < |f(x) - L| < ε
|
|
||||||
0 < |f(x) - +infinity| < ε
|
|
||||||
|
|
||||||
--> this formalization doens't seem promising
|
|
||||||
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: lim_{x -> 0+} 1/x = +infinity
|
|
||||||
∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
-- unboudned limit := "for any M, there exists a sufficiently close x s.t. f(x) is strictly greater than M"
|
|
||||||
∀ M: ℝ, 0 < M, ∃ δ : ℝ, 0 < δ, ∀ x : ℝ, 0 < x - c < δ → M < f(x)
|
|
||||||
|
|
||||||
proof:
|
|
||||||
consider some M > 0 (intro M)
|
|
||||||
-- choose delta, M < f(x) --> M < 1/x --> 1/M > x --> x < M⁻¹
|
|
||||||
δ = M⁻¹
|
|
||||||
. show 0 < δ
|
|
||||||
fact M > 0 --> M⁻¹ > 0 (by lemma in lean, division by positive number)
|
|
||||||
0 < x - c -> rewrite
|
|
||||||
-> 0 < x
|
|
||||||
x - c < δ -> rewrite
|
|
||||||
-> x < M⁻¹
|
|
||||||
(WTS: M < x⁻¹)
|
|
||||||
x < M⁻¹
|
|
||||||
-- multiply both sides by x⁻¹ if x⁻¹ > 0 (lemma, have stmt)
|
|
||||||
-> 0 < x --> x⁻¹ > 0
|
|
||||||
x⁻¹ * x < M^⁻¹ * x⁻¹
|
|
||||||
by identity x⁻¹ * x = 1 of fields (lemma in lean or automation)
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
-- multiply both sides by M if M > 0
|
|
||||||
1 < M⁻¹ * x⁻¹
|
|
||||||
M * 1 < M * M⁻¹ * x⁻¹
|
|
||||||
-- identity
|
|
||||||
M < x⁻¹
|
|
||||||
Qed
|
|
||||||
|
|
||||||
-/
|
|
||||||
|
|
||||||
-- import real numbers form mathlib
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := x⁻¹
|
|
||||||
#print f
|
|
||||||
#check f
|
|
||||||
#check f 1
|
|
||||||
-- #eval f 1
|
|
||||||
-- theorem any_R : ℝ -> R := λ x : ℝ, x -- TODO
|
|
||||||
theorem unit_test_f_1 : f 1 = 1 := by simp [f]
|
|
||||||
theorem unit_test_f_2 : f 2 = 1/2 := by simp [f]
|
|
||||||
noncomputable def f' (x : ℝ) : ℝ := 1/x
|
|
||||||
theorem units_f_eq_f' : ∀ x : ℝ , f x = f' x := by simp [f, f']
|
|
||||||
#print units_f_eq_f'
|
|
||||||
|
|
||||||
-- lim_{x -> c+} f(x) = +infinity := ∀ M > 0, ∃ δ > 0, ∀ x : ℝ, 0 < x - c < δ → f(x) > M
|
|
||||||
def unbounded_limit (f : ℝ -> ℝ) (c : ℝ) : Prop :=
|
|
||||||
∀ M : ℝ, 0 < M → ∃ δ : ℝ, 0 < δ ∧ ∀ x : ℝ, 0 < x - c ∧ x - c < δ → M < f x
|
|
||||||
|
|
||||||
-- show 1/x is unbounded as x -> 0 (or 1/x has a veritcal asymptote at x = 0)
|
|
||||||
theorem limit_of_reciprocal_of_x_is_unbounded: unbounded_limit f 0 := by
|
|
||||||
unfold unbounded_limit f
|
|
||||||
-- choose M : ℝ and is M > 0
|
|
||||||
intro M h_M_pos
|
|
||||||
-- choose delta = M⁻¹ by a tactic
|
|
||||||
use M⁻¹
|
|
||||||
-- deconstruct the constructor Left ∧ Right = And(Left, Right) to Left, Right using a tactic
|
|
||||||
apply And.intro
|
|
||||||
. exact (by simp [h_M_pos]) -- TODO try to find the lemma in mathlib to prove this
|
|
||||||
. intro x ⟨h_x_pos, h_x_lt_M⟩
|
|
||||||
-- rewrite x - 0 to x using a tactic for sub
|
|
||||||
rw [sub_zero] at h_x_pos h_x_lt_M
|
|
||||||
-- using rewrite do M < x⁻¹ → M * x < x⁻¹ * x by mulitpling both sides by x on the right
|
|
||||||
-- #print mul_lt_mul_right -- (a0 : 0 < a) : b * a < c * a ↔ b < c
|
|
||||||
rw [←mul_lt_mul_right h_M_pos] at h_x_lt_M
|
|
||||||
-- #print mul_inv_cancel
|
|
||||||
-- mul_inv_cancel: a ≠ 0 → a * a⁻¹ = 1
|
|
||||||
nth_rewrite 2 [mul_comm] at h_x_lt_M
|
|
||||||
have h_M_neq_zero : M ≠ 0 := ne_of_gt h_M_pos
|
|
||||||
rw [mul_inv_cancel h_M_neq_zero] at h_x_lt_M
|
|
||||||
-- multiply both sides by x⁻¹ on the left
|
|
||||||
have h_x_inv_pos : 0 < x⁻¹ := inv_pos.mpr h_x_pos
|
|
||||||
rw [← mul_lt_mul_left h_x_inv_pos] at h_x_lt_M
|
|
||||||
-- apply associativity of mul
|
|
||||||
rw [← mul_assoc] at h_x_lt_M
|
|
||||||
-- mul_inv_cancel: a ≠ 0 → a * a⁻¹ = 1
|
|
||||||
nth_rewrite 2 [mul_comm] at h_x_lt_M
|
|
||||||
-- cancel the x * x⁻¹ to 1
|
|
||||||
have h_x_neq_zero : x ≠ 0 := ne_of_gt h_x_pos
|
|
||||||
rw [mul_inv_cancel h_x_neq_zero] at h_x_lt_M
|
|
||||||
-- apply 1 * M = M
|
|
||||||
rw [one_mul] at h_x_lt_M
|
|
||||||
rw [mul_comm] at h_x_lt_M
|
|
||||||
rw [one_mul] at h_x_lt_M
|
|
||||||
assumption
|
|
|
@ -1 +0,0 @@
|
||||||
def hello := "world"
|
|
|
@ -1,34 +0,0 @@
|
||||||
-- {
|
|
||||||
-- "problem": "Let $t(x) = \\sqrt{3x+1}$ and $f(x)=5-t(x)$. What is $t(f(5))$?",
|
|
||||||
-- "level": "Level 4",
|
|
||||||
-- "type": "Algebra",
|
|
||||||
-- "solution": "We first evaluate $f(5) = 5 -t(5) = 5-\\sqrt{5\\cdot3+1}=1$. Thus $t(f(5))=t(1)=\\sqrt{3\\cdot1 + 1}=\\boxed{2}$."
|
|
||||||
-- }
|
|
||||||
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
import Mathlib.Data.Real.Sqrt
|
|
||||||
import Mathlib.Algebra.GroupPower.Order
|
|
||||||
|
|
||||||
noncomputable def t (x : ℝ) : ℝ := Real.sqrt (3 * x + 1)
|
|
||||||
noncomputable def f (x : ℝ) : ℝ := 5 - t x
|
|
||||||
|
|
||||||
theorem solve_t_at_5: t 5 = 4 := by
|
|
||||||
have h0 : Real.sqrt 4 ^ 2 = 4 := Real.sq_sqrt (Nat.ofNat_nonneg _)
|
|
||||||
have h1 : 3 * 5 + 1 = 4^2 := by rfl
|
|
||||||
have h2 : Real.sqrt (3 * 5 + 1) = Real.sqrt 4^2:= by sorry
|
|
||||||
unfold t
|
|
||||||
rw[h2, h0]
|
|
||||||
|
|
||||||
theorem solve_f_at_5: f 5 = 1 := by
|
|
||||||
unfold f
|
|
||||||
have h: t 5 = 4 := by apply solve_t_at_5
|
|
||||||
rw[h]
|
|
||||||
ring
|
|
||||||
|
|
||||||
theorem solve_t_f_at_5: t (f 5) = 2 := by
|
|
||||||
unfold t
|
|
||||||
have h0: f 5 = 1 := by apply solve_f_at_5
|
|
||||||
have h1: 3 * 1 + 1 = 2^2 := by rfl
|
|
||||||
have h2: Real.sqrt (3 * 1 + 1) = Real.sqrt 2^2 := by sorry
|
|
||||||
have h3: Real.sqrt 2^2 = 2 := Real.sq_sqrt (Nat.ofNat_nonneg _)
|
|
||||||
rw[h0, h2, h3]
|
|
|
@ -1,26 +0,0 @@
|
||||||
-- {
|
|
||||||
-- "problem": "The perimeter of a rectangle is 24 inches. What is the number of square inches in the maximum possible area for this rectangle?",
|
|
||||||
-- "level": "Level 3",
|
|
||||||
-- "type": "Algebra",
|
|
||||||
-- "solution": "Let one pair of parallel sides have length $x$ and the other pair of parallel sides have length $12-x$. This means that the perimeter of the rectangle is $x+x+12-x+12-x=24$ as the problem states. The area of this rectangle is $12x-x^2$. Completing the square results in $-(x-6)^2+36\\le 36$ since $(x-6)^2\\ge 0$, so the maximum area of $\\boxed{36}$ is obtained when the rectangle is a square of side length 6 inches."
|
|
||||||
-- }
|
|
||||||
|
|
||||||
-- Note: translating this to 2x + 2y = 24, what is xy?
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
import Mathlib.Algebra.Group.Defs
|
|
||||||
import Mathlib.Algebra.Ring.Defs
|
|
||||||
import Mathlib.Tactic.Linarith.Frontend
|
|
||||||
|
|
||||||
def valid_perimeter (x y : ℕ) : Prop :=
|
|
||||||
2 * x + 2 * y = 24
|
|
||||||
|
|
||||||
def area (x y : ℝ) := x * y
|
|
||||||
|
|
||||||
theorem rewrite_y_as_x: valid_perimeter x y → y = 12 - x := by
|
|
||||||
unfold valid_perimeter
|
|
||||||
intro p
|
|
||||||
have h0 : 24 = 2 * 12 := by rfl
|
|
||||||
have h1 : 2 * x + 2 * y = 2 * (x + y) := by ring
|
|
||||||
have h2 : 2 * (x + y) = 2 * 12 → x + y = 12 := by sorry
|
|
||||||
have h3 : x + y = 12 → y = 12 - x := by sorry
|
|
||||||
rw[h0, h1, h2] at p
|
|
|
@ -1,19 +0,0 @@
|
||||||
-- {
|
|
||||||
-- "problem": "If $\\sqrt{2\\sqrt{t-2}} = \\sqrt[4]{7 - t}$, then find $t$.",
|
|
||||||
-- "level": "Level 4",
|
|
||||||
-- "type": "Algebra",
|
|
||||||
-- "solution": "We raise both sides to the fourth power, which is equivalent to squaring twice, in order to get rid of the radicals. The left-hand side becomes $$\\left(\\sqrt{2\\sqrt{t-2}}\\right)^4 = \\left(2\\sqrt{t-2}\\right)^2 = 4 \\cdot (t-2) = 4t-8.$$The right-hand side becomes $\\left(\\sqrt[4]{7-t}\\right)^4 = 7-t$. Setting them equal, $$4t-8 = 7-t \\quad\\Longrightarrow\\quad 5t = 15,$$and $t = \\boxed{3}$. Checking, we find that this value does indeed satisfy the original equation."
|
|
||||||
-- }
|
|
||||||
|
|
||||||
import Mathlib.Data.Real.Basic
|
|
||||||
import Mathlib.Data.Nat.Pow
|
|
||||||
|
|
||||||
noncomputable def a (t : ℝ) : ℝ := (2 * (t - 2) ^ (1 / 2)) ^ (1/2)
|
|
||||||
noncomputable def b (t : ℝ) : ℝ := (7 - t)^(1/4)
|
|
||||||
|
|
||||||
def valid_t (t : ℝ) : Prop :=
|
|
||||||
a t = b t
|
|
||||||
|
|
||||||
theorem LHS_to_4 : ∀ t : ℝ, (a t) ^ 4 = 4 * t - 8 := by sorry
|
|
||||||
theorem RHS_to_4 : ∀ t : ℝ, (b t) ^ 4 = 7 - t := by sorry
|
|
||||||
theorem solution : valid_t 3 := by sorry
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue