243 lines
8.9 KiB
Plaintext
243 lines
8.9 KiB
Plaintext
import Lean.Data.Json
|
||
import Lean.Environment
|
||
|
||
import Pantograph.Commands
|
||
import Pantograph.Serial
|
||
import Pantograph.Meta
|
||
import Pantograph.Symbols
|
||
|
||
namespace Pantograph
|
||
|
||
|
||
structure Context where
|
||
coreContext: Lean.Core.Context
|
||
|
||
/-- Stores state of the REPL -/
|
||
structure State where
|
||
environments: Array Lean.Environment := #[]
|
||
proofTrees: Array Meta.ProofTree := #[]
|
||
|
||
-- State monad
|
||
abbrev Subroutine := ReaderT Context (StateT State IO)
|
||
|
||
def State.getEnv (state: State) (id: Nat): Except String Lean.Environment :=
|
||
match state.environments.get? id with
|
||
| .some env => return env
|
||
| .none => throw s!"Invalid environment id {id}"
|
||
|
||
|
||
-- Utilities
|
||
def option_expect (o: Option α) (error: String): Except String α :=
|
||
match o with
|
||
| .some value => return value
|
||
| .none => throw error
|
||
|
||
|
||
open Commands
|
||
|
||
/-- Parse a command either in `{ "cmd": ..., "payload": ... }` form or `cmd { ... }` form. -/
|
||
def parse_command (s: String): Except String Command := do
|
||
let s := s.trim
|
||
match s.get? 0 with
|
||
| .some '{' => -- Parse in Json mode
|
||
Lean.fromJson? (← Lean.Json.parse s)
|
||
| .some _ => -- Parse in line mode
|
||
let offset := s.posOf ' ' |> s.offsetOfPos
|
||
if offset = s.length then
|
||
return { cmd := s.take offset, payload := Lean.Json.null }
|
||
else
|
||
let payload ← s.drop offset |> Lean.Json.parse
|
||
return { cmd := s.take offset, payload := payload }
|
||
| .none => throw "Command is empty"
|
||
|
||
|
||
|
||
unsafe def execute (command: Command): Subroutine Lean.Json := do
|
||
match command.cmd with
|
||
| "create" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => create args
|
||
| .error x => return errorJson x
|
||
| "catalog" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => catalog args
|
||
| .error x => return errorJson x
|
||
| "clear" =>
|
||
-- Delete all the environments
|
||
let ret ← clear
|
||
return Lean.toJson ret
|
||
| "inspect" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => inspect args
|
||
| .error x => return errorJson x
|
||
| "proof.start" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => proof_start args
|
||
| .error x => return errorJson x
|
||
| "proof.tactic" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => proof_tactic args
|
||
| .error x => return errorJson x
|
||
| "proof.printTree" =>
|
||
match Lean.fromJson? command.payload with
|
||
| .ok args => proof_print_tree args
|
||
| .error x => return errorJson x
|
||
| cmd =>
|
||
let error: InteractionError := { error := "unknown", desc := s!"Unknown command {cmd}" }
|
||
return Lean.toJson error
|
||
where
|
||
errorJson (s: String) := Lean.toJson ({ error := "json", desc := s }: InteractionError)
|
||
errorIndex (s: String) := Lean.toJson ({ error := "index", desc := s }: InteractionError)
|
||
create (args: Create): Subroutine Lean.Json := do
|
||
let state ← get
|
||
let envId := state.environments.size
|
||
let env ← Lean.importModules
|
||
(imports := args.imports.map (λ str => { module := str_to_name str, runtimeOnly := false }))
|
||
(opts := {})
|
||
(trustLevel := 1)
|
||
modify fun s => { environments := s.environments.push env }
|
||
let num_filtered_symbols := env.constants.fold (init := 0) (λ acc name info =>
|
||
acc + if is_symbol_unsafe_or_internal name info then 0 else 1)
|
||
return Lean.toJson ({
|
||
envId := envId,
|
||
symbols := env.constants.size,
|
||
filtered_symbols := num_filtered_symbols }: CreateResult)
|
||
catalog (args: Catalog): Subroutine Lean.Json := do
|
||
let state ← get
|
||
match state.getEnv args.envId with
|
||
| .error error => return Lean.toJson <| errorIndex error
|
||
| .ok env =>
|
||
let names := env.constants.fold (init := []) (λ es name info =>
|
||
match to_filtered_symbol name info with
|
||
| .some x => x::es
|
||
| .none => es)
|
||
return Lean.toJson <| ({ symbols := names }: CatalogResult)
|
||
clear: Subroutine Lean.Json := do
|
||
let state ← get
|
||
let nEnv := state.environments.size
|
||
for env in state.environments do
|
||
env.freeRegions
|
||
set ({ }: State)
|
||
return Lean.toJson ({ nEnv := nEnv }: ClearResult)
|
||
inspect (args: Inspect): Subroutine Lean.Json := do
|
||
let context ← read
|
||
let state ← get
|
||
match state.getEnv args.envId with
|
||
| .error error => return Lean.toJson <| errorIndex error
|
||
| .ok env =>
|
||
let name := str_to_name args.name
|
||
let info? := env.find? name
|
||
match info? with
|
||
| none => return Lean.toJson <| errorIndex s!"Symbol not found {args.name}"
|
||
| some info =>
|
||
let format ← Serial.expr_to_str
|
||
(env := env)
|
||
(coreContext := context.coreContext)
|
||
(expr := info.toConstantVal.type)
|
||
let module? := env.getModuleIdxFor? name >>=
|
||
(λ idx => env.allImportedModuleNames.get? idx.toNat) |>.map toString
|
||
return Lean.toJson ({
|
||
type := format,
|
||
module? := module?
|
||
}: InspectResult)
|
||
proof_start (args: ProofStart): Subroutine Lean.Json := do
|
||
let context ← read
|
||
let state ← get
|
||
let ret?: Except Lean.Json Meta.ProofTree ← ExceptT.run <| (do
|
||
let env ← match state.getEnv args.envId with
|
||
| .error error => throw <| Lean.toJson <| errorIndex error
|
||
| .ok env => pure env
|
||
let tree := Meta.createProofTree
|
||
(name := args.name.getD "Untitled")
|
||
(env := env)
|
||
(coreContext := context.coreContext)
|
||
let expr: Lean.Expr ← match args.expr, args.copyFrom with
|
||
| .some expr, .none =>
|
||
let syn ← match Serial.syntax_from_str env expr with
|
||
| .error str => throw <| Lean.toJson ({ error := "parsing", desc := str }: InteractionError)
|
||
| .ok syn => pure syn
|
||
let expr: Lean.Expr ← match (← Meta.ProofM.syntax_to_expr syn |>.run' tree) with
|
||
| .error str => throw <| Lean.toJson ({ error := "elab", desc := str }: InteractionError)
|
||
| .ok expr => pure expr
|
||
pure expr
|
||
| .none, .some copyFrom =>
|
||
match Serial.expr_from_const env (name := str_to_name copyFrom) with
|
||
| .error str =>
|
||
IO.println "Symbol not found"
|
||
throw <| errorIndex str
|
||
| .ok expr => pure expr
|
||
| .none, .none =>
|
||
throw <| Lean.toJson ({ error := "arguments", desc := "At least one of {expr, copyFrom} must be supplied" }: InteractionError)
|
||
| _, _ => throw <| Lean.toJson ({ error := "arguments", desc := "Cannot populate both of {expr, copyFrom}" }: InteractionError)
|
||
let (_, tree) := ← (Meta.ProofM.start expr |>.run tree)
|
||
return tree
|
||
)
|
||
match ret? with
|
||
| .error error => return error
|
||
| .ok tree =>
|
||
-- Put the new tree in the environment
|
||
let nextTreeId := state.proofTrees.size
|
||
set { state with proofTrees := state.proofTrees.push tree }
|
||
return Lean.toJson ({ treeId := nextTreeId }: ProofStartResult)
|
||
proof_tactic (args: ProofTactic): Subroutine Lean.Json := do
|
||
let state ← get
|
||
match state.proofTrees.get? args.treeId with
|
||
| .none => return Lean.toJson <| errorIndex "Invalid tree index {args.treeId}"
|
||
| .some tree =>
|
||
let (result, nextTree) ← Meta.ProofM.execute
|
||
(stateId := args.stateId)
|
||
(goalId := args.goalId.getD 0)
|
||
(tactic := args.tactic) |>.run tree
|
||
match result with
|
||
| .invalid message => return Lean.toJson <| errorIndex message
|
||
| .success nextId? goals =>
|
||
set { state with proofTrees := state.proofTrees.set! args.treeId nextTree }
|
||
return Lean.toJson ({ nextId? := nextId?, goals := goals }: ProofTacticResultSuccess)
|
||
| .failure messages =>
|
||
return Lean.toJson ({ errorMessages := messages }: ProofTacticResultFailure)
|
||
proof_print_tree (args: ProofPrintTree): Subroutine Lean.Json := do
|
||
let state ← get
|
||
match state.proofTrees.get? args.treeId with
|
||
| .none => return Lean.toJson <| errorIndex "Invalid tree index {args.treeId}"
|
||
| .some tree =>
|
||
let parents := tree.states.map λ state => match state.parent with
|
||
| .none => ""
|
||
| .some parent => s!"{parent}.{state.parentGoalId}"
|
||
return Lean.toJson ({parents := parents}: ProofPrintTreeResult)
|
||
|
||
|
||
end Pantograph
|
||
|
||
|
||
-- Main IO functions
|
||
open Pantograph
|
||
|
||
unsafe def loop : Subroutine Unit := do
|
||
let command ← (← IO.getStdin).getLine
|
||
if command.trim.length = 0 then return ()
|
||
match parse_command command with
|
||
| .error error =>
|
||
let error := Lean.toJson ({ error := "json", desc := error }: Commands.InteractionError)
|
||
IO.println (toString error)
|
||
| .ok command =>
|
||
let ret ← execute command
|
||
IO.println <| toString <| ret
|
||
loop
|
||
|
||
unsafe def main : IO Unit := do
|
||
Lean.enableInitializersExecution
|
||
Lean.initSearchPath (← Lean.findSysroot)
|
||
let context: Context := {
|
||
coreContext := {
|
||
currNamespace := str_to_name "Aniva",
|
||
openDecls := [], -- No 'open' directives needed
|
||
fileName := "<Pantograph>",
|
||
fileMap := { source := "", positions := #[0], lines := #[1] }
|
||
}
|
||
}
|
||
try
|
||
loop.run context |>.run' {}
|
||
catch ex =>
|
||
IO.println "Uncaught IO exception"
|
||
IO.println ex.toString
|