File size: 7,986 Bytes
0115dcf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import Pantograph.Environment
import Pantograph.Goal
import Pantograph.Protocol
import Pantograph.Delate

namespace Pantograph

open 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"

def runMetaM { Ξ± } (metaM: MetaM Ξ±): CoreM Ξ± :=
  metaM.run'

def errorI (type desc: String): Protocol.InteractionError := { error := type, desc := desc }

/-- Adds the given paths to Lean package search path. This must run with at
least an empty string, otherwise Lean will not be able to find any symbols -/
@[export pantograph_init_search]
unsafe def initSearch (sp: String := ""): IO Unit := do
  Lean.enableInitializersExecution
  Lean.initSearchPath (← Lean.findSysroot) (sp := System.SearchPath.parse sp)

/-- Creates a Core.Context object needed to run all monads -/
@[export pantograph_create_core_context]
def createCoreContext (options : Array String) : IO Core.Context := do
  let options? ← options.foldlM setOptionFromString' Options.empty |>.run
  let options ← match options? with
    | .ok options => pure options
    | .error e => throw $ IO.userError s!"Options cannot be parsed: {e}"
  return {
    currNamespace := `Cirno,
    openDecls := [],     -- No 'open' directives needed
    fileName := "<Pantograph>",
    fileMap := { source := "", positions := #[0] },
    options,
    maxRecDepth := maxRecDepth.get options,
  }

/-- Creates a Core.State object needed to run all monads -/
@[export pantograph_create_core_state]
def createCoreState (env : Environment) : IO Core.State := do
  return { env }

@[export pantograph_parse_elab_type_m]
def parseElabType (type : String) : Protocol.FallibleT Elab.TermElabM Expr := do
  let env ← MonadEnv.getEnv
  let syn ← match parseTerm env type with
    | .error str => Protocol.throw $ errorI "parse" str
    | .ok syn => pure syn
  match ← elabType syn with
  | .error str => Protocol.throw $ errorI "elab" str
  | .ok expr => return (← instantiateMVars expr)

/-- This must be a TermElabM since the parsed expr contains extra information -/
@[export pantograph_parse_elab_expr_m]
def parseElabExpr (expr : String) (expectedType? : Option String := .none) : Protocol.FallibleT Elab.TermElabM Expr := do
  let env ← MonadEnv.getEnv
  let expectedType? ← expectedType?.mapM parseElabType
  let syn ← match parseTerm env expr with
    | .error str => Protocol.throw $ errorI "parse" str
    | .ok syn => pure syn
  match ← elabTerm syn expectedType? with
  | .error str => Protocol.throw $ errorI "elab" str
  | .ok expr => return (← instantiateMVars expr)

@[export pantograph_expr_echo_m]
def exprEcho (expr: String) (expectedType?: Option String := .none) (options: @&Protocol.Options := {}):
    Protocol.FallibleT Elab.TermElabM Protocol.ExprEchoResult := do
  let expr ← parseElabExpr expr expectedType?
  try
    let type ← unfoldAuxLemmas (← Meta.inferType expr)
    return {
        type := (← serializeExpression options type),
        expr := (← serializeExpression options expr),
    }
  catch exception =>
    Protocol.throw $ errorI "typing" (← exception.toMessageData.toString)

@[export pantograph_goal_start_expr_m]
def goalStartExpr (expr: String) : Protocol.FallibleT Elab.TermElabM GoalState := do
  let t ← parseElabType expr
  Elab.Term.synthesizeSyntheticMVarsUsingDefault
  GoalState.create t

@[export pantograph_goal_serialize_m]
def goalSerialize (state: GoalState) (options: @&Protocol.Options): CoreM (Array Protocol.Goal) :=
  runMetaM <| state.serializeGoals options

@[export pantograph_goal_print_m]
def goalPrint (state: GoalState) (rootExpr: Bool) (parentExprs: Bool)
  (goals: Bool) (extraMVars : Array Name) (options: @&Protocol.Options)
  : CoreM Protocol.GoalPrintResult := runMetaM do
  state.restoreMetaM

  let rootExpr? := state.rootExpr?
  let root? ← if rootExpr then
      rootExpr?.mapM Ξ» expr => state.withRootContext do
        serializeExpression options (← instantiateAll expr)
    else
      pure .none
  let parentExprs? ← if parentExprs then
      .some <$> state.parentMVars.mapM Ξ» parent => parent.withContext do
        let val? := state.getMVarEAssignment parent
        val?.mapM Ξ» val => do
          serializeExpression options (← instantiateAll val)
    else
      pure .none
  let goals ← if goals then
      goalSerialize state options
    else
      pure #[]
  let extraMVars ← extraMVars.mapM Ξ» name => do
    let mvarId: MVarId := ⟨name⟩
    let .some _ ← mvarId.findDecl? | return {}
    state.withContext mvarId do
      let .some expr ← getExprMVarAssignment? mvarId | return {}
      serializeExpression options (← instantiateAll expr)
  let env ← getEnv
  return {
    root?,
    parentExprs?,
    goals,
    extraMVars,
    rootHasSorry := rootExpr?.map (Β·.hasSorry) |>.getD false,
    rootHasUnsafe := rootExpr?.map (env.hasUnsafe Β·) |>.getD false,
    rootHasMVar := rootExpr?.map (Β·.hasExprMVar) |>.getD false,
  }

@[export pantograph_goal_have_m]
protected def GoalState.tryHave (state: GoalState) (site : Site) (binderName: Name) (type: String): Elab.TermElabM TacticResult := do
  let type ← match (← parseTermM type) with
    | .ok syn => pure syn
    | .error error => return .parseError error
  state.restoreElabM
  state.tryTacticM site $ Tactic.evalHave binderName type
protected def GoalState.tryLet (state : GoalState) (site : Site) (binderName : Name) (type : String)
    : Elab.TermElabM TacticResult := do
  state.restoreElabM
  let type ← match Parser.runParserCategory
    (env := ← MonadEnv.getEnv)
    (catName := `term)
    (input := type)
    (fileName := ← getFileName) with
    | .ok syn => pure syn
    | .error error => return .parseError error
  state.tryTacticM site $ Tactic.evalLet binderName type
@[export pantograph_goal_try_define_m]
protected def GoalState.tryDefine (state: GoalState) (site : Site) (binderName: Name) (expr: String): Elab.TermElabM TacticResult := do
  let expr ← match (← parseTermM expr) with
    | .ok syn => pure syn
    | .error error => return .parseError error
  state.restoreElabM
  state.tryTacticM site $ Tactic.evalDefine binderName expr
@[export pantograph_goal_try_draft_m]
protected def GoalState.tryDraft (state: GoalState) (site : Site) (expr: String): Elab.TermElabM TacticResult := do
  let expr ← match (← parseTermM expr) with
    | .ok syn => pure syn
    | .error error => return .parseError error
  state.restoreElabM
  state.tryTacticM site $ Tactic.evalDraft expr

-- Cancel the token after a timeout.
@[export pantograph_run_cancel_token_with_timeout_m]
def runCancelTokenWithTimeout (cancelToken : IO.CancelToken) (timeout : UInt32) : IO Unit := do
  let _ ← EIO.asTask do
    IO.sleep timeout
    cancelToken.set
  return ()

def spawnCancelToken (timeout : UInt32) : IO IO.CancelToken := do
  let token ← IO.CancelToken.new
  runCancelTokenWithTimeout token timeout
  return token