Compare commits
230 Commits
a3a244159b
...
f20ee8dc87
Author | SHA1 | Date |
---|---|---|
Leni Aniva | f20ee8dc87 | |
Leni Aniva | a41b95e540 | |
Leni Aniva | a11df9f2e9 | |
Leni Aniva | ed220bc7fb | |
Leni Aniva | 7eb5419f36 | |
Leni Aniva | 6d85c19589 | |
Leni Aniva | 6b44d9ef14 | |
Leni Aniva | 535770bbd7 | |
Leni Aniva | 823c9635c7 | |
Leni Aniva | 55b44c3fa1 | |
Leni Aniva | 30c1fd894f | |
Leni Aniva | f02f9592d7 | |
Leni Aniva | 0e63583a1d | |
Leni Aniva | d4e49310f0 | |
Leni Aniva | 2f48cfbc19 | |
Leni Aniva | 22bb818a1c | |
Leni Aniva | 63e64a1e9f | |
Leni Aniva | 7af24a4f0a | |
Leni Aniva | 09189ce600 | |
Leni Aniva | 19d2f5ff3f | |
Leni Aniva | d50720f622 | |
Leni Aniva | d9ed051b4d | |
Leni Aniva | 38cb91652f | |
Leni Aniva | 013cb8bb57 | |
Leni Aniva | 7fe73551c3 | |
Leni Aniva | 5a60ca74d5 | |
Leni Aniva | 41cb3f68cd | |
Leni Aniva | 058f5a98b2 | |
Leni Aniva | 1b7b6a644b | |
Leni Aniva | 042dc8f530 | |
Leni Aniva | 92351c9a3d | |
Leni Aniva | 8a447e67cd | |
Leni Aniva | ed196494c1 | |
Leni Aniva | 8b43dc0f25 | |
Leni Aniva | 744c9ee286 | |
Leni Aniva | 9c8cc33e07 | |
Leni Aniva | 216bb9e920 | |
Leni Aniva | f462843218 | |
Leni Aniva | a1ed8f4b3d | |
Leni Aniva | 7988a25ce8 | |
Leni Aniva | 2802cc204f | |
Leni Aniva | e9c9548f17 | |
Leni Aniva | 2b71203c1e | |
Leni Aniva | f939388dbf | |
Leni Aniva | 2c48ff9e42 | |
Leni Aniva | 621b10c354 | |
Leni Aniva | 73e4c1d81c | |
Leni Aniva | 7f6b57bc08 | |
Leni Aniva | 140055b16b | |
Leni Aniva | 1bea2ca4e1 | |
Leni Aniva | e09a6c7c9d | |
Leni Aniva | 10e6877f0e | |
Leni Aniva | 252f85e66c | |
Leni Aniva | 14011945a0 | |
Leni Aniva | 2ea8b1c73c | |
Leni Aniva | 431e81ca2f | |
Leni Aniva | cfd74aba91 | |
Leni Aniva | 4a1114ab00 | |
Leni Aniva | 4a89aaf70e | |
Leni Aniva | 46faa5c089 | |
Leni Aniva | e79e386b39 | |
Leni Aniva | 8fa1a7d383 | |
Leni Aniva | 9e68a9cae4 | |
Leni Aniva | a698a4250f | |
Leni Aniva | 47fabf333b | |
Leni Aniva | 62d20be841 | |
Leni Aniva | 516ab15961 | |
Leni Aniva | f016d60d07 | |
Leni Aniva | aae19ec942 | |
Leni Aniva | 81aabc52ea | |
Leni Aniva | e6dbf88ce2 | |
Leni Aniva | 3debcc021a | |
Leni Aniva | 4eec930dd4 | |
Leni Aniva | ef864ea16d | |
Leni Aniva | 0b91c41ad2 | |
Leni Aniva | a5b0721482 | |
Leni Aniva | 996f16bbb8 | |
Leni Aniva | f18a9dd1d5 | |
Leni Aniva | eeb149a32c | |
Leni Aniva | 2ad7ad8778 | |
Leni Aniva | 7bc0f82654 | |
Leni Aniva | 267d635c05 | |
Leni Aniva | 93b7d8b67d | |
Leni Aniva | 7e28ded23f | |
Leni Aniva | cb0712ccf6 | |
Leni Aniva | 111781816f | |
Leni Aniva | 18c318cb73 | |
Leni Aniva | 0254759581 | |
Leni Aniva | df4e044e5f | |
Leni Aniva | 5f5d06f1d8 | |
Leni Aniva | 4acd367ca7 | |
Leni Aniva | fe5c1eda7d | |
Leni Aniva | 40d61fecc5 | |
Leni Aniva | 25f3a2f19d | |
Leni Aniva | d5ef05a7b0 | |
Leni Aniva | 34d9b02797 | |
Leni Aniva | 30eda7ef8c | |
Leni Aniva | 6a81d83c1f | |
Leni Aniva | 50ac2fea4b | |
Leni Aniva | 6fb1b2e787 | |
Leni Aniva | 6692303da6 | |
Leni Aniva | a8bfa56587 | |
Leni Aniva | 1c370ef2ae | |
Leni Aniva | dc90b6b73e | |
Leni Aniva | da194a1165 | |
Leni Aniva | aef93cf506 | |
Leni Aniva | a540dd4540 | |
Leni Aniva | 85eb42207c | |
Leni Aniva | 69be7c3920 | |
Leni Aniva | 83ff58dffc | |
Leni Aniva | 3c96a7c0ea | |
Leni Aniva | ff4671cdd0 | |
Leni Aniva | 085b12c255 | |
Leni Aniva | ac9f6f810c | |
Leni Aniva | bd0c66facc | |
Leni Aniva | 2fe4fa9bc4 | |
Leni Aniva | d7fcc502f9 | |
Leni Aniva | 94c4b2cfe3 | |
Leni Aniva | 924a67f46d | |
Leni Aniva | 8a8db545a5 | |
Leni Aniva | f2b54ec018 | |
Leni Aniva | 3c2d93259f | |
Leni Aniva | 079f12d6d3 | |
Leni Aniva | d846210b9e | |
Leni Aniva | dbfee00420 | |
Leni Aniva | cdb1e8576f | |
Leni Aniva | c80d7567b6 | |
Leni Aniva | f72a82a4c9 | |
Leni Aniva | 35f411041e | |
Leni Aniva | 860d2e239a | |
Leni Aniva | e0cfdfaf16 | |
Leni Aniva | fe850ded98 | |
Leni Aniva | aaebb6b121 | |
Leni Aniva | a1d991f5db | |
Leni Aniva | cc9e659c06 | |
Leni Aniva | a491316541 | |
Leni Aniva | e654613182 | |
Leni Aniva | d35803791e | |
Leni Aniva | d9745094fa | |
Leni Aniva | 53b63bf46c | |
Leni Aniva | 4396da3e65 | |
Leni Aniva | ce585f7288 | |
Leni Aniva | 32fedede6a | |
Leni Aniva | 8182da436d | |
Leni Aniva | c6bb4be597 | |
Leni Aniva | ce1cb13e54 | |
Leni Aniva | a5b5e01858 | |
Leni Aniva | 4be9dbc84a | |
Leni Aniva | 97d658cfc5 | |
Leni Aniva | 333355a85d | |
Leni Aniva | 4a4a33cea7 | |
Leni Aniva | 1638c308a9 | |
Leni Aniva | 59ac83f0b7 | |
Leni Aniva | d1c0dc376f | |
Leni Aniva | 6cf328a84f | |
Leni Aniva | 1a99a2e7b2 | |
Leni Aniva | 60854525b9 | |
Leni Aniva | e523e8bcc6 | |
Leni Aniva | de250eafd0 | |
Leni Aniva | c0dfa04b18 | |
Leni Aniva | 4ce932eb3b | |
Leni Aniva | 045181356c | |
Leni Aniva | 41e1f64d44 | |
Leni Aniva | 3b1746490d | |
Leni Aniva | f064bb21a4 | |
Leni Aniva | 269e5c707c | |
Leni Aniva | c852db2f46 | |
Leni Aniva | 8029298db7 | |
Leni Aniva | d991533170 | |
Leni Aniva | a9294e0338 | |
Leni Aniva | 3d7d5d6b4d | |
Leni Aniva | 538ba6e7d7 | |
Leni Aniva | 41db295ff5 | |
Leni Aniva | 7a5fe554ba | |
Leni Aniva | 13f3460e9a | |
Leni Aniva | a8cf94ccb1 | |
Leni Aniva | 0948e71d60 | |
Leni Aniva | 6d15d1e670 | |
Leni Aniva | 35b391881e | |
Leni Aniva | d7077ce854 | |
Leni Aniva | 75f43786fb | |
Leni Aniva | f538f580bd | |
Leni Aniva | f1f1c20ff9 | |
Leni Aniva | 6b96f7893f | |
Leni Aniva | b98304f78a | |
Leni Aniva | a6e337a89e | |
Leni Aniva | a86af1bc57 | |
Leni Aniva | b74119e378 | |
Leni Aniva | 9c4c43a9f1 | |
Leni Aniva | bd4fbcc369 | |
Leni Aniva | ff8fed8741 | |
Leni Aniva | 98edaa3297 | |
Leni Aniva | 1f27532769 | |
Leni Aniva | 0c330c8778 | |
Leni Aniva | 59c046efc6 | |
Leni Aniva | a8cbb3be4f | |
Leni Aniva | 96cbbf2551 | |
Leni Aniva | b2ba26528d | |
Leni Aniva | 7771408de1 | |
Leni Aniva | 9eadd1d4d4 | |
Leni Aniva | 5cedb9d88c | |
Leni Aniva | 622aa7f969 | |
Leni Aniva | 4613777607 | |
Leni Aniva | 3e05722d1e | |
Leni Aniva | 068a188fea | |
Leni Aniva | e0c5f76451 | |
Leni Aniva | da1cdf3d16 | |
Leni Aniva | 1ed1aff7c9 | |
Leni Aniva | 95ed7d115c | |
Leni Aniva | 1fed222f56 | |
Leni Aniva | 94bc3355a2 | |
Leni Aniva | ba779766c0 | |
Leni Aniva | 1ad45f650f | |
Leni Aniva | 0f8df08dd5 | |
Leni Aniva | 116c7ff4c6 | |
Leni Aniva | 6a71dad389 | |
Leni Aniva | 1bf929b1e4 | |
Leni Aniva | 46ccad1669 | |
Leni Aniva | 76d76630ee | |
Leni Aniva | 083ec8beec | |
Leni Aniva | 15aab3d31f | |
Leni Aniva | 2a4d348aab | |
Leni Aniva | 8127e9ba06 | |
Leni Aniva | e246fd961f | |
Leni Aniva | 4d636ec12b | |
Leni Aniva | d3af535652 | |
Leni Aniva | 7c96479bb5 | |
Leni Aniva | b5cb464694 | |
Leni Aniva | f6a520c7a0 | |
Leni Aniva | 1a611c1415 |
|
@ -1,2 +1,6 @@
|
||||||
|
.*
|
||||||
|
!.gitignore
|
||||||
|
|
||||||
|
*.olean
|
||||||
/build
|
/build
|
||||||
/lake-packages
|
/lake-packages
|
||||||
|
|
66
Main.lean
66
Main.lean
|
@ -1,4 +1,66 @@
|
||||||
|
import Lean.Data.Json
|
||||||
|
import Lean.Environment
|
||||||
|
|
||||||
|
import Pantograph.Version
|
||||||
|
import Pantograph.Library
|
||||||
import Pantograph
|
import Pantograph
|
||||||
|
|
||||||
def main : IO Unit :=
|
-- Main IO functions
|
||||||
IO.println s!"Hello, {hello}!"
|
open Pantograph
|
||||||
|
|
||||||
|
/-- Parse a command either in `{ "cmd": ..., "payload": ... }` form or `cmd { ... }` form. -/
|
||||||
|
def parseCommand (s: String): Except String Protocol.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 }: Protocol.InteractionError)
|
||||||
|
-- Using `Lean.Json.compress` here to prevent newline
|
||||||
|
IO.println error.compress
|
||||||
|
| .ok command =>
|
||||||
|
let ret ← execute command
|
||||||
|
let str := match state.options.printJsonPretty with
|
||||||
|
| true => ret.pretty
|
||||||
|
| false => ret.compress
|
||||||
|
IO.println str
|
||||||
|
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
|
||||||
|
println! s!"{version}"
|
||||||
|
return
|
||||||
|
|
||||||
|
initSearch ""
|
||||||
|
|
||||||
|
let coreContext ← args.filterMap (λ s => if s.startsWith "--" then .some <| s.drop 2 else .none)
|
||||||
|
|>.toArray |> createCoreContext
|
||||||
|
let imports:= args.filter (λ s => ¬ (s.startsWith "--"))
|
||||||
|
let coreState ← createCoreState imports.toArray
|
||||||
|
let context: Context := {
|
||||||
|
imports
|
||||||
|
}
|
||||||
|
try
|
||||||
|
let coreM := loop.run context |>.run' {}
|
||||||
|
IO.println "ready."
|
||||||
|
discard <| coreM.toIO coreContext coreState
|
||||||
|
catch ex =>
|
||||||
|
IO.println "Uncaught IO exception"
|
||||||
|
IO.println ex.toString
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
LIB := ./.lake/build/lib/Pantograph.olean
|
||||||
|
EXE := ./.lake/build/bin/pantograph
|
||||||
|
SOURCE := $(wildcard Pantograph/*.lean) $(wildcard *.lean) lean-toolchain
|
||||||
|
|
||||||
|
TEST_EXE := ./.lake/build/bin/test
|
||||||
|
TEST_SOURCE := $(wildcard Test/*.lean)
|
||||||
|
|
||||||
|
$(LIB) $(EXE): $(SOURCE)
|
||||||
|
lake build pantograph
|
||||||
|
|
||||||
|
$(TEST_EXE): $(LIB) $(TEST_SOURCE)
|
||||||
|
lake build test
|
||||||
|
|
||||||
|
test: $(TEST_EXE)
|
||||||
|
$(TEST_EXE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
lake clean
|
||||||
|
|
||||||
|
.PHONY: test clean
|
193
Pantograph.lean
193
Pantograph.lean
|
@ -1 +1,192 @@
|
||||||
def hello := "world"
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Pantograph.Environment
|
||||||
|
import Pantograph.Library
|
||||||
|
import Lean.Data.HashMap
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
structure Context where
|
||||||
|
imports: List String
|
||||||
|
|
||||||
|
/-- Stores state of the REPL -/
|
||||||
|
structure State where
|
||||||
|
options: Protocol.Options := {}
|
||||||
|
nextId: Nat := 0
|
||||||
|
goalStates: Lean.HashMap Nat GoalState := Lean.HashMap.empty
|
||||||
|
|
||||||
|
/-- Main state monad for executing commands -/
|
||||||
|
abbrev MainM := ReaderT Context (StateT State Lean.CoreM)
|
||||||
|
-- HACK: For some reason writing `CommandM α := MainM (Except ... α)` disables
|
||||||
|
-- certain monadic features in `MainM`
|
||||||
|
abbrev CR α := Except Protocol.InteractionError α
|
||||||
|
|
||||||
|
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}"
|
||||||
|
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
|
||||||
|
| "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
|
||||||
|
| cmd =>
|
||||||
|
let error: Protocol.InteractionError :=
|
||||||
|
errorCommand s!"Unknown command {cmd}"
|
||||||
|
return Lean.toJson error
|
||||||
|
where
|
||||||
|
errorCommand := errorI "command"
|
||||||
|
errorIndex := errorI "index"
|
||||||
|
-- Command Functions
|
||||||
|
reset (_: Protocol.Reset): MainM (CR Protocol.StatResult) := do
|
||||||
|
let state ← get
|
||||||
|
let nGoals := state.goalStates.size
|
||||||
|
set { state with nextId := 0, goalStates := Lean.HashMap.empty }
|
||||||
|
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
|
||||||
|
expr_echo (args: Protocol.ExprEcho): MainM (CR Protocol.ExprEchoResult) := do
|
||||||
|
let state ← get
|
||||||
|
exprEcho args.expr args.type? 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,
|
||||||
|
noRepeat := args.noRepeat?.getD options.noRepeat,
|
||||||
|
printAuxDecls := args.printAuxDecls?.getD options.printAuxDecls,
|
||||||
|
printImplementationDetailHyps := args.printImplementationDetailHyps?.getD options.printImplementationDetailHyps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .ok { }
|
||||||
|
options_print (_: Protocol.OptionsPrint): MainM (CR Protocol.OptionsPrintResult) := do
|
||||||
|
return .ok (← get).options
|
||||||
|
goal_start (args: Protocol.GoalStart): MainM (CR Protocol.GoalStartResult) := do
|
||||||
|
let state ← get
|
||||||
|
let env ← Lean.MonadEnv.getEnv
|
||||||
|
let expr?: Except _ GoalState ← runTermElabM (match args.expr, args.copyFrom with
|
||||||
|
| .some expr, .none => goalStartExpr expr
|
||||||
|
| .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 := state.nextId
|
||||||
|
set { state with
|
||||||
|
goalStates := state.goalStates.insert stateId goalState,
|
||||||
|
nextId := state.nextId + 1
|
||||||
|
}
|
||||||
|
return .ok { stateId, root := goalState.root.name.toString }
|
||||||
|
goal_tactic (args: Protocol.GoalTactic): MainM (CR Protocol.GoalTacticResult) := do
|
||||||
|
let state ← get
|
||||||
|
match state.goalStates.find? args.stateId with
|
||||||
|
| .none => return .error $ errorIndex s!"Invalid state index {args.stateId}"
|
||||||
|
| .some goalState => do
|
||||||
|
let nextGoalState?: Except _ GoalState ←
|
||||||
|
match args.tactic?, args.expr?, args.have?, args.calc?, args.conv? with
|
||||||
|
| .some tactic, .none, .none, .none, .none => do
|
||||||
|
pure ( Except.ok (← goalTactic goalState args.goalId tactic))
|
||||||
|
| .none, .some expr, .none, .none, .none => do
|
||||||
|
pure ( Except.ok (← goalAssign goalState args.goalId expr))
|
||||||
|
| .none, .none, .some type, .none, .none => do
|
||||||
|
let binderName := args.binderName?.getD ""
|
||||||
|
pure ( Except.ok (← goalHave goalState args.goalId binderName type))
|
||||||
|
| .none, .none, .none, .some pred, .none => do
|
||||||
|
pure ( Except.ok (← goalCalc goalState args.goalId pred))
|
||||||
|
| .none, .none, .none, .none, .some true => do
|
||||||
|
pure ( Except.ok (← goalConv goalState args.goalId))
|
||||||
|
| .none, .none, .none, .none, .some false => do
|
||||||
|
pure ( Except.ok (← goalConvExit goalState))
|
||||||
|
| _, _, _, _, _ => pure (Except.error <| errorI "arguments" "Exactly one of {tactic, expr, have} must be supplied")
|
||||||
|
match nextGoalState? with
|
||||||
|
| .error error => return .error error
|
||||||
|
| .ok (.success nextGoalState) =>
|
||||||
|
let nextStateId := state.nextId
|
||||||
|
set { state with
|
||||||
|
goalStates := state.goalStates.insert state.nextId nextGoalState,
|
||||||
|
nextId := state.nextId + 1,
|
||||||
|
}
|
||||||
|
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 (.indexError goalId) =>
|
||||||
|
return .error $ errorIndex s!"Invalid goal id index {goalId}"
|
||||||
|
| .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
|
||||||
|
match state.goalStates.find? args.target with
|
||||||
|
| .none => return .error $ errorIndex s!"Invalid state index {args.target}"
|
||||||
|
| .some target => do
|
||||||
|
let nextState? ← match args.branch?, args.goals? with
|
||||||
|
| .some branchId, .none => do
|
||||||
|
match state.goalStates.find? branchId with
|
||||||
|
| .none => return .error $ errorIndex s!"Invalid state index {branchId}"
|
||||||
|
| .some branch => pure $ goalContinue target 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 := state.nextId
|
||||||
|
set { state with
|
||||||
|
goalStates := state.goalStates.insert nextStateId nextGoalState,
|
||||||
|
nextId := state.nextId + 1
|
||||||
|
}
|
||||||
|
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
|
||||||
|
match state.goalStates.find? args.stateId with
|
||||||
|
| .none => return .error $ errorIndex s!"Invalid state index {args.stateId}"
|
||||||
|
| .some goalState => runMetaM <| do
|
||||||
|
return .ok (← goalPrint goalState state.options)
|
||||||
|
|
||||||
|
end Pantograph
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
namespace Pantograph.Environment
|
||||||
|
|
||||||
|
def isNameInternal (n: Lean.Name): Bool :=
|
||||||
|
-- Returns true if the name is an implementation detail which should not be shown to the user.
|
||||||
|
isLeanSymbol n ∨ (Lean.privateToUserName? n |>.map isLeanSymbol |>.getD false) ∨ n.isAuxLemma ∨ n.hasMacroScopes
|
||||||
|
where
|
||||||
|
isLeanSymbol (name: Lean.Name): Bool := match name.getRoot with
|
||||||
|
| .str _ name => name == "Lean"
|
||||||
|
| _ => true
|
||||||
|
|
||||||
|
def toCompactSymbolName (n: Lean.Name) (info: Lean.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) |>.map toString
|
||||||
|
let value? := match args.value?, info with
|
||||||
|
| .some true, _ => info.value?
|
||||||
|
| .some false, _ => .none
|
||||||
|
| .none, .defnInfo _ => info.value?
|
||||||
|
| .none, _ => .none
|
||||||
|
-- Information common to all symbols
|
||||||
|
let core := {
|
||||||
|
type := ← (serialize_expression options info.type).run',
|
||||||
|
isUnsafe := info.isUnsafe,
|
||||||
|
value? := ← value?.mapM (λ v => serialize_expression 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 <| info.type.getUsedConstants.map (λ n => name_to_ast n)
|
||||||
|
else .none,
|
||||||
|
valueDependency? := ← if args.dependency?.getD false
|
||||||
|
then info.value?.mapM (λ e => do
|
||||||
|
let e ← unfoldAuxLemmas e
|
||||||
|
pure $ e.getUsedConstants.filter (!isNameInternal ·) |>.map (λ n => name_to_ast n) )
|
||||||
|
else pure (.none),
|
||||||
|
module? := module?
|
||||||
|
}
|
||||||
|
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 := ← (serialize_expression options rule.rhs).run',
|
||||||
|
})
|
||||||
|
k := r.k,
|
||||||
|
} }
|
||||||
|
| _ => pure core
|
||||||
|
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 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,463 @@
|
||||||
|
/-
|
||||||
|
Functions for handling metavariables
|
||||||
|
|
||||||
|
All the functions starting with `try` resume their inner monadic state.
|
||||||
|
-/
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
def Lean.MessageLog.getErrorMessages (log : MessageLog) : MessageLog :=
|
||||||
|
{
|
||||||
|
msgs := log.msgs.filter fun m => match m.severity with | MessageSeverity.error => true | _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
def filename: String := "<pantograph>"
|
||||||
|
|
||||||
|
/--
|
||||||
|
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
|
||||||
|
-- New metavariables acquired in this state
|
||||||
|
newMVars: SSet MVarId
|
||||||
|
|
||||||
|
-- Parent state metavariable source
|
||||||
|
parentMVar?: Option MVarId
|
||||||
|
|
||||||
|
-- Existence of this field shows that we are currently in `conv` mode.
|
||||||
|
convMVar?: Option (MVarId × 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 Expr := .none
|
||||||
|
|
||||||
|
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 goal ← Meta.mkFreshExprMVar expr (kind := MetavarKind.synthetic) (userName := .anonymous)
|
||||||
|
let savedStateMonad: Elab.Tactic.TacticM Elab.Tactic.SavedState := MonadBacktrack.saveState
|
||||||
|
let root := goal.mvarId!
|
||||||
|
let savedState ← savedStateMonad { elaborator := .anonymous } |>.run' { goals := [root]}
|
||||||
|
return {
|
||||||
|
savedState,
|
||||||
|
root,
|
||||||
|
newMVars := SSet.insert .empty root,
|
||||||
|
parentMVar? := .none,
|
||||||
|
}
|
||||||
|
protected def GoalState.isConv (state: GoalState): Bool :=
|
||||||
|
state.convMVar?.isSome
|
||||||
|
protected def GoalState.goals (state: GoalState): List MVarId :=
|
||||||
|
state.savedState.tactic.goals
|
||||||
|
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
|
||||||
|
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
|
||||||
|
private 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]
|
||||||
|
|
||||||
|
private def newMVarSet (mctxOld: @&MetavarContext) (mctxNew: @&MetavarContext): SSet MVarId :=
|
||||||
|
mctxNew.decls.foldl (fun acc mvarId mvarDecl =>
|
||||||
|
if let .some prevMVarDecl := mctxOld.decls.find? mvarId then
|
||||||
|
assert! prevMVarDecl.type == mvarDecl.type
|
||||||
|
acc
|
||||||
|
else
|
||||||
|
acc.insert mvarId
|
||||||
|
) SSet.empty
|
||||||
|
|
||||||
|
/-- Inner function for executing tactic on goal state -/
|
||||||
|
def executeTactic (state: Elab.Tactic.SavedState) (goal: MVarId) (tactic: Syntax) :
|
||||||
|
Elab.TermElabM (Except (Array String) Elab.Tactic.SavedState):= do
|
||||||
|
let tacticM (stx: Syntax): Elab.Tactic.TacticM (Except (Array String) Elab.Tactic.SavedState) := do
|
||||||
|
state.restore
|
||||||
|
Elab.Tactic.setGoals [goal]
|
||||||
|
try
|
||||||
|
Elab.Tactic.evalTactic stx
|
||||||
|
if (← getThe Core.State).messages.hasErrors then
|
||||||
|
let messages := (← getThe Core.State).messages.getErrorMessages |>.toList.toArray
|
||||||
|
let errors ← (messages.map Message.data).mapM fun md => md.toString
|
||||||
|
return .error errors
|
||||||
|
else
|
||||||
|
return .ok (← MonadBacktrack.saveState)
|
||||||
|
catch exception =>
|
||||||
|
return .error #[← exception.toMessageData.toString]
|
||||||
|
tacticM tactic { elaborator := .anonymous } |>.run' state.tactic
|
||||||
|
|
||||||
|
/-- 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 goal index is out of bounds
|
||||||
|
| indexError (goalId: Nat)
|
||||||
|
-- The given action cannot be executed in the state
|
||||||
|
| invalidAction (message: String)
|
||||||
|
|
||||||
|
/-- Execute tactic on given state -/
|
||||||
|
protected def GoalState.tryTactic (state: GoalState) (goalId: Nat) (tactic: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
||||||
|
| .some goal => pure $ goal
|
||||||
|
| .none => return .indexError goalId
|
||||||
|
goal.checkNotAssigned `GoalState.tryTactic
|
||||||
|
let tactic ← match Parser.runParserCategory
|
||||||
|
(env := ← MonadEnv.getEnv)
|
||||||
|
(catName := if state.isConv then `conv else `tactic)
|
||||||
|
(input := tactic)
|
||||||
|
(fileName := filename) with
|
||||||
|
| .ok stx => pure $ stx
|
||||||
|
| .error error => return .parseError error
|
||||||
|
match (← executeTactic (state := state.savedState) (goal := goal) (tactic := tactic)) with
|
||||||
|
| .error errors =>
|
||||||
|
return .failure errors
|
||||||
|
| .ok nextSavedState =>
|
||||||
|
-- Assert that the definition of metavariables are the same
|
||||||
|
let nextMCtx := nextSavedState.term.meta.meta.mctx
|
||||||
|
let prevMCtx := state.mctx
|
||||||
|
-- Generate a list of mvarIds that exist in the parent state; Also test the
|
||||||
|
-- assertion that the types have not changed on any mvars.
|
||||||
|
return .success {
|
||||||
|
state with
|
||||||
|
savedState := nextSavedState
|
||||||
|
newMVars := newMVarSet prevMCtx nextMCtx,
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
calcPrevRhs? := .none,
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Assumes elabM has already been restored. Assumes expr has already typechecked -/
|
||||||
|
protected def GoalState.assign (state: GoalState) (goal: MVarId) (expr: Expr):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
let goalType ← goal.getType
|
||||||
|
try
|
||||||
|
-- For some reason this is needed. One of the unit tests will fail if this isn't here
|
||||||
|
let error?: Option String ← goal.withContext (do
|
||||||
|
let exprType ← Meta.inferType expr
|
||||||
|
if ← Meta.isDefEq goalType exprType then
|
||||||
|
pure .none
|
||||||
|
else do
|
||||||
|
return .some s!"{← Meta.ppExpr expr} : {← Meta.ppExpr exprType} != {← Meta.ppExpr goalType}"
|
||||||
|
)
|
||||||
|
if let .some error := error? then
|
||||||
|
return .parseError error
|
||||||
|
goal.checkNotAssigned `GoalState.assign
|
||||||
|
goal.assign expr
|
||||||
|
if (← getThe Core.State).messages.hasErrors then
|
||||||
|
let messages := (← getThe Core.State).messages.getErrorMessages |>.toList.toArray
|
||||||
|
let errors ← (messages.map Message.data).mapM fun md => md.toString
|
||||||
|
return .failure errors
|
||||||
|
else
|
||||||
|
let prevMCtx := state.savedState.term.meta.meta.mctx
|
||||||
|
let nextMCtx ← getMCtx
|
||||||
|
-- Generate a list of mvarIds that exist in the parent state; Also test the
|
||||||
|
-- assertion that the types have not changed on any mvars.
|
||||||
|
let newMVars := newMVarSet prevMCtx nextMCtx
|
||||||
|
let nextGoals ← newMVars.toList.filterM (λ mvar => do pure !(← mvar.isAssigned))
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := {
|
||||||
|
term := ← MonadBacktrack.saveState,
|
||||||
|
tactic := { goals := nextGoals }
|
||||||
|
},
|
||||||
|
newMVars,
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
protected def GoalState.tryAssign (state: GoalState) (goalId: Nat) (expr: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
||||||
|
| .some goal => pure goal
|
||||||
|
| .none => return .indexError goalId
|
||||||
|
let expr ← match Parser.runParserCategory
|
||||||
|
(env := state.env)
|
||||||
|
(catName := `term)
|
||||||
|
(input := expr)
|
||||||
|
(fileName := filename) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
let goalType ← goal.getType
|
||||||
|
try
|
||||||
|
let expr ← goal.withContext $
|
||||||
|
Elab.Term.elabTermAndSynthesize (stx := expr) (expectedType? := .some goalType)
|
||||||
|
state.assign goal expr
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
-- Specialized Tactics
|
||||||
|
|
||||||
|
protected def GoalState.tryHave (state: GoalState) (goalId: Nat) (binderName: String) (type: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
||||||
|
| .some goal => pure goal
|
||||||
|
| .none => return .indexError goalId
|
||||||
|
let type ← match Parser.runParserCategory
|
||||||
|
(env := state.env)
|
||||||
|
(catName := `term)
|
||||||
|
(input := type)
|
||||||
|
(fileName := filename) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
let binderName := binderName.toName
|
||||||
|
try
|
||||||
|
-- Implemented similarly to the intro tactic
|
||||||
|
let nextGoals: List MVarId ← goal.withContext $ (do
|
||||||
|
let type ← Elab.Term.elabType (stx := type)
|
||||||
|
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 fvar := mkFVar fvarId
|
||||||
|
let mvarUpstream ←
|
||||||
|
withTheReader Meta.Context (fun ctx => { ctx with lctx := lctxUpstream }) do
|
||||||
|
Meta.withNewLocalInstances #[fvar] 0 (do
|
||||||
|
let mvarUpstream ← Meta.mkFreshExprMVarAt (← getLCtx) (← Meta.getLocalInstances)
|
||||||
|
(← goal.getType) (kind := MetavarKind.synthetic) (userName := .anonymous)
|
||||||
|
let expr: Expr := .app (.lam binderName type mvarBranch .default) mvarUpstream
|
||||||
|
goal.assign expr
|
||||||
|
pure mvarUpstream)
|
||||||
|
|
||||||
|
pure [mvarBranch.mvarId!, mvarUpstream.mvarId!]
|
||||||
|
)
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := {
|
||||||
|
term := ← MonadBacktrack.saveState,
|
||||||
|
tactic := { goals := nextGoals }
|
||||||
|
},
|
||||||
|
newMVars := nextGoals.toSSet,
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
/-- Enter conv tactic mode -/
|
||||||
|
protected def GoalState.conv (state: GoalState) (goalId: Nat):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
if state.convMVar?.isSome then
|
||||||
|
return .invalidAction "Already in conv state"
|
||||||
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
||||||
|
| .some goal => pure goal
|
||||||
|
| .none => return .indexError goalId
|
||||||
|
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.setGoals [newGoal.mvarId!]
|
||||||
|
pure rhs.mvarId!
|
||||||
|
return (← MonadBacktrack.saveState, convMVar)
|
||||||
|
try
|
||||||
|
let (nextSavedState, convRhs) ← tacticM { elaborator := .anonymous } |>.run' state.savedState.tactic
|
||||||
|
let prevMCtx := state.mctx
|
||||||
|
let nextMCtx := nextSavedState.term.meta.meta.mctx
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := nextSavedState
|
||||||
|
newMVars := newMVarSet prevMCtx nextMCtx,
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
convMVar? := .some (convRhs, goal),
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
/-- Exit from `conv` mode. Resumes all goals before the mode starts and applys the conv -/
|
||||||
|
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
|
||||||
|
let nextMCtx := nextSavedState.term.meta.meta.mctx
|
||||||
|
let prevMCtx := state.savedState.term.meta.meta.mctx
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := nextSavedState
|
||||||
|
newMVars := newMVarSet prevMCtx nextMCtx,
|
||||||
|
parentMVar? := .some convGoal,
|
||||||
|
convMVar? := .none
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
protected def GoalState.tryCalc (state: GoalState) (goalId: Nat) (pred: String):
|
||||||
|
Elab.TermElabM TacticResult := do
|
||||||
|
state.restoreElabM
|
||||||
|
if state.convMVar?.isSome then
|
||||||
|
return .invalidAction "Cannot initiate `calc` while in `conv` state"
|
||||||
|
let goal ← match state.savedState.tactic.goals.get? goalId with
|
||||||
|
| .some goal => pure goal
|
||||||
|
| .none => return .indexError goalId
|
||||||
|
let `(term|$pred) ← match Parser.runParserCategory
|
||||||
|
(env := state.env)
|
||||||
|
(catName := `term)
|
||||||
|
(input := pred)
|
||||||
|
(fileName := filename) with
|
||||||
|
| .ok syn => pure syn
|
||||||
|
| .error error => return .parseError error
|
||||||
|
try
|
||||||
|
goal.withContext do
|
||||||
|
let target ← instantiateMVars (← goal.getDecl).type
|
||||||
|
let tag := (← goal.getDecl).userName
|
||||||
|
|
||||||
|
let mut step ← Elab.Term.elabType <| ← do
|
||||||
|
if let some prevRhs := state.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 := state.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 calcPrevRhs? := Option.some rhs
|
||||||
|
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
|
||||||
|
if ¬(← Meta.isDefEq proofType target) then
|
||||||
|
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
|
||||||
|
return .success {
|
||||||
|
root := state.root,
|
||||||
|
savedState := {
|
||||||
|
term := ← MonadBacktrack.saveState,
|
||||||
|
tactic := { goals },
|
||||||
|
},
|
||||||
|
newMVars := goals.toSSet,
|
||||||
|
parentMVar? := .some goal,
|
||||||
|
calcPrevRhs?
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .failure #[← exception.toMessageData.toString]
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/--
|
||||||
|
Brings into scope a list of goals
|
||||||
|
-/
|
||||||
|
protected def GoalState.resume (state: GoalState) (goals: List MVarId): Except String GoalState :=
|
||||||
|
if ¬ (goals.all (λ goal => state.mvars.contains goal)) then
|
||||||
|
.error s!"Goals 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 },
|
||||||
|
},
|
||||||
|
calcPrevRhs? := .none,
|
||||||
|
}
|
||||||
|
/--
|
||||||
|
Brings into scope all goals from `branch`
|
||||||
|
-/
|
||||||
|
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)
|
||||||
|
|
||||||
|
protected def GoalState.rootExpr? (goalState: GoalState): Option Expr := do
|
||||||
|
let expr ← goalState.mctx.eAssignment.find? goalState.root
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := goalState.mctx) (e := expr)
|
||||||
|
if expr.hasMVar 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
|
||||||
|
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
|
||||||
|
protected def GoalState.assignedExprOf? (goalState: GoalState) (mvar: MVarId): Option Expr := do
|
||||||
|
let expr ← goalState.mctx.eAssignment.find? mvar
|
||||||
|
let (expr, _) := instantiateMVarsCore (mctx := goalState.mctx) (e := expr)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,213 @@
|
||||||
|
import Pantograph.Environment
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Serial
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
def defaultTermElabMContext: Lean.Elab.Term.Context := {
|
||||||
|
autoBoundImplicit := true,
|
||||||
|
declName? := some "_pantograph".toName,
|
||||||
|
errToSorry := false
|
||||||
|
}
|
||||||
|
def runMetaM { α } (metaM: Lean.MetaM α): Lean.CoreM α :=
|
||||||
|
metaM.run'
|
||||||
|
def runTermElabM { α } (termElabM: Lean.Elab.TermElabM α): Lean.CoreM α :=
|
||||||
|
termElabM.run' (ctx := defaultTermElabMContext) |>.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 Lean.Core.Context := do
|
||||||
|
let options? ← options.foldlM Lean.setOptionFromString' Lean.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 := Lean.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 Lean.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_catalog_m]
|
||||||
|
def envCatalog: Lean.CoreM Protocol.EnvCatalogResult :=
|
||||||
|
Environment.catalog ({}: Protocol.EnvCatalog)
|
||||||
|
|
||||||
|
@[export pantograph_mk_options]
|
||||||
|
def mkOptions
|
||||||
|
(printJsonPretty: Bool)
|
||||||
|
(printExprPretty: Bool)
|
||||||
|
(printExprAST: Bool)
|
||||||
|
(noRepeat: Bool)
|
||||||
|
(printAuxDecls: Bool)
|
||||||
|
(printImplementationDetailHyps: Bool)
|
||||||
|
: Protocol.Options := {
|
||||||
|
printJsonPretty,
|
||||||
|
printExprPretty,
|
||||||
|
printExprAST,
|
||||||
|
noRepeat,
|
||||||
|
printAuxDecls,
|
||||||
|
printImplementationDetailHyps,
|
||||||
|
}
|
||||||
|
|
||||||
|
@[export pantograph_env_inspect_m]
|
||||||
|
def envInspect (name: String) (value: Bool) (dependency: Bool) (options: @&Protocol.Options):
|
||||||
|
Lean.CoreM (Protocol.CR Protocol.EnvInspectResult) :=
|
||||||
|
Environment.inspect ({
|
||||||
|
name, value? := .some value, dependency?:= .some dependency
|
||||||
|
}: Protocol.EnvInspect) options
|
||||||
|
|
||||||
|
@[export pantograph_env_add_m]
|
||||||
|
def envAdd (name: String) (type: String) (value: String) (isTheorem: Bool):
|
||||||
|
Lean.CoreM (Protocol.CR Protocol.EnvAddResult) :=
|
||||||
|
Environment.addDecl { name, type, value, isTheorem }
|
||||||
|
|
||||||
|
def parseElabType (type: String): Lean.Elab.TermElabM (Protocol.CR Lean.Expr) := do
|
||||||
|
let env ← Lean.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 (← Lean.instantiateMVars expr)
|
||||||
|
|
||||||
|
/-- This must be a TermElabM since the parsed expr contains extra information -/
|
||||||
|
def parseElabExpr (expr: String) (expectedType?: Option String := .none): Lean.Elab.TermElabM (Protocol.CR Lean.Expr) := do
|
||||||
|
let env ← Lean.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 (← Lean.instantiateMVars expr)
|
||||||
|
|
||||||
|
@[export pantograph_expr_echo_m]
|
||||||
|
def exprEcho (expr: String) (expectedType?: Option String := .none) (options: @&Protocol.Options):
|
||||||
|
Lean.CoreM (Protocol.CR Protocol.ExprEchoResult) :=
|
||||||
|
runTermElabM do
|
||||||
|
let expr ← match ← parseElabExpr expr expectedType? with
|
||||||
|
| .error e => return .error e
|
||||||
|
| .ok expr => pure expr
|
||||||
|
try
|
||||||
|
let type ← unfoldAuxLemmas (← Lean.Meta.inferType expr)
|
||||||
|
return .ok {
|
||||||
|
type := (← serialize_expression options type),
|
||||||
|
expr := (← serialize_expression options expr)
|
||||||
|
}
|
||||||
|
catch exception =>
|
||||||
|
return .error $ errorI "typing" (← exception.toMessageData.toString)
|
||||||
|
|
||||||
|
@[export pantograph_goal_start_expr_m]
|
||||||
|
def goalStartExpr (expr: String): Lean.CoreM (Protocol.CR GoalState) :=
|
||||||
|
runTermElabM do
|
||||||
|
let expr ← match ← parseElabType expr with
|
||||||
|
| .error e => return .error e
|
||||||
|
| .ok expr => pure $ expr
|
||||||
|
return .ok $ ← GoalState.create expr
|
||||||
|
|
||||||
|
@[export pantograph_goal_tactic_m]
|
||||||
|
def goalTactic (state: GoalState) (goalId: Nat) (tactic: String): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryTactic goalId tactic
|
||||||
|
|
||||||
|
@[export pantograph_goal_assign_m]
|
||||||
|
def goalAssign (state: GoalState) (goalId: Nat) (expr: String): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryAssign goalId expr
|
||||||
|
|
||||||
|
@[export pantograph_goal_have_m]
|
||||||
|
def goalHave (state: GoalState) (goalId: Nat) (binderName: String) (type: String): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryHave goalId binderName type
|
||||||
|
|
||||||
|
@[export pantograph_goal_conv_m]
|
||||||
|
def goalConv (state: GoalState) (goalId: Nat): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.conv goalId
|
||||||
|
|
||||||
|
@[export pantograph_goal_conv_exit_m]
|
||||||
|
def goalConvExit (state: GoalState): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.convExit
|
||||||
|
|
||||||
|
@[export pantograph_goal_calc_m]
|
||||||
|
def goalCalc (state: GoalState) (goalId: Nat) (pred: String): Lean.CoreM TacticResult :=
|
||||||
|
runTermElabM <| state.tryCalc goalId pred
|
||||||
|
|
||||||
|
@[export pantograph_goal_focus]
|
||||||
|
def goalFocus (state: GoalState) (goalId: Nat): Option GoalState :=
|
||||||
|
state.focus goalId
|
||||||
|
|
||||||
|
@[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_continue]
|
||||||
|
def goalContinue (target: GoalState) (branch: GoalState): Except String GoalState :=
|
||||||
|
target.continue branch
|
||||||
|
|
||||||
|
@[export pantograph_goal_serialize_m]
|
||||||
|
def goalSerialize (state: GoalState) (options: @&Protocol.Options): Lean.CoreM (Array Protocol.Goal) :=
|
||||||
|
runMetaM <| state.serializeGoals (parent := .none) options
|
||||||
|
|
||||||
|
@[export pantograph_goal_print_m]
|
||||||
|
def goalPrint (state: GoalState) (options: @&Protocol.Options): Lean.CoreM Protocol.GoalPrintResult :=
|
||||||
|
runMetaM do
|
||||||
|
state.restoreMetaM
|
||||||
|
return {
|
||||||
|
root? := ← state.rootExpr?.mapM (λ expr => do
|
||||||
|
serialize_expression options (← unfoldAuxLemmas expr)),
|
||||||
|
parent? := ← state.parentExpr?.mapM (λ expr => do
|
||||||
|
serialize_expression options (← unfoldAuxLemmas expr)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,277 @@
|
||||||
|
/-
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
-- 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
|
||||||
|
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
|
||||||
|
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?: Option Bool := .none
|
||||||
|
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
|
||||||
|
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
|
||||||
|
-- If true/false, show/hide the value expressions; By default definitions
|
||||||
|
-- values are shown and theorem values are hidden.
|
||||||
|
value?: Option Bool := .some false
|
||||||
|
-- If true, show the type and value dependencies
|
||||||
|
dependency?: 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
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
structure EnvAdd where
|
||||||
|
name: String
|
||||||
|
type: String
|
||||||
|
value: String
|
||||||
|
isTheorem: Bool
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure EnvAddResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
|
||||||
|
/-- Set options; See `Options` struct above for meanings -/
|
||||||
|
structure OptionsSet where
|
||||||
|
printJsonPretty?: Option Bool
|
||||||
|
printExprPretty?: Option Bool
|
||||||
|
printExprAST?: Option Bool
|
||||||
|
noRepeat?: Option Bool
|
||||||
|
printAuxDecls?: Option Bool
|
||||||
|
printImplementationDetailHyps?: Option Bool
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure OptionsSetResult where
|
||||||
|
deriving Lean.ToJson
|
||||||
|
structure OptionsPrint where
|
||||||
|
deriving Lean.FromJson
|
||||||
|
abbrev OptionsPrintResult := Options
|
||||||
|
|
||||||
|
structure GoalStart where
|
||||||
|
-- Only one of the fields below may be populated.
|
||||||
|
expr: Option String -- Directly parse in an expression
|
||||||
|
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
|
||||||
|
calc?: Option String := .none
|
||||||
|
-- true to enter `conv`, `false` to exit. In case of exit the `goalId` is ignored.
|
||||||
|
conv?: Option Bool := .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
|
||||||
|
deriving Lean.FromJson
|
||||||
|
structure GoalPrintResult where
|
||||||
|
-- The root expression
|
||||||
|
root?: Option Expression := .none
|
||||||
|
-- The filling expression of the parent goal
|
||||||
|
parent?: Option 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
|
||||||
|
|
||||||
|
abbrev CR α := Except InteractionError α
|
||||||
|
|
||||||
|
end Pantograph.Protocol
|
|
@ -0,0 +1,315 @@
|
||||||
|
/-
|
||||||
|
All serialisation functions
|
||||||
|
-/
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Pantograph.Goal
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
-- Symbol processing functions --
|
||||||
|
|
||||||
|
def Lean.Name.isAuxLemma (n : Lean.Name) : Bool := n matches .num (.str _ "_auxLemma") _
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
/-- Unfold all lemmas created by `Lean.Meta.mkAuxLemma`. These end in `_auxLemma.nn` where `nn` is a number. -/
|
||||||
|
def unfoldAuxLemmas (e : Lean.Expr) : Lean.CoreM Lean.Expr := do
|
||||||
|
Lean.Meta.deltaExpand e Lean.Name.isAuxLemma
|
||||||
|
|
||||||
|
--- Input Functions ---
|
||||||
|
|
||||||
|
/-- Read syntax object from string -/
|
||||||
|
def parseTerm (env: Environment) (s: String): Except String Syntax :=
|
||||||
|
Parser.runParserCategory
|
||||||
|
(env := env)
|
||||||
|
(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)
|
||||||
|
|
||||||
|
|
||||||
|
--- Output Functions ---
|
||||||
|
|
||||||
|
def type_expr_to_bound (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 name_to_ast (name: Name) (sanitize: Bool := true): String :=
|
||||||
|
let internal := name.isInaccessibleUserName || name.hasMacroScopes
|
||||||
|
if sanitize && internal then "_"
|
||||||
|
else toString name |> enclose_if_escaped
|
||||||
|
where
|
||||||
|
enclose_if_escaped (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 serialize_sort_level_ast (level: Level) (sanitize: Bool): 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 := serialize_sort_level_ast v sanitize
|
||||||
|
let w := serialize_sort_level_ast w sanitize
|
||||||
|
s!"(:max {v} {w})"
|
||||||
|
| .imax v w =>
|
||||||
|
let v := serialize_sort_level_ast v sanitize
|
||||||
|
let w := serialize_sort_level_ast w sanitize
|
||||||
|
s!"(:imax {v} {w})"
|
||||||
|
| .param name =>
|
||||||
|
let name := name_to_ast name sanitize
|
||||||
|
s!"{name}"
|
||||||
|
| .mvar id =>
|
||||||
|
let name := name_to_ast id.name sanitize
|
||||||
|
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 serialize_expression_ast (expr: Expr) (sanitize: Bool := true): MetaM String := do
|
||||||
|
self expr
|
||||||
|
where
|
||||||
|
self (e: Expr): MetaM String :=
|
||||||
|
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 := of_name fvarId.name
|
||||||
|
pure s!"(:fv {name})"
|
||||||
|
| .mvar mvarId =>
|
||||||
|
let name := of_name mvarId.name
|
||||||
|
pure s!"(:mv {name})"
|
||||||
|
| .sort level =>
|
||||||
|
let level := serialize_sort_level_ast level sanitize
|
||||||
|
pure s!"(:sort {level})"
|
||||||
|
| .const declName _ =>
|
||||||
|
-- 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' := of_name binderName
|
||||||
|
let binderType' ← self binderType
|
||||||
|
let body' ← self body
|
||||||
|
let binderInfo' := binder_info_to_ast binderInfo
|
||||||
|
pure s!"(:lambda {binderName'} {binderType'} {body'}{binderInfo'})"
|
||||||
|
| .forallE binderName binderType body binderInfo => do
|
||||||
|
let binderName' := of_name binderName
|
||||||
|
let binderType' ← self binderType
|
||||||
|
let body' ← self body
|
||||||
|
let binderInfo' := binder_info_to_ast binderInfo
|
||||||
|
pure s!"(:forall {binderName'} {binderType'} {body'}{binderInfo'})"
|
||||||
|
| .letE name type value body _ => do
|
||||||
|
-- Dependent boolean flag diacarded
|
||||||
|
let name' := name_to_ast name
|
||||||
|
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 => s!"\"{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 typeName idx inner => do
|
||||||
|
let env ← getEnv
|
||||||
|
let ctor := getStructureCtor env typeName
|
||||||
|
let fieldName := getStructureFields env typeName |>.get! idx
|
||||||
|
let projectorName := getProjFnForField? env typeName fieldName |>.get!
|
||||||
|
|
||||||
|
let autos := String.intercalate " " (List.replicate ctor.numParams "_")
|
||||||
|
let inner ← self inner
|
||||||
|
pure s!"((:c {projectorName}) {autos} {inner})"
|
||||||
|
-- Elides all unhygenic names
|
||||||
|
binder_info_to_ast : Lean.BinderInfo → String
|
||||||
|
| .default => ""
|
||||||
|
| .implicit => " :implicit"
|
||||||
|
| .strictImplicit => " :strictImplicit"
|
||||||
|
| .instImplicit => " :instImplicit"
|
||||||
|
of_name (name: Name) := name_to_ast name sanitize
|
||||||
|
|
||||||
|
def serialize_expression (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 $ ← serialize_expression_ast e
|
||||||
|
| false => pure $ .none
|
||||||
|
return {
|
||||||
|
pp?,
|
||||||
|
sexp?
|
||||||
|
}
|
||||||
|
|
||||||
|
/-- Adapted from ppGoal -/
|
||||||
|
def serialize_goal (options: @&Protocol.Options) (goal: MVarId) (mvarDecl: MetavarDecl) (parentDecl?: Option MetavarDecl)
|
||||||
|
: 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 _ _ _ =>
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
return {
|
||||||
|
name := of_name fvarId.name,
|
||||||
|
userName:= of_name userName.simpMacroScopes,
|
||||||
|
}
|
||||||
|
| .ldecl _ fvarId userName _ _ _ _ => do
|
||||||
|
return {
|
||||||
|
name := of_name fvarId.name,
|
||||||
|
userName := toString userName.simpMacroScopes,
|
||||||
|
}
|
||||||
|
let ppVar (localDecl : LocalDecl) : MetaM Protocol.Variable := do
|
||||||
|
match localDecl with
|
||||||
|
| .cdecl _ fvarId userName type _ _ =>
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
let type ← instantiateMVars type
|
||||||
|
return {
|
||||||
|
name := of_name fvarId.name,
|
||||||
|
userName:= of_name userName,
|
||||||
|
isInaccessible? := .some userName.isInaccessibleUserName
|
||||||
|
type? := .some (← serialize_expression options type)
|
||||||
|
}
|
||||||
|
| .ldecl _ fvarId userName type val _ _ => do
|
||||||
|
let userName := userName.simpMacroScopes
|
||||||
|
let type ← instantiateMVars type
|
||||||
|
let value? ← if showLetValues then
|
||||||
|
let val ← instantiateMVars val
|
||||||
|
pure $ .some (← serialize_expression options val)
|
||||||
|
else
|
||||||
|
pure $ .none
|
||||||
|
return {
|
||||||
|
name := of_name fvarId.name,
|
||||||
|
userName:= of_name userName,
|
||||||
|
isInaccessible? := .some userName.isInaccessibleUserName
|
||||||
|
type? := .some (← serialize_expression 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 := of_name goal.name,
|
||||||
|
userName? := if mvarDecl.userName == .anonymous then .none else .some (of_name mvarDecl.userName),
|
||||||
|
isConversion := isLHSGoal? mvarDecl.type |>.isSome,
|
||||||
|
target := (← serialize_expression options (← instantiateMVars mvarDecl.type)),
|
||||||
|
vars := vars.reverse.toArray
|
||||||
|
}
|
||||||
|
where
|
||||||
|
of_name (n: Name) := name_to_ast 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 ← serialize_goal 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 -/
|
||||||
|
protected def GoalState.print (goalState: GoalState) (options: Protocol.GoalDiag := {}): MetaM Unit := do
|
||||||
|
goalState.restoreMetaM
|
||||||
|
let savedState := goalState.savedState
|
||||||
|
let goals := savedState.tactic.goals
|
||||||
|
let mctx ← getMCtx
|
||||||
|
let root := goalState.root
|
||||||
|
-- Print the root
|
||||||
|
match mctx.decls.find? root with
|
||||||
|
| .some decl => printMVar ">" root decl
|
||||||
|
| .none => IO.println s!">{root.name}: ??"
|
||||||
|
goals.forM (fun mvarId => do
|
||||||
|
if mvarId != root then
|
||||||
|
match mctx.decls.find? mvarId with
|
||||||
|
| .some decl => printMVar "⊢" mvarId decl
|
||||||
|
| .none => IO.println s!"⊢{mvarId.name}: ??"
|
||||||
|
)
|
||||||
|
let goals := goals.toSSet
|
||||||
|
mctx.decls.forM (fun mvarId decl => do
|
||||||
|
if goals.contains mvarId || mvarId == root then
|
||||||
|
pure ()
|
||||||
|
-- Print the remainig ones that users don't see in Lean
|
||||||
|
else if options.printAll then
|
||||||
|
let pref := if goalState.newMVars.contains mvarId then "~" else " "
|
||||||
|
printMVar pref mvarId decl
|
||||||
|
else
|
||||||
|
pure ()
|
||||||
|
--IO.println s!" {mvarId.name}{userNameToString decl.userName}"
|
||||||
|
)
|
||||||
|
where
|
||||||
|
printMVar (pref: String) (mvarId: MVarId) (decl: MetavarDecl): MetaM Unit := do
|
||||||
|
if options.printContext then
|
||||||
|
decl.lctx.fvarIdToDecl.forM printFVar
|
||||||
|
let type ← if options.instantiate
|
||||||
|
then instantiateMVars decl.type
|
||||||
|
else pure $ decl.type
|
||||||
|
let type_sexp ← serialize_expression_ast type (sanitize := false)
|
||||||
|
IO.println s!"{pref}{mvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type} {type_sexp}"
|
||||||
|
if options.printValue then
|
||||||
|
if let Option.some value := (← getMCtx).eAssignment.find? mvarId then
|
||||||
|
let value ← if options.instantiate
|
||||||
|
then instantiateMVars value
|
||||||
|
else pure $ value
|
||||||
|
IO.println s!" := {← Meta.ppExpr value}"
|
||||||
|
printFVar (fvarId: FVarId) (decl: LocalDecl): MetaM Unit := do
|
||||||
|
IO.println s!" | {fvarId.name}{userNameToString decl.userName}: {← Meta.ppExpr decl.type}"
|
||||||
|
userNameToString : Name → String
|
||||||
|
| .anonymous => ""
|
||||||
|
| other => s!"[{other}]"
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
@[export pantograph_version]
|
||||||
|
def version := "0.2.14"
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,141 @@
|
||||||
|
# Pantograph
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
For Nix based workflow, see below.
|
||||||
|
|
||||||
|
Install `elan` and `lake`. Execute
|
||||||
|
``` sh
|
||||||
|
make build/bin/pantograph
|
||||||
|
```
|
||||||
|
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`,
|
||||||
|
``` sh
|
||||||
|
LIB="../lib"
|
||||||
|
LIB_MATHLIB="$LIB/mathlib4/lake-packages"
|
||||||
|
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 provided `flake.nix` has a develop environment with Lean already setup.
|
||||||
|
|
||||||
|
## Executable Usage
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
pantograph MODULES|LEAN_OPTIONS
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The `pantograph` 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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
See `Pantograph/Protocol.lean` for a description of the parameters and return values in JSON.
|
||||||
|
- `reset`: Delete all cached expressions and proof trees
|
||||||
|
- `expr.echo {"expr": <expr>, "type": <optional expected type>}`: Determine the type of an expression and round-trip 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.
|
||||||
|
- `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`
|
||||||
|
- `options.print`: Display the current set of options
|
||||||
|
- `goal.start {["name": <name>], ["expr": <expr>], ["copyFrom": <symbol>]}`: Start a new goal from a given expression or symbol
|
||||||
|
- `goal.tactic {"stateId": <id>, "goalId": <id>, ["tactic": <tactic>], ["expr":
|
||||||
|
<expr>]}`: Execute a tactic string on a given goal. `tactic` executes an
|
||||||
|
ordinary tactic, `expr` assigns an expression to the current goal, `have`
|
||||||
|
executes the have tactic, ``calc` (of the form `lhs op rhs`) executes one step
|
||||||
|
of `calc`, and `"conv": true`/`"conv": false` enters/exits conversion tactic
|
||||||
|
mode.
|
||||||
|
- `goal.continue {"stateId": <id>, ["branch": <id>], ["goals": <names>]}`: Continue from a proof state
|
||||||
|
- `goal.remove {"stateIds": [<id>]}"`: Remove a bunch of stored goals.
|
||||||
|
- `goal.print {"stateId": <id>}"`: Print a goal state
|
||||||
|
- `stat`: Display resource usage
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
If lean encounters stack overflow problems when printing catalog, execute this before running lean:
|
||||||
|
```sh
|
||||||
|
ulimit -s unlimited
|
||||||
|
```
|
||||||
|
|
||||||
|
## Library Usage
|
||||||
|
|
||||||
|
`Pantograph/Library.lean` exposes a series of interfaces which allow FFI call
|
||||||
|
with `Pantograph` which mirrors the REPL commands above. It is recommended to
|
||||||
|
call Pantograph via this FFI since it provides a tremendous speed up.
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
The tests are based on `LSpec`. To run tests,
|
||||||
|
``` sh
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nix based workflow
|
||||||
|
|
||||||
|
The included Nix flake provides build targets for `sharedLib` and `executable`.
|
||||||
|
The executable can be used as-is, but linking against the shared library
|
||||||
|
requires the presence of `lean-all`.
|
||||||
|
|
||||||
|
To run tests:
|
||||||
|
``` sh
|
||||||
|
nix flake check
|
||||||
|
```
|
|
@ -0,0 +1,63 @@
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Library
|
||||||
|
import Pantograph.Protocol
|
||||||
|
import Lean
|
||||||
|
import LSpec
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
namespace Pantograph
|
||||||
|
|
||||||
|
-- Auxiliary functions
|
||||||
|
namespace Protocol
|
||||||
|
/-- Set internal names to "" -/
|
||||||
|
def Goal.devolatilize (goal: Goal): Goal :=
|
||||||
|
{
|
||||||
|
goal with
|
||||||
|
name := "",
|
||||||
|
vars := goal.vars.map removeInternalAux,
|
||||||
|
}
|
||||||
|
where removeInternalAux (v: Variable): Variable :=
|
||||||
|
{
|
||||||
|
v with
|
||||||
|
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
|
||||||
|
|
||||||
|
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}"
|
||||||
|
| .indexError index => s!".indexError {index}"
|
||||||
|
| .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 := Pantograph.defaultTermElabMContext)
|
||||||
|
|
||||||
|
end Test
|
||||||
|
|
||||||
|
end Pantograph
|
|
@ -0,0 +1,107 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Serial
|
||||||
|
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),
|
||||||
|
("Lean.Name".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 suite: List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("Catalog", test_catalog),
|
||||||
|
("Symbol Visibility", test_symbol_visibility),
|
||||||
|
("Inspect", test_inspect),
|
||||||
|
]
|
||||||
|
|
||||||
|
end Pantograph.Test.Environment
|
|
@ -0,0 +1,171 @@
|
||||||
|
/- Integration test for the REPL
|
||||||
|
-/
|
||||||
|
import LSpec
|
||||||
|
import Pantograph
|
||||||
|
namespace Pantograph.Test.Integration
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
def subroutine_named_step (name cmd: String) (payload: List (String × Lean.Json))
|
||||||
|
(expected: Lean.Json): MainM LSpec.TestSeq := do
|
||||||
|
let result ← execute { cmd := cmd, payload := Lean.Json.mkObj payload }
|
||||||
|
return LSpec.test name (toString result = toString expected)
|
||||||
|
def subroutine_step (cmd: String) (payload: List (String × Lean.Json))
|
||||||
|
(expected: Lean.Json): MainM LSpec.TestSeq := subroutine_named_step cmd cmd payload expected
|
||||||
|
|
||||||
|
def subroutine_runner (steps: List (MainM LSpec.TestSeq)): IO LSpec.TestSeq := do
|
||||||
|
-- Setup the environment for execution
|
||||||
|
let env ← Lean.importModules
|
||||||
|
(imports := #[{module := Lean.Name.str .anonymous "Init", runtimeOnly := false }])
|
||||||
|
(opts := {})
|
||||||
|
(trustLevel := 1)
|
||||||
|
let context: Context := {
|
||||||
|
imports := ["Init"]
|
||||||
|
}
|
||||||
|
let coreContext: Lean.Core.Context ← createCoreContext #[]
|
||||||
|
let commands: MainM LSpec.TestSeq :=
|
||||||
|
steps.foldlM (λ suite step => do
|
||||||
|
let result ← step
|
||||||
|
return suite ++ result) LSpec.TestSeq.done
|
||||||
|
try
|
||||||
|
let coreM := commands.run context |>.run' {}
|
||||||
|
return Prod.fst $ (← coreM.toIO coreContext { env := env })
|
||||||
|
catch ex =>
|
||||||
|
return LSpec.check s!"Uncaught IO exception: {ex.toString}" false
|
||||||
|
|
||||||
|
def test_elab : IO LSpec.TestSeq :=
|
||||||
|
subroutine_runner [
|
||||||
|
subroutine_step "expr.echo"
|
||||||
|
[("expr", .str "λ {α : Sort (u + 1)} => List α")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
type := { pp? := .some "{α : Type u} → Type u" },
|
||||||
|
expr := { pp? := .some "fun {α} => List α" }
|
||||||
|
}: Protocol.ExprEchoResult)),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_option_modify : IO LSpec.TestSeq :=
|
||||||
|
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 := {}
|
||||||
|
subroutine_runner [
|
||||||
|
subroutine_step "env.inspect"
|
||||||
|
[("name", .str "Nat.add_one")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
type := { pp? }, module? }:
|
||||||
|
Protocol.EnvInspectResult)),
|
||||||
|
subroutine_step "options.set"
|
||||||
|
[("printExprAST", .bool true)]
|
||||||
|
(Lean.toJson ({ }:
|
||||||
|
Protocol.OptionsSetResult)),
|
||||||
|
subroutine_step "env.inspect"
|
||||||
|
[("name", .str "Nat.add_one")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
type := { pp?, sexp? }, module? }:
|
||||||
|
Protocol.EnvInspectResult)),
|
||||||
|
subroutine_step "options.print"
|
||||||
|
[]
|
||||||
|
(Lean.toJson ({ options with printExprAST := true }:
|
||||||
|
Protocol.OptionsPrintResult))
|
||||||
|
]
|
||||||
|
def test_malformed_command : IO LSpec.TestSeq :=
|
||||||
|
let invalid := "invalid"
|
||||||
|
subroutine_runner [
|
||||||
|
subroutine_named_step "Invalid command" invalid
|
||||||
|
[("name", .str "Nat.add_one")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
error := "command", desc := s!"Unknown command {invalid}"}:
|
||||||
|
Protocol.InteractionError)),
|
||||||
|
subroutine_named_step "JSON Deserialization" "expr.echo"
|
||||||
|
[(invalid, .str "Random garbage data")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
error := "command", desc := s!"Unable to parse json: Pantograph.Protocol.ExprEcho.expr: String expected"}:
|
||||||
|
Protocol.InteractionError))
|
||||||
|
]
|
||||||
|
def test_tactic : IO LSpec.TestSeq :=
|
||||||
|
let goal1: Protocol.Goal := {
|
||||||
|
name := "_uniq.11",
|
||||||
|
target := { pp? := .some "∀ (q : Prop), x ∨ q → q ∨ x" },
|
||||||
|
vars := #[{ name := "_uniq.10", userName := "x", isInaccessible? := .some false, type? := .some { pp? := .some "Prop" }}],
|
||||||
|
}
|
||||||
|
let goal2: Protocol.Goal := {
|
||||||
|
name := "_uniq.14",
|
||||||
|
target := { pp? := .some "x ∨ y → y ∨ x" },
|
||||||
|
vars := #[
|
||||||
|
{ name := "_uniq.10", userName := "x", isInaccessible? := .some false, type? := .some { pp? := .some "Prop" }},
|
||||||
|
{ name := "_uniq.13", userName := "y", isInaccessible? := .some false, type? := .some { pp? := .some "Prop" }}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
subroutine_runner [
|
||||||
|
subroutine_step "goal.start"
|
||||||
|
[("expr", .str "∀ (p q: Prop), p ∨ q → q ∨ p")]
|
||||||
|
(Lean.toJson ({stateId := 0, root := "_uniq.9"}:
|
||||||
|
Protocol.GoalStartResult)),
|
||||||
|
subroutine_step "goal.tactic"
|
||||||
|
[("stateId", .num 0), ("goalId", .num 0), ("tactic", .str "intro x")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
nextStateId? := .some 1,
|
||||||
|
goals? := #[goal1],
|
||||||
|
}:
|
||||||
|
Protocol.GoalTacticResult)),
|
||||||
|
subroutine_step "goal.print"
|
||||||
|
[("stateId", .num 1)]
|
||||||
|
(Lean.toJson ({
|
||||||
|
parent? := .some { pp? := .some "fun x => ?m.12 x" },
|
||||||
|
}:
|
||||||
|
Protocol.GoalPrintResult)),
|
||||||
|
subroutine_step "goal.tactic"
|
||||||
|
[("stateId", .num 1), ("goalId", .num 0), ("tactic", .str "intro y")]
|
||||||
|
(Lean.toJson ({
|
||||||
|
nextStateId? := .some 2,
|
||||||
|
goals? := #[goal2],
|
||||||
|
}:
|
||||||
|
Protocol.GoalTacticResult))
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_env_add_inspect : IO LSpec.TestSeq :=
|
||||||
|
let name1 := "Pantograph.mystery"
|
||||||
|
let name2 := "Pantograph.mystery2"
|
||||||
|
subroutine_runner [
|
||||||
|
subroutine_step "env.add"
|
||||||
|
[
|
||||||
|
("name", .str name1),
|
||||||
|
("type", .str "Prop → Prop → Prop"),
|
||||||
|
("value", .str "λ (a b: Prop) => Or a b"),
|
||||||
|
("isTheorem", .bool false)
|
||||||
|
]
|
||||||
|
(Lean.toJson ({}: Protocol.EnvAddResult)),
|
||||||
|
subroutine_step "env.inspect"
|
||||||
|
[("name", .str name1)]
|
||||||
|
(Lean.toJson ({
|
||||||
|
value? := .some { pp? := .some "fun a b => a ∨ b" },
|
||||||
|
type := { pp? := .some "Prop → Prop → Prop" },
|
||||||
|
}:
|
||||||
|
Protocol.EnvInspectResult)),
|
||||||
|
subroutine_step "env.add"
|
||||||
|
[
|
||||||
|
("name", .str name2),
|
||||||
|
("type", .str "Nat → Int"),
|
||||||
|
("value", .str "λ (a: Nat) => a + 1"),
|
||||||
|
("isTheorem", .bool false)
|
||||||
|
]
|
||||||
|
(Lean.toJson ({}: Protocol.EnvAddResult)),
|
||||||
|
subroutine_step "env.inspect"
|
||||||
|
[("name", .str name2)]
|
||||||
|
(Lean.toJson ({
|
||||||
|
value? := .some { pp? := .some "fun a => ↑a + 1" },
|
||||||
|
type := { pp? := .some "Nat → Int" },
|
||||||
|
}:
|
||||||
|
Protocol.EnvInspectResult))
|
||||||
|
]
|
||||||
|
|
||||||
|
def suite: List (String × IO LSpec.TestSeq) :=
|
||||||
|
[
|
||||||
|
("Elab", test_elab),
|
||||||
|
("Option modify", test_option_modify),
|
||||||
|
("Malformed command", test_malformed_command),
|
||||||
|
("Tactic", test_tactic),
|
||||||
|
("env.add env.inspect", test_env_add_inspect),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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 _ 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,53 @@
|
||||||
|
import LSpec
|
||||||
|
import Test.Environment
|
||||||
|
import Test.Integration
|
||||||
|
import Test.Library
|
||||||
|
import Test.Metavar
|
||||||
|
import Test.Proofs
|
||||||
|
import Test.Serial
|
||||||
|
|
||||||
|
-- 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),
|
||||||
|
("Integration", Integration.suite),
|
||||||
|
("Library", Library.suite env_default),
|
||||||
|
("Metavar", Metavar.suite env_default),
|
||||||
|
("Proofs", Proofs.suite env_default),
|
||||||
|
("Serial", Serial.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,281 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
namespace Pantograph.Test.Metavar
|
||||||
|
open Pantograph
|
||||||
|
open Lean
|
||||||
|
|
||||||
|
abbrev TestM := StateRefT LSpec.TestSeq (ReaderT Protocol.Options Elab.TermElabM)
|
||||||
|
|
||||||
|
def addTest (test: LSpec.TestSeq): TestM Unit := do
|
||||||
|
set $ (← get) ++ test
|
||||||
|
|
||||||
|
-- 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 (← serialize_expression_ast 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 },
|
||||||
|
isInaccessible? := .some false
|
||||||
|
})).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 := defaultTermElabMContext)
|
||||||
|
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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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 (goalId := 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 "Nat → Prop", .some "∀ (x : Nat), ?m.29 x"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state2.rootExpr?.isNone
|
||||||
|
|
||||||
|
let state3 ← match ← state2.tryAssign (goalId := 1) (expr := "fun x => Eq.refl x") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check ":= Eq.refl" ((← 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.tryTactic (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.tryTactic (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 ≤ ?m.succ", .some "?m.succ ≤ 5", .some "Nat"])
|
||||||
|
addTest $ LSpec.test "(2 root)" state1b.rootExpr?.isNone
|
||||||
|
|
||||||
|
-- Roundtrip
|
||||||
|
--let coupled_goals := coupled_goals.map (λ g =>
|
||||||
|
-- { name := str_to_name $ name_to_ast g.name (sanitize := false)})
|
||||||
|
let coupled_goals := coupled_goals.map (λ g => name_to_ast 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 ≤ ?m.succ", .some "?m.succ ≤ 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 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,565 @@
|
||||||
|
/-
|
||||||
|
Tests pertaining to goals with no interdependencies
|
||||||
|
-/
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Goal
|
||||||
|
import Pantograph.Serial
|
||||||
|
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 := StateRefT LSpec.TestSeq (ReaderT Protocol.Options Elab.TermElabM)
|
||||||
|
|
||||||
|
def addTest (test: LSpec.TestSeq): TestM Unit := do
|
||||||
|
set $ (← get) ++ test
|
||||||
|
|
||||||
|
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 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 },
|
||||||
|
isInaccessible? := .some false
|
||||||
|
})).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 := defaultTermElabMContext)
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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.tryTactic (goalId := 0) (tactic := "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.tryTactic (goalId := 0) (tactic := "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.tryTactic (goalId := 0) (tactic := "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.tryTactic (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.tryTactic (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 }),
|
||||||
|
isInaccessible? := x.snd.map (λ _ => false)
|
||||||
|
})).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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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 [("p", "Prop"), ("q", "Prop"), ("h", "p ∨ q")] "q ∨ p"])
|
||||||
|
addTest $ LSpec.check "(1 parent)" state1.parentExpr?.isSome
|
||||||
|
addTest $ LSpec.check "(1 root)" state1.rootExpr?.isNone
|
||||||
|
|
||||||
|
let tactic := "cases h"
|
||||||
|
let state2 ← match ← state1.tryTactic (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"])
|
||||||
|
addTest $ LSpec.check "(2 parent)" state2.parentExpr?.isSome
|
||||||
|
addTest $ LSpec.check "(2 root)" state2.rootExpr?.isNone
|
||||||
|
|
||||||
|
let state2parent ← serialize_expression_ast state2.parentExpr?.get! (sanitize := false)
|
||||||
|
-- This is due to delayed assignment
|
||||||
|
addTest $ LSpec.test "(2 parent)" (state2parent ==
|
||||||
|
"((:mv _uniq.43) (:fv _uniq.16) ((:c Eq.refl) ((:c Or) (:fv _uniq.10) (:fv _uniq.13)) (:fv _uniq.16)))")
|
||||||
|
|
||||||
|
let state3_1 ← match ← state2.tryTactic (goalId := 0) (tactic := "apply Or.inr") with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
let state3_1parent ← serialize_expression_ast state3_1.parentExpr?.get! (sanitize := false)
|
||||||
|
addTest $ LSpec.test "(3_1 parent)" (state3_1parent == "((:c Or.inr) (:fv _uniq.13) (:fv _uniq.10) (:mv _uniq.78))")
|
||||||
|
addTest $ LSpec.check "· apply Or.inr" (state3_1.goals.length = 1)
|
||||||
|
let state4_1 ← match ← state3_1.tryTactic (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 ← serialize_expression_ast state4_1.parentExpr?.get! (sanitize := false)
|
||||||
|
addTest $ LSpec.test "(4_1 parent)" (state4_1parent == "(:fv _uniq.47)")
|
||||||
|
addTest $ LSpec.check "(4_1 root)" state4_1.rootExpr?.isNone
|
||||||
|
let state3_2 ← match ← state2.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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, isInaccessible? := .some false },
|
||||||
|
{ userName := "q", type? := .some typeProp, isInaccessible? := .some false },
|
||||||
|
{ userName := "h✝", type? := .some { pp? := .some varName }, isInaccessible? := .some true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_have: TestM Unit := do
|
||||||
|
let state? ← startProof (.expr "∀ (p q: Prop), p → ((p ∨ q) ∨ (p ∨ q))")
|
||||||
|
let state0 ← match state? with
|
||||||
|
| .some state => pure state
|
||||||
|
| .none => do
|
||||||
|
addTest $ assertUnreachable "Goal could not parse"
|
||||||
|
return ()
|
||||||
|
let tactic := "intro p q h"
|
||||||
|
let state1 ← match ← state0.tryTactic (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 [("p", "Prop"), ("q", "Prop"), ("h", "p")] "(p ∨ q) ∨ p ∨ q"])
|
||||||
|
|
||||||
|
let expr := "Or.inl (Or.inl h)"
|
||||||
|
let state2 ← match ← state1.tryAssign (goalId := 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state2.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
let haveBind := "y"
|
||||||
|
let haveType := "p ∨ q"
|
||||||
|
let state2 ← match ← state1.tryHave (goalId := 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 (options := ← read)).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 (goalId := 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state3.serializeGoals (options := ← read)).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 (goalId := 0) (expr := expr) with
|
||||||
|
| .success state => pure state
|
||||||
|
| other => do
|
||||||
|
addTest $ assertUnreachable $ other.toString
|
||||||
|
return ()
|
||||||
|
addTest $ LSpec.check s!":= {expr}" ((← state4.serializeGoals (options := ← read)).map (·.devolatilize) =
|
||||||
|
#[])
|
||||||
|
|
||||||
|
addTest $ LSpec.check "(4 root)" state4.rootExpr?.isSome
|
||||||
|
|
||||||
|
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.tryTactic (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 (goalId := 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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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.tryTactic (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 (goalId := 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"
|
||||||
|
])
|
||||||
|
|
||||||
|
let tactic := "apply h1"
|
||||||
|
let state2m ← match ← state2.tryTactic (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 (goalId := 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")
|
||||||
|
])
|
||||||
|
let tactic := "apply h2"
|
||||||
|
let state4m ← match ← state4.tryTactic (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 suite (env: Environment): List (String × IO LSpec.TestSeq) :=
|
||||||
|
let tests := [
|
||||||
|
("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),
|
||||||
|
("have", test_have),
|
||||||
|
("conv", test_conv),
|
||||||
|
("calc", test_calc),
|
||||||
|
]
|
||||||
|
tests.map (fun (name, test) => (name, proofRunner env test))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end Pantograph.Test.Proofs
|
|
@ -0,0 +1,106 @@
|
||||||
|
import LSpec
|
||||||
|
import Pantograph.Serial
|
||||||
|
import Test.Common
|
||||||
|
import Lean
|
||||||
|
|
||||||
|
open Lean
|
||||||
|
namespace Pantograph.Test.Serial
|
||||||
|
|
||||||
|
open Pantograph
|
||||||
|
|
||||||
|
deriving instance Repr, DecidableEq for Protocol.BoundExpression
|
||||||
|
|
||||||
|
def test_name_to_ast: LSpec.TestSeq :=
|
||||||
|
let quote := "\""
|
||||||
|
let escape := "\\"
|
||||||
|
LSpec.test "a.b.1" (name_to_ast (Name.num (.str (.str .anonymous "a") "b") 1) = "a.b.1") ++
|
||||||
|
LSpec.test "seg.«a.b»" (name_to_ast (Name.str (.str .anonymous "seg") "a.b") = s!"{quote}seg.«a.b»{quote}") ++
|
||||||
|
-- Pathological test case
|
||||||
|
LSpec.test s!"«̈{escape}{quote}»" (name_to_ast (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 ((← type_expr_to_bound 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 _ (:c Nat) (:forall _ (: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)) :implicit) :implicit)"),
|
||||||
|
-- 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 ((← serialize_expression_ast 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 × 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))"),
|
||||||
|
-- This tests `autoBoundImplicit`
|
||||||
|
("λ {α : Sort (u + 1)} => List α", "(:lambda α (:sort (+ u 1)) ((:c List) 0) :implicit)"),
|
||||||
|
]
|
||||||
|
let termElabM: Elab.TermElabM LSpec.TestSeq := entries.foldlM (λ suites (source, target) => 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
|
||||||
|
let test := LSpec.check source ((← serialize_expression_ast expr) = target)
|
||||||
|
return LSpec.TestSeq.append suites test) LSpec.TestSeq.done
|
||||||
|
runMetaMSeq env $ termElabM.run' (ctx := defaultTermElabMContext)
|
||||||
|
|
||||||
|
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)) :implicit) :implicit)"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
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 ((← serialize_expression_ast expr) = target)
|
||||||
|
return LSpec.TestSeq.append suites test) LSpec.TestSeq.done
|
||||||
|
runMetaMSeq env $ termElabM.run' (ctx := defaultTermElabMContext)
|
||||||
|
|
||||||
|
-- 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) :=
|
||||||
|
[
|
||||||
|
("name_to_ast", do pure test_name_to_ast),
|
||||||
|
("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.Serial
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="256"
|
||||||
|
height="256"
|
||||||
|
viewBox="0 0 55.900957 55.900957"
|
||||||
|
version="1.1"
|
||||||
|
id="svg21534"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
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="namedview21536"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="5.1754899"
|
||||||
|
inkscape:cx="158.82554"
|
||||||
|
inkscape:cy="91.682142"
|
||||||
|
inkscape:window-width="3777"
|
||||||
|
inkscape:window-height="2093"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1"><inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid23833"
|
||||||
|
spacingx="3.4938098"
|
||||||
|
spacingy="3.4938098"
|
||||||
|
empspacing="4" /></sodipodi:namedview><defs
|
||||||
|
id="defs21531" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"><rect
|
||||||
|
style="fill:#3e3e3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.78013;stroke-miterlimit:3.4;stroke-dasharray:none"
|
||||||
|
id="rect26805"
|
||||||
|
width="11.502316"
|
||||||
|
height="2.2512667"
|
||||||
|
x="33.344425"
|
||||||
|
y="7.6690259"
|
||||||
|
ry="0.28140834"
|
||||||
|
rx="0.47926313" /><path
|
||||||
|
style="fill:#3e3e3e;stroke:none;stroke-width:0.218363px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||||
|
d="m 35.764667,9.9513013 c 0,0 -26.8581417,13.7987337 -28.0863506,14.9501437 -1.250042,1.171878 3.2347846,3.945325 3.2347846,3.945325 l 21.34979,14.934062 6.624567,0.453105 -27.599216,-17.304358 c 0,0 -0.603209,-0.08927 -0.600411,-0.762283 0.0028,-0.673015 27.32022,-16.4227356 27.32022,-16.4227356 z"
|
||||||
|
id="path27381"
|
||||||
|
sodipodi:nodetypes="csccccscc" /><path
|
||||||
|
style="fill:#3e3e3e;stroke:none;stroke-width:0.218363px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||||
|
d="M 10.97848,26.985751 40.537772,9.7943227 41.921795,9.7005084 11.210626,27.421377 Z"
|
||||||
|
id="path27479" /><path
|
||||||
|
style="fill:#3e3e3e;stroke:none;stroke-width:0.218363px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
|
||||||
|
d="m 7.0509847,25.522367 c -0.8266141,1.386819 -2.4011783,4.48805 -2.4706357,4.90223 -0.069458,0.414182 0.4434324,0.513474 0.8491061,0.757041 C 5.835129,31.425204 19.33424,43.917182 19.33424,43.917182 l 0.324562,-0.539228 c 0,0 -14.2055729,-12.369493 -14.0644435,-12.868167 0.1411292,-0.498672 3.544896,-3.777392 3.544896,-3.777392 L 7.4596884,25.117508 Z"
|
||||||
|
id="path27481" /><rect
|
||||||
|
style="fill:#3e3e3e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.11692;stroke-miterlimit:3.4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect27483"
|
||||||
|
width="36.38942"
|
||||||
|
height="3.6217353"
|
||||||
|
x="13.953447"
|
||||||
|
y="43.009739"
|
||||||
|
rx="0.43672624"
|
||||||
|
ry="0.43672624" /><path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.218363px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M -2.1119274,7.666599 H 64.179192"
|
||||||
|
id="path27487" /></g></svg>
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -0,0 +1,238 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709336216,
|
||||||
|
"narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1656928814,
|
||||||
|
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lean": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"lean4-mode": "lean4-mode",
|
||||||
|
"nix": "nix",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"nixpkgs-old": "nixpkgs-old"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1711508550,
|
||||||
|
"narHash": "sha256-UK4DnYmwXLcqHA316Zkn0cnujdYlxqUf+b6S4l56Q3s=",
|
||||||
|
"owner": "leanprover",
|
||||||
|
"repo": "lean4",
|
||||||
|
"rev": "b4caee80a3dfc5c9619d88b16c40cc3db90da4e2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "leanprover",
|
||||||
|
"ref": "b4caee80a3dfc5c9619d88b16c40cc3db90da4e2",
|
||||||
|
"repo": "lean4",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lean4-mode": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676498134,
|
||||||
|
"narHash": "sha256-u3WvyKxOViZG53hkb8wd2/Og6muTecbh+NdflIgVeyk=",
|
||||||
|
"owner": "leanprover",
|
||||||
|
"repo": "lean4-mode",
|
||||||
|
"rev": "2c6ef33f476fdf5eb5e4fa4fa023ba8b11372440",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "leanprover",
|
||||||
|
"repo": "lean4-mode",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lowdown-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1633514407,
|
||||||
|
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lspec": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1701971219,
|
||||||
|
"narHash": "sha256-HYDRzkT2UaLDrqKNWesh9C4LJNt0JpW0u68wYVj4Byw=",
|
||||||
|
"owner": "lurk-lab",
|
||||||
|
"repo": "LSpec",
|
||||||
|
"rev": "3388be5a1d1390594a74ec469fd54a5d84ff6114",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "lurk-lab",
|
||||||
|
"ref": "3388be5a1d1390594a74ec469fd54a5d84ff6114",
|
||||||
|
"repo": "LSpec",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix": {
|
||||||
|
"inputs": {
|
||||||
|
"lowdown-src": "lowdown-src",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1657097207,
|
||||||
|
"narHash": "sha256-SmeGmjWM3fEed3kQjqIAO8VpGmkC2sL1aPE7kKpK650=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nix",
|
||||||
|
"rev": "f6316b49a0c37172bca87ede6ea8144d7d89832f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1653988320,
|
||||||
|
"narHash": "sha256-ZaqFFsSDipZ6KVqriwM34T739+KLYJvNmCWzErjAg7c=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "2fa57ed190fd6c7c746319444f34b5917666e5c1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-22.05-small",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "lib",
|
||||||
|
"lastModified": 1709237383,
|
||||||
|
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "lib",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-old": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1581379743,
|
||||||
|
"narHash": "sha256-i1XCn9rKuLjvCdu2UeXKzGLF6IuQePQKFt4hEKRU5oc=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "34c7eb7545d155cc5b6f499b23a7cb1c96ab4d59",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-19.03",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-regression": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1643052045,
|
||||||
|
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1686089707,
|
||||||
|
"narHash": "sha256-LTNlJcru2qJ0XhlhG9Acp5KyjB774Pza3tRH0pKIb3o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "af21c31b2a1ec5d361ed8050edd0303c31306397",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1711703276,
|
||||||
|
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"lean": "lean",
|
||||||
|
"lspec": "lspec",
|
||||||
|
"nixpkgs": "nixpkgs_3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
description = "Pantograph";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
lean = {
|
||||||
|
url = "github:leanprover/lean4?ref=b4caee80a3dfc5c9619d88b16c40cc3db90da4e2";
|
||||||
|
# Do not follow input's nixpkgs since it could cause build failures
|
||||||
|
};
|
||||||
|
lspec = {
|
||||||
|
url = "github:lurk-lab/LSpec?ref=3388be5a1d1390594a74ec469fd54a5d84ff6114";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs @ {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-parts,
|
||||||
|
lean,
|
||||||
|
lspec,
|
||||||
|
...
|
||||||
|
} : flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
flake = {
|
||||||
|
};
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
];
|
||||||
|
perSystem = { system, pkgs, ... }: let
|
||||||
|
leanPkgs = lean.packages.${system};
|
||||||
|
lspecLib = leanPkgs.buildLeanPackage {
|
||||||
|
name = "LSpec";
|
||||||
|
roots = [ "Main" "LSpec" ];
|
||||||
|
src = "${lspec}";
|
||||||
|
};
|
||||||
|
project = leanPkgs.buildLeanPackage {
|
||||||
|
name = "Pantograph";
|
||||||
|
roots = [ "Main" "Pantograph" ];
|
||||||
|
src = pkgs.lib.cleanSourceWith {
|
||||||
|
src = ./.;
|
||||||
|
filter = path: type:
|
||||||
|
!(pkgs.lib.hasInfix "/Test/" path) &&
|
||||||
|
!(pkgs.lib.hasSuffix ".md" path) &&
|
||||||
|
!(pkgs.lib.hasSuffix "Makefile" path);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
test = leanPkgs.buildLeanPackage {
|
||||||
|
name = "Test";
|
||||||
|
# NOTE: The src directory must be ./. since that is where the import
|
||||||
|
# root begins (e.g. `import Test.Environment` and not `import
|
||||||
|
# Environment`) and thats where `lakefile.lean` resides.
|
||||||
|
roots = [ "Test.Main" ];
|
||||||
|
deps = [ lspecLib project ];
|
||||||
|
src = pkgs.lib.cleanSourceWith {
|
||||||
|
src = ./.;
|
||||||
|
filter = path: type:
|
||||||
|
!(pkgs.lib.hasInfix "Pantograph" path);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in rec {
|
||||||
|
packages = {
|
||||||
|
inherit (leanPkgs) lean lean-all;
|
||||||
|
inherit (project) sharedLib executable;
|
||||||
|
default = project.executable;
|
||||||
|
};
|
||||||
|
checks = {
|
||||||
|
test = pkgs.runCommand "test" {
|
||||||
|
buildInputs = [ test.executable leanPkgs.lean-all ];
|
||||||
|
} ''
|
||||||
|
#export LEAN_SRC_PATH="${./.}"
|
||||||
|
${test.executable}/bin/test > $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = [ leanPkgs.lean-all leanPkgs.lean ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"version": 7,
|
||||||
|
"packagesDir": ".lake/packages",
|
||||||
|
"packages":
|
||||||
|
[{"url": "https://github.com/lurk-lab/LSpec.git",
|
||||||
|
"type": "git",
|
||||||
|
"subDir": null,
|
||||||
|
"rev": "3388be5a1d1390594a74ec469fd54a5d84ff6114",
|
||||||
|
"name": "LSpec",
|
||||||
|
"manifestFile": "lake-manifest.json",
|
||||||
|
"inputRev": "3388be5a1d1390594a74ec469fd54a5d84ff6114",
|
||||||
|
"inherited": false,
|
||||||
|
"configFile": "lakefile.lean"}],
|
||||||
|
"name": "pantograph",
|
||||||
|
"lakeDir": ".lake"}
|
|
@ -1,19 +1,25 @@
|
||||||
import Lake
|
import Lake
|
||||||
open Lake DSL
|
open Lake DSL
|
||||||
|
|
||||||
|
package pantograph
|
||||||
package pantograph {
|
|
||||||
-- add package configuration options here
|
|
||||||
}
|
|
||||||
|
|
||||||
require mathlib from git
|
|
||||||
"https://github.com/leanprover-community/mathlib4.git" @ "8e5a00a8afc8913c0584cb85f37951995275fd87"
|
|
||||||
|
|
||||||
lean_lib Pantograph {
|
lean_lib Pantograph {
|
||||||
-- add library configuration options here
|
defaultFacets := #[LeanLib.sharedFacet]
|
||||||
}
|
}
|
||||||
|
|
||||||
@[default_target]
|
@[default_target]
|
||||||
lean_exe pantograph {
|
lean_exe pantograph {
|
||||||
root := `Main
|
root := `Main
|
||||||
|
-- Somehow solves the native symbol not found problem
|
||||||
|
supportInterpreter := true
|
||||||
|
}
|
||||||
|
|
||||||
|
require LSpec from git
|
||||||
|
"https://github.com/lurk-lab/LSpec.git" @ "3388be5a1d1390594a74ec469fd54a5d84ff6114"
|
||||||
|
lean_lib Test {
|
||||||
|
}
|
||||||
|
lean_exe test {
|
||||||
|
root := `Test.Main
|
||||||
|
-- Somehow solves the native symbol not found problem
|
||||||
|
supportInterpreter := true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
leanprover/lean4:nightly-2023-05-06
|
leanprover/lean4:nightly-2024-03-27
|
||||||
|
|
Loading…
Reference in New Issue