-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMain.lean
More file actions
289 lines (249 loc) · 9.27 KB
/
Main.lean
File metadata and controls
289 lines (249 loc) · 9.27 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
import Lean
import Comparator
import Lean4Checker.Replay
import Export.Parse
namespace Comparator
structure Context where
projectDir : System.FilePath
challengeModule : Lean.Name
solutionModule : Lean.Name
theoremNames : Array Lean.Name
legalAxioms : Array Lean.Name
leanPrefix : System.FilePath
gitLocation : System.FilePath
enableNanoda : Bool
abbrev M := ReaderT Context IO
structure LandrunArgs where
cmd : String
args : Array String
envPass : Array String
envOverride : Array (String × Option String) := #[]
readablePaths : Array System.FilePath
writablePaths : Array System.FilePath
executablePaths : Array System.FilePath
@[inline]
def getTheoremNames : M (Array Lean.Name) := do return (← read).theoremNames
@[inline]
def getProjectDir : M System.FilePath := do return (← read).projectDir
@[inline]
def getChallengeModule : M Lean.Name := do return (← read).challengeModule
@[inline]
def getSolutionModule : M Lean.Name := do return (← read).solutionModule
@[inline]
def getLegalAxioms : M (Array Lean.Name) := do return (← read).legalAxioms
@[inline]
def getLeanPrefix : M System.FilePath := do return (← read).leanPrefix
@[inline]
def getGitLocation : M System.FilePath := do return (← read).gitLocation
@[inline]
def getNanodaEnabled : M Bool := do return (← read).enableNanoda
def queryGitLocation : IO System.FilePath := do
let out ← IO.Process.run {
cmd := "which",
args := #["git"],
stdout := .piped,
}
return out.trimAscii.toString
def queryLeanPrefix (projectDir : System.FilePath) : IO System.FilePath := do
let out ← IO.Process.run {
cmd := "lean",
args := #["--print-prefix"],
stdout := .piped,
cwd := projectDir
}
return out.trimAscii.toString
def buildLandrunArgs (spawnArgs : LandrunArgs) : Array String :=
let args := #["--best-effort", "--ro", "/", "--rw", "/dev", "-ldd", "-add-exec"]
let args := spawnArgs.envPass.foldl (init := args) (fun acc env => acc ++ #["--env", env])
let args := spawnArgs.readablePaths.foldl (init := args) (fun acc path => acc ++ #["--ro", path.toString])
let args := spawnArgs.writablePaths.foldl (init := args) (fun acc path => acc ++ #["--rwx", path.toString])
let args := spawnArgs.executablePaths.foldl (init := args) (fun acc path => acc ++ #["--rox", path.toString])
args ++ #[spawnArgs.cmd] ++ spawnArgs.args
def runSandBoxedWithStdout (spawnArgs : LandrunArgs) : M String := do
let args := buildLandrunArgs spawnArgs
IO.Process.run {
cmd := "landrun",
args,
stdout := .piped,
env := spawnArgs.envOverride
cwd := (← getProjectDir)
}
def runSandBoxed (spawnArgs : LandrunArgs) : M Unit := do
let args := buildLandrunArgs spawnArgs
let proc ← IO.Process.spawn {
cmd := "landrun",
args,
env := spawnArgs.envOverride
cwd := (← getProjectDir)
}
let ret ← proc.wait
if ret != 0 then
throw <| .userError s!"Child exited with {ret}"
def safeLakeBuild (target : Lean.Name) : M Unit := do
IO.println s!"Building {target}"
let leanPrefix ← getLeanPrefix
let projectDir ← getProjectDir
let dotLakeDir := projectDir / ".lake"
let gitLocation ← getGitLocation
if !(← System.FilePath.pathExists dotLakeDir) then
IO.FS.createDir dotLakeDir
runSandBoxed {
cmd := "lake",
args := #["build", target.toString (escape := false)],
envPass := #["PATH", "HOME", "LEAN_ABORT_ON_PANIC"]
envOverride := #[("LEAN_ABORT_ON_PANIC", some "1")]
readablePaths := #[projectDir]
writablePaths := #[dotLakeDir]
executablePaths := #[leanPrefix, gitLocation]
}
def safeExport (module : Lean.Name) (decls : Array Lean.Name) : M String := do
IO.println s!"Exporting {decls} from {module}"
let baseArgs := #[module.toString (escape := false), "--"]
let args := decls.foldl (·.push <| ·.toString (escape := false)) baseArgs
let leanPrefix ← getLeanPrefix
let projectDir ← getProjectDir
let dotLakeDir := projectDir / ".lake"
runSandBoxedWithStdout {
cmd := "lean4export",
args := args,
envPass := #["PATH", "HOME", "LEAN_PATH", "LEAN_ABORT_ON_PANIC"]
envOverride := #[("LEAN_ABORT_ON_PANIC", some "1")]
readablePaths := #[projectDir, dotLakeDir]
writablePaths := #[]
executablePaths := #[leanPrefix]
}
def runNanoda (solutionExport : String) : M Unit := do
IO.println "Running nanoda kernel on solution"
IO.FS.withTempFile fun config configPath => do
let legalAxioms ← getLegalAxioms
config.putStr <| Lean.Json.compress <| Lean.Json.mkObj [
("use_stdin", true),
("permitted_axioms", .arr <| legalAxioms.map (.str ∘ Lean.Name.toString)),
("unpermitted_axiom_hard_error", true),
("nat_extension", true),
("string_extension", true),
]
config.flush
let spawnArgs := {
cmd := "nanoda_bin",
args := #[configPath.toString],
envPass := #[]
readablePaths := #[configPath.toString]
writablePaths := #[]
executablePaths := #[]
}
let args := buildLandrunArgs spawnArgs
let proc ← IO.Process.spawn {
cmd := "landrun",
args,
stdin := .piped
env := spawnArgs.envOverride
cwd := (← getProjectDir)
}
let (nanodaStdin, proc) ← proc.takeStdin
nanodaStdin.putStr solutionExport
nanodaStdin.flush
let ret ← proc.wait
if ret != 0 then
throw <| .userError s!"Child exited with {ret}"
IO.println "Nanoda kernel accepts the solution"
def runKernel (solution : Export.ExportedEnv) : M Unit := do
IO.println "Running Lean default kernel on solution."
let mut env ← Lean.mkEmptyEnvironment
let mut constMap := solution.constMap
-- Lean's kernel interprets just the addition of `Quot as adding all of these so adding them
-- multiple times leads to errors.
constMap := constMap.erase `Quot.mk |>.erase `Quot.lift |>.erase `Quot.ind
discard <| env.replay' constMap
IO.println "Lean default kernel accepts the solution"
def primitiveTargets : M (Array Lean.Name) := do
-- The challenge needs to have all the built-in constants of the kernel, as the
-- kernel makes no guarantees when fed other definitions here.
-- List from `git grep new_persistent_expr_const src/kernel/`
return #[
-- ``Nat.zero,
-- ``Nat.succ,
``Nat.add,
``Nat.sub,
``Nat.mul,
``Nat.pow,
``Nat.gcd,
``Nat.div,
``Nat.mod,
``Nat.beq,
``Nat.ble,
``Nat.land,
``Nat.lor,
``Nat.xor,
``Nat.shiftLeft,
``Nat.shiftRight,
``String.ofList,
]
def builtinTargets : M (Array Lean.Name) := do
if ← getNanodaEnabled then
-- TODO: fix when nanoda fixes its string handling
let mut additional := #[``Nat, ``String, ``String.mk, ``Char, ``Char.ofNat, ``List]
if (← getLegalAxioms).contains ``Quot.sound then
additional := additional ++ #[``Quot, ``Quot.mk, ``Quot.lift, ``Quot.ind]
return additional
else
return #[]
def stringStream (s : String) : BaseIO IO.FS.Stream := do
let ref ← IO.mkRef {
data := s.toByteArray
}
return IO.FS.Stream.ofBuffer ref
def verifyMatch (challengeExport : String) (solutionExport : String) :
M Unit := do
let challenge ← Export.parseStream (← stringStream challengeExport)
let solution ← Export.parseStream (← stringStream solutionExport)
let theoremNames ← getTheoremNames
let targets := (← getTheoremNames) ++ (← getLegalAxioms)
IO.ofExcept <| Comparator.compareAt challenge solution targets (← primitiveTargets)
IO.ofExcept <| Comparator.checkAxioms solution theoremNames (← getLegalAxioms)
if ← getNanodaEnabled then
runNanoda solutionExport
runKernel solution
def compareIt : M Unit := do
let challengeModule ← getChallengeModule
let exportTargets := (← builtinTargets) ++ (← getTheoremNames) ++ (← getLegalAxioms) ++ (← primitiveTargets)
safeLakeBuild challengeModule
let challengeExport ← safeExport challengeModule exportTargets
let solutionModule ← getSolutionModule
safeLakeBuild solutionModule
let solutionExport ← safeExport solutionModule exportTargets
verifyMatch challengeExport solutionExport
IO.println "Your solution is okay!"
structure Config where
challenge_module : String
solution_module : String
theorem_names : Array String
permitted_axioms : Array String
enable_nanoda : Bool
deriving Lean.FromJson, Lean.ToJson, Repr
def M.run (x : M α) (cfg : Config) : IO α := do
let cwd ← IO.Process.getCurrentDir
let leanPrefix ← queryLeanPrefix cwd
let gitLocation ← queryGitLocation
ReaderT.run x {
projectDir := cwd
challengeModule := cfg.challenge_module.toName,
solutionModule := cfg.solution_module.toName,
theoremNames := cfg.theorem_names.map String.toName,
legalAxioms := cfg.permitted_axioms.map String.toName,
leanPrefix := leanPrefix,
gitLocation := gitLocation,
enableNanoda := cfg.enable_nanoda
}
end Comparator
def main (args : List String) : IO Unit := do
let some (configPath : String) := args[0]?
| throw <| .userError "Expected config file path as first argument."
let content ← IO.FS.readFile configPath
let config ← IO.ofExcept <| Lean.FromJson.fromJson? <| ← IO.ofExcept <| Lean.Json.parse content
Comparator.M.run Comparator.compareIt config