-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathHelper.lean
More file actions
254 lines (233 loc) · 9.83 KB
/
Helper.lean
File metadata and controls
254 lines (233 loc) · 9.83 KB
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/-
Copyright (c) 2023-2024 Lean FRO LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: David Thrane Christiansen
-/
import SubVerso.Compat
import SubVerso.Examples.Env
import SubVerso.Helper
import SubVerso.Signature
/-!
This is a helper to be run as a subprocess from Verso, allowing it to request
elaboration/highlighting of various sorts.
It should primarily be communicated with via the API in `SubVerso.Helper`, specifically
`send`/`sendThe` and `recieve`/`receiveThe`—see module `VersoBlog.LiterateLeanPage` in Verso for
an example.
-/
open Lean Elab Frontend
open Lean.Elab.Command (liftTermElabM elabCommand CommandElabM)
open Lean.Elab.Term (TermElabM TermElabM.run elabType elabTerm)
open SubVerso Examples Helper
open SubVerso.Highlighting (Highlighted highlight)
abbrev HelperM := ReaderT Environment IO
/--
Removes the source info from syntax, to prevent unwanted InfoTree collisions.
-/
def removeSourceInfo (stx : Syntax) : Syntax :=
Id.run <| stx.replaceM fun
| .node _ k args => some (.node .none k args)
| .ident _ s x pre => some (.ident .none s x pre)
| .atom _ i => some (.atom .none i)
| .missing => some .missing
def handle (input output : IO.FS.Stream) : FrontendM Bool := do
let some (req : Request) ← receive input
| return false -- EOF between messages - terminate!
let resp ←
match req with
| .term code type? =>
try
let stx ←
match Parser.runParserCategory (← runCommandElabM getEnv) `term code with
| .error e => throw <| IO.userError s!"Parse error on term: {e}"
| .ok v => pure v
let tyStx? ← type?.mapM fun type => do
match Parser.runParserCategory (← runCommandElabM getEnv) `term type with
| .error e => throw <| IO.userError s!"Parse error on type: {e}"
| .ok v => pure v
setMessages {} -- don't persist messages from prior elaboration tasks
runCommandElabM <| withoutModifyingEnv <| Compat.commandWithoutAsync do
let infoState ← getInfoState
try
setInfoState {}
withEnableInfoTree true do
let cmd ←
if let some tyStx := tyStx? then `(#check ($(⟨stx⟩) : $(⟨removeSourceInfo tyStx⟩)))
else `(#check $(⟨stx⟩))
try
elabCommand cmd
catch
| e =>
return Response.error 4 (← e.toMessageData.toString) none
let trees := (← get).infoState.trees
let msgs := (← get).messages
if msgs.hasErrors then
return Response.error 5 "Elaboration failed" <| some <| .arr <|
(← msgs.toArray.filter (·.severity == .error) |>.mapM (Json.str <$> ·.toString))
let hl ← liftTermElabM <| do
-- No messages - those are confusing here
highlight stx #[] trees
pure <| Response.result <| .highlighted hl
finally
setInfoState infoState
catch
| IO.Error.userError e =>
pure <| Response.error 0 e none
| other => throw other
| .name code =>
try
let parser := Parser.ident
let fn := Parser.andthenFn Parser.whitespace parser.fn
let ictx := Parser.mkInputContext code "<input>"
let env ← runCommandElabM getEnv
let s := fn.run ictx { env, options := {} } (Parser.getTokenTable env) (Parser.mkParserState code)
let stx? :=
if s.hasError then
Except.error (s.toErrorMsg ictx)
else if Compat.String.atEnd code s.pos then
Except.ok (s.stxStack.back)
else
Except.error ((s.mkError "end of input").toErrorMsg ictx)
setMessages {} -- don't persist messages from prior elaboration tasks
runCommandElabM <| withoutModifyingEnv <| Compat.commandWithoutAsync do
let infoState ← getInfoState
try
let name ←
match stx? with
| .error e => throwError m!"Parse error on identifier: {e}"
| .ok v => pure v
setInfoState {}
withEnableInfoTree true do
try
Command.runTermElabM fun _ => Compat.realizeNameNoOverloads name
catch
| e =>
return Response.error 4 (← e.toMessageData.toString) none
let trees := (← get).infoState.trees
let msgs := (← get).messages
if msgs.hasErrors then
return Response.error 5 "Elaboration failed" <| some <| .arr <|
(← msgs.toArray.filter (·.severity == .error) |>.mapM (Json.str <$> ·.toString))
let hl ← liftTermElabM <| do
-- No messages - those are confusing here
highlight name #[] trees
pure <| Response.result <| .highlighted hl
finally
setInfoState infoState
catch
| IO.Error.userError e =>
pure <| Response.error 0 e none
| other => throw other
| .command code =>
try
let stx ←
match Parser.runParserCategory (← runCommandElabM getEnv) `command code with
| .error e => throw <| IO.userError s!"Parse error on command: {e}"
| .ok v => pure v
setMessages {} -- don't persist messages from prior elaboration tasks
runCommandElabM <| Compat.commandWithoutAsync do
let infoState ← getInfoState
try
setInfoState {}
withEnableInfoTree true do
try
elabCommand stx
catch
| e =>
return Response.error 6 (← e.toMessageData.toString) none
let trees := (← get).infoState.trees
let msgs := (← get).messages
if msgs.hasErrors then
return Response.error 7 "Command failed" <| some <| .arr <|
(← msgs.toArray.filter (·.severity == .error) |>.mapM (Json.str <$> ·.toString))
let hl ← liftTermElabM do
highlight stx msgs.toArray trees
pure <| Response.result <| .highlighted hl
finally
setInfoState infoState
catch
| IO.Error.userError e =>
pure <| Response.error 0 e none
| other => throw other
| .signature code =>
let parser := Parser.andthen Parser.Command.declId Parser.Command.declSig
let fn := Parser.andthenFn Parser.whitespace parser.fn
let ictx := Parser.mkInputContext code "<input>"
let env ← runCommandElabM getEnv
let s := fn.run ictx { env, options := {} } (Parser.getTokenTable env) (Parser.mkParserState code)
let stxs? :=
if s.hasError then
Except.error (s.toErrorMsg ictx)
else if Compat.String.atEnd code s.pos then
Except.ok (s.stxStack.pop.back, s.stxStack.back)
else
Except.error ((s.mkError "end of input").toErrorMsg ictx)
try
let (name, sig) ←
match stxs? with
| .error e => throw <| IO.userError s!"Parse error on command: {e}"
| .ok v => pure v
setMessages {} -- don't persist messages from prior elaboration tasks
runCommandElabM <| Compat.commandWithoutAsync do
let infoState ← getInfoState
try
setInfoState {}
withEnableInfoTree (m := CommandElabM) true do
let hl ←
try
let (hl, _, _, _) ← checkSignature ⟨name⟩ ⟨sig⟩
let msgs := (← get).messages
if msgs.hasErrors then
return Response.error 9 "Command failed" <| some <| .arr <|
(← msgs.toArray.filter (·.severity == .error) |>.mapM (Json.str <$> ·.toString))
pure hl
catch
| e =>
return Response.error 8 (← e.toMessageData.toString) none
return Response.result <| .highlighted hl
finally
setInfoState infoState
catch
| IO.Error.userError e =>
pure <| Response.error 0 e none
| other => throw other
| .exit =>
return false
send output resp
pure true
def serve (input output : IO.FS.Stream) : FrontendM UInt32 := do
repeat
if !(← handle input output) then break
return 0
unsafe def main : (args : List String) → IO UInt32
| [mod] => do
try
initSearchPath (← findSysroot)
let modName := mod.toName
let sp ← Compat.initSrcSearchPath
let sp : SearchPath := (sp : List System.FilePath) ++ [("." : System.FilePath)]
let fname ← do
if let some fname ← sp.findModuleWithExt "lean" modName then
pure fname
else
throw <| IO.userError s!"Failed to load {modName} from {sp}"
let ictx := Parser.mkInputContext (← IO.FS.readFile fname) fname.toString
let (headerStx, parserState, msgs) ← Parser.parseHeader ictx
let imports := headerToImports headerStx
enableInitializersExecution
let env ← Compat.importModules imports {}
let pctx : Context := {inputCtx := ictx}
let commandState : Command.State := { env, maxRecDepth := defaultMaxRecDepth, messages := msgs }
let scopes :=
let sc := commandState.scopes[0]!
{sc with opts := sc.opts.setBool `pp.tagAppFns true } :: commandState.scopes.tail!
let commandState := { commandState with scopes }
let cmdPos := parserState.pos
let cmdSt ← IO.mkRef {commandState, parserState, cmdPos}
processCommands pctx cmdSt
return (← (serve (← IO.getStdin) (← IO.getStdout)) pctx cmdSt)
catch e =>
IO.eprintln s!"error finding highlighted code: {toString e}"
return 2
| other => do
IO.eprintln s!"Didn't understand args: {other}"
pure 1