lean-interact 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lean_interact-0.3.2 → lean_interact-0.4.0}/PKG-INFO +249 -75
- {lean_interact-0.3.2 → lean_interact-0.4.0}/README.md +248 -74
- {lean_interact-0.3.2 → lean_interact-0.4.0}/examples/beq_plus.py +1 -1
- {lean_interact-0.3.2 → lean_interact-0.4.0}/pyproject.toml +1 -1
- {lean_interact-0.3.2 → lean_interact-0.4.0}/src/lean_interact/interface.py +106 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/src/lean_interact/server.py +4 -4
- {lean_interact-0.3.2 → lean_interact-0.4.0}/src/lean_interact/utils.py +1 -1
- {lean_interact-0.3.2 → lean_interact-0.4.0}/tests/test_server.py +18 -15
- {lean_interact-0.3.2 → lean_interact-0.4.0}/.devcontainer/devcontainer.json +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/.github/workflows/ci.yml +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/.gitignore +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/LICENSE +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/examples/proof_generation_and_autoformalization.py +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/examples/type_check.py +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/src/lean_interact/__init__.py +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/src/lean_interact/config.py +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/tests/test_utils.py +0 -0
- {lean_interact-0.3.2 → lean_interact-0.4.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-interact
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: LeanInteract is a Python package that allows you to interact with the Lean theorem prover.
|
|
5
5
|
Author-email: Auguste Poiroux <auguste.poiroux@epfl.ch>
|
|
6
6
|
License: MIT License
|
|
@@ -37,16 +37,50 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
|
|
38
38
|
# LeanInteract
|
|
39
39
|
|
|
40
|
+
[](https://badge.fury.io/py/lean-interact)
|
|
41
|
+
[](https://www.python.org/downloads/)
|
|
42
|
+
[](https://opensource.org/licenses/MIT)
|
|
43
|
+
|
|
40
44
|
**LeanInteract** is a Python package designed to seamlessly interact with Lean 4 through the [Lean REPL](https://github.com/leanprover-community/repl).
|
|
41
45
|
|
|
42
46
|
## Key Features
|
|
43
47
|
|
|
44
48
|
- **🔗 Interactivity**: Execute Lean code and files directly from Python.
|
|
45
49
|
- **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
|
|
46
|
-
- **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.
|
|
50
|
+
- **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.19.0-rc2`.
|
|
51
|
+
- We backport the latest features of Lean REPL to older versions of Lean.
|
|
47
52
|
- **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
|
|
48
53
|
- Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
|
|
49
54
|
|
|
55
|
+
## Table of Contents
|
|
56
|
+
|
|
57
|
+
- [Key Features](#key-features)
|
|
58
|
+
- [Installation and Setup](#installation-and-setup)
|
|
59
|
+
- [Script examples](#script-examples)
|
|
60
|
+
- [Usage](#usage)
|
|
61
|
+
- [Basic example](#basic-example)
|
|
62
|
+
- [Tactic mode](#tactic-mode)
|
|
63
|
+
- [Custom Lean configuration](#custom-lean-configuration)
|
|
64
|
+
- [Specific Lean version](#specific-lean-version)
|
|
65
|
+
- [Existing Lean projects](#existing-lean-projects)
|
|
66
|
+
- [Temporary project with dependencies](#temporary-project-with-dependencies)
|
|
67
|
+
- [Fine-grained temporary project](#fine-grained-temporary-project)
|
|
68
|
+
- [Available Queries](#available-queries)
|
|
69
|
+
- [Command](#command)
|
|
70
|
+
- [FileCommand](#filecommand)
|
|
71
|
+
- [ProofStep](#proofstep)
|
|
72
|
+
- [Environment Pickling](#environment-pickling)
|
|
73
|
+
- [ProofState Pickling](#proofstate-pickling)
|
|
74
|
+
- [Helper Commands](#helper-commands)
|
|
75
|
+
- [Advanced options](#advanced-options)
|
|
76
|
+
- [LeanServer](#leanserver)
|
|
77
|
+
- [Custom Lean REPL](#custom-lean-repl)
|
|
78
|
+
- [Similar tools](#similar-tools)
|
|
79
|
+
- [Troubleshooting](#troubleshooting)
|
|
80
|
+
- [Contributing](#contributing)
|
|
81
|
+
- [Citation](#citation)
|
|
82
|
+
- [License](#license)
|
|
83
|
+
|
|
50
84
|
## Installation and Setup
|
|
51
85
|
|
|
52
86
|
You can install the LeanInteract package using the following command:
|
|
@@ -59,11 +93,7 @@ Requirements:
|
|
|
59
93
|
|
|
60
94
|
- Python >= 3.10
|
|
61
95
|
- git
|
|
62
|
-
- [Lean 4](https://leanprover-community.github.io/get_started.html) (
|
|
63
|
-
|
|
64
|
-
> [!NOTE]
|
|
65
|
-
> This tool is still experimental and has been primarily tested on Linux. Compatibility with macOS is not guaranteed. For Windows, use WSL.
|
|
66
|
-
> Please report any issues you encounter.
|
|
96
|
+
- [Lean 4](https://leanprover-community.github.io/get_started.html) (Tip: use the `install-lean` command from LeanInteract)
|
|
67
97
|
|
|
68
98
|
## Script examples
|
|
69
99
|
|
|
@@ -72,11 +102,12 @@ In the `examples` directory, you will find a few scripts demonstrating how to us
|
|
|
72
102
|
- `proof_generation_and_autoformalization.py`: use [DeepSeek-Prover-V1.5](https://arxiv.org/abs/2408.08152), [Goedel-Prover](https://goedel-lm.github.io/), and other models on [MiniF2F](https://github.com/yangky11/miniF2F-lean4) and [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) benchmarks.
|
|
73
103
|
- `beq_plus.py`: run the autoformalization [BEq+](https://arxiv.org/abs/2406.07222) metric on the [ProofNetVerif](https://huggingface.co/datasets/PAug/ProofNetVerif) benchmark.
|
|
74
104
|
- `type_check.py`: optimize type checking using environment states.
|
|
75
|
-
- `statement_autoformalization_sampling.py`: sampling-based method used in [Improving Autoformalization using Type Checking](https://arxiv.org/abs/2406.07222).
|
|
76
105
|
|
|
77
106
|
## Usage
|
|
78
107
|
|
|
79
|
-
###
|
|
108
|
+
### Basic example
|
|
109
|
+
|
|
110
|
+
The following code will use the default Lean version (latest available):
|
|
80
111
|
|
|
81
112
|
```python
|
|
82
113
|
from lean_interact import LeanREPLConfig, LeanServer, Command
|
|
@@ -90,7 +121,15 @@ server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
|
|
|
90
121
|
<summary>Output</summary>
|
|
91
122
|
|
|
92
123
|
```python
|
|
93
|
-
CommandResponse(
|
|
124
|
+
CommandResponse(
|
|
125
|
+
env=0,
|
|
126
|
+
messages=[
|
|
127
|
+
Message(start_pos=Pos(line=1, column=0),
|
|
128
|
+
end_pos=Pos(line=1, column=42),
|
|
129
|
+
data='Goals accomplished!',
|
|
130
|
+
severity='info')
|
|
131
|
+
]
|
|
132
|
+
)
|
|
94
133
|
```
|
|
95
134
|
|
|
96
135
|
</details>
|
|
@@ -105,21 +144,116 @@ server.run(Command(cmd="example (x : Nat) : x = 5 → x = 5 := by exact ex x", e
|
|
|
105
144
|
<summary>Output</summary>
|
|
106
145
|
|
|
107
146
|
```python
|
|
108
|
-
CommandResponse(
|
|
147
|
+
CommandResponse(
|
|
148
|
+
env=1,
|
|
149
|
+
messages=[
|
|
150
|
+
Message(start_pos=Pos(line=1, column=0),
|
|
151
|
+
end_pos=Pos(line=1, column=50),
|
|
152
|
+
data='Goals accomplished!',
|
|
153
|
+
severity='info')
|
|
154
|
+
]
|
|
155
|
+
)
|
|
109
156
|
```
|
|
110
157
|
|
|
111
158
|
</details>
|
|
112
159
|
|
|
160
|
+
See [Available Queries](#available-queries) for all available commands.
|
|
161
|
+
|
|
113
162
|
> [!NOTE]
|
|
114
163
|
> The initial invocation of `LeanREPLConfig` might take some time as it downloads and builds Lean REPL. Future executions with identical parameters will be significantly quicker due to caching.
|
|
115
164
|
|
|
116
|
-
###
|
|
165
|
+
### Tactic mode
|
|
166
|
+
|
|
167
|
+
> [!WARNING]
|
|
168
|
+
> This feature is experimental in Lean REPL and may not work as expected: some valid proofs might be incorrectly rejected. Please report any issues you encounter [here](https://github.com/leanprover-community/repl/issues).
|
|
169
|
+
|
|
170
|
+
First, let's run a command to create a theorem with a `sorry` proof:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := sorry"))
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
<details>
|
|
177
|
+
<summary>Output</summary>
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
CommandResponse(
|
|
181
|
+
sorries=[
|
|
182
|
+
Sorry(start_pos=Pos(line=1, column=40),
|
|
183
|
+
end_pos=Pos(line=1, column=45),
|
|
184
|
+
goal='n : Nat\n⊢ n = 5 → n = 5',
|
|
185
|
+
proof_state=0)
|
|
186
|
+
],
|
|
187
|
+
env=0,
|
|
188
|
+
messages=[
|
|
189
|
+
Message(start_pos=Pos(line=1, column=8),
|
|
190
|
+
end_pos=Pos(line=1, column=10),
|
|
191
|
+
data="declaration uses 'sorry'",
|
|
192
|
+
severity='warning')
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
</details>
|
|
198
|
+
|
|
199
|
+
You can then iterate on the proof state by executing tactics:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from lean_interact import ProofStep
|
|
203
|
+
|
|
204
|
+
server.run(ProofStep(tactic="intro h", proof_state=0))
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
<details>
|
|
208
|
+
<summary>Output</summary>
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
ProofStepResponse(
|
|
212
|
+
proof_state=1,
|
|
213
|
+
goals=['n : Nat\nh : n = 5\n⊢ n = 5'],
|
|
214
|
+
proof_status='Incomplete: open goals remain'
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
</details>
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
server.run(ProofStep(tactic="exact h", proof_state=1))
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
<details>
|
|
225
|
+
<summary>Output</summary>
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
ProofStepResponse(proof_state=2, goals=[], proof_status='Completed')
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
or by directly running the entire proof:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
server.run(ProofStep(tactic="(\nintro h\nexact h)", proof_state=0))
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
<details>
|
|
240
|
+
<summary>Output</summary>
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
ProofStepResponse(proof_state=3, goals=[], proof_status='Completed')
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
</details>
|
|
247
|
+
|
|
248
|
+
### Custom Lean configuration
|
|
249
|
+
|
|
250
|
+
#### Specific Lean version
|
|
117
251
|
|
|
118
252
|
```python
|
|
119
253
|
config = LeanREPLConfig(lean_version="v4.7.0")
|
|
120
254
|
```
|
|
121
255
|
|
|
122
|
-
|
|
256
|
+
#### Existing Lean projects
|
|
123
257
|
|
|
124
258
|
```python
|
|
125
259
|
config = LeanREPLConfig(project=LocalProject("path/to/your/project"))
|
|
@@ -143,7 +277,7 @@ server.run(FileCommand(path="file.lean"))
|
|
|
143
277
|
> [!IMPORTANT]
|
|
144
278
|
> Ensure the project can be *successfully* built with `lake build` before using LeanInteract.
|
|
145
279
|
|
|
146
|
-
|
|
280
|
+
#### Temporary project with dependencies
|
|
147
281
|
|
|
148
282
|
```python
|
|
149
283
|
from lean_interact import TempRequireProject
|
|
@@ -175,21 +309,19 @@ theorem ex_mathlib (x : ℝ) (y : ℚ) :
|
|
|
175
309
|
|
|
176
310
|
```python
|
|
177
311
|
CommandResponse(
|
|
178
|
-
|
|
179
|
-
Message(
|
|
180
|
-
start_pos=Pos(line=2, column=8),
|
|
181
|
-
end_pos=Pos(line=2, column=18),
|
|
182
|
-
data="declaration uses 'sorry'",
|
|
183
|
-
severity='warning'
|
|
184
|
-
)],
|
|
312
|
+
env=0,
|
|
185
313
|
sorries=[
|
|
186
|
-
Sorry(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
314
|
+
Sorry(end_pos=Pos(line=3, column=51),
|
|
315
|
+
goal='x : ℝ\ny : ℚ\n⊢ Irrational x → Irrational (x + ↑y)',
|
|
316
|
+
start_pos=Pos(line=3, column=46),
|
|
317
|
+
proof_state=0)
|
|
318
|
+
],
|
|
319
|
+
messages=[
|
|
320
|
+
Message(end_pos=Pos(line=2, column=18),
|
|
321
|
+
data="declaration uses 'sorry'",
|
|
322
|
+
start_pos=Pos(line=2, column=8),
|
|
323
|
+
severity='warning')
|
|
324
|
+
]
|
|
193
325
|
)
|
|
194
326
|
```
|
|
195
327
|
|
|
@@ -200,7 +332,7 @@ CommandResponse(
|
|
|
200
332
|
> - Mathlib is a large library and may take some time to download and build.
|
|
201
333
|
> - A separate cache is used for each unique set of dependencies.
|
|
202
334
|
|
|
203
|
-
|
|
335
|
+
#### Fine-grained temporary project
|
|
204
336
|
|
|
205
337
|
For more control over the temporary project, you can use `TemporaryProject` to specify the content of the lakefile.
|
|
206
338
|
|
|
@@ -223,79 +355,82 @@ require mathlib from git
|
|
|
223
355
|
"""))
|
|
224
356
|
```
|
|
225
357
|
|
|
226
|
-
|
|
358
|
+
## Available Queries
|
|
227
359
|
|
|
228
|
-
|
|
229
|
-
server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := sorry"))
|
|
230
|
-
```
|
|
360
|
+
LeanInteract supports various types of queries to interact with the Lean REPL. We briefly describe them in this section. You can check the file [interface.py](src/lean_interact/interface.py) for more details.
|
|
231
361
|
|
|
232
|
-
|
|
233
|
-
|
|
362
|
+
### Command
|
|
363
|
+
|
|
364
|
+
Execute Lean code directly:
|
|
234
365
|
|
|
235
366
|
```python
|
|
236
|
-
|
|
237
|
-
messages=[
|
|
238
|
-
Message(
|
|
239
|
-
start_pos=Pos(line=1, column=8),
|
|
240
|
-
end_pos=Pos(line=1, column=10),
|
|
241
|
-
data="declaration uses 'sorry'",
|
|
242
|
-
severity='warning'
|
|
243
|
-
)],
|
|
244
|
-
sorries=[
|
|
245
|
-
Sorry(
|
|
246
|
-
start_pos=Pos(line=1, column=40),
|
|
247
|
-
end_pos=Pos(line=1, column=45),
|
|
248
|
-
goal='n : Nat\n⊢ n = 5 → n = 5',
|
|
249
|
-
proof_state=0
|
|
250
|
-
)],
|
|
251
|
-
env=0
|
|
252
|
-
)
|
|
253
|
-
```
|
|
367
|
+
from lean_interact import Command
|
|
254
368
|
|
|
255
|
-
|
|
369
|
+
# Execute a simple theorem
|
|
370
|
+
response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
|
|
256
371
|
|
|
257
|
-
|
|
372
|
+
# Execute with options to get tactics information
|
|
373
|
+
response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := by simp", all_tactics=True))
|
|
258
374
|
|
|
259
|
-
|
|
260
|
-
server.run(
|
|
375
|
+
# Continue in the same environment
|
|
376
|
+
response = server.run(Command(cmd="#check ex", env=response.env))
|
|
261
377
|
```
|
|
262
378
|
|
|
263
|
-
|
|
264
|
-
|
|
379
|
+
### FileCommand
|
|
380
|
+
|
|
381
|
+
Process Lean files:
|
|
265
382
|
|
|
266
383
|
```python
|
|
267
|
-
|
|
268
|
-
```
|
|
384
|
+
from lean_interact import FileCommand
|
|
269
385
|
|
|
270
|
-
|
|
386
|
+
# Execute a Lean file
|
|
387
|
+
response = server.run(FileCommand(path="myfile.lean"))
|
|
271
388
|
|
|
272
|
-
|
|
273
|
-
server.run(
|
|
389
|
+
# With options for more information
|
|
390
|
+
response = server.run(FileCommand(path="myfile.lean", root_goals=True))
|
|
274
391
|
```
|
|
275
392
|
|
|
276
|
-
|
|
277
|
-
|
|
393
|
+
### ProofStep
|
|
394
|
+
|
|
395
|
+
Work with proof tactics step by step:
|
|
278
396
|
|
|
279
397
|
```python
|
|
280
|
-
|
|
398
|
+
from lean_interact import ProofStep
|
|
399
|
+
|
|
400
|
+
# Apply a tactic to a proof state
|
|
401
|
+
response = server.run(ProofStep(proof_state=0, tactic="intro h"))
|
|
402
|
+
|
|
403
|
+
# Apply multiple tactics at once
|
|
404
|
+
response = server.run(ProofStep(proof_state=0, tactic="intro h\nexact h"))
|
|
281
405
|
```
|
|
282
406
|
|
|
283
|
-
|
|
407
|
+
### Environment Pickling
|
|
284
408
|
|
|
285
|
-
|
|
409
|
+
Save and restore environment states:
|
|
286
410
|
|
|
287
411
|
```python
|
|
288
|
-
|
|
412
|
+
from lean_interact import PickleEnvironment, UnpickleEnvironment
|
|
413
|
+
|
|
414
|
+
# Save an environment
|
|
415
|
+
server.run(PickleEnvironment(env=1, pickle_to="env_state.olean"))
|
|
416
|
+
|
|
417
|
+
# Restore an environment
|
|
418
|
+
server.run(UnpickleEnvironment(unpickle_env_from="env_state.olean"))
|
|
289
419
|
```
|
|
290
420
|
|
|
291
|
-
|
|
292
|
-
|
|
421
|
+
### ProofState Pickling
|
|
422
|
+
|
|
423
|
+
Save and restore proof states:
|
|
293
424
|
|
|
294
425
|
```python
|
|
295
|
-
|
|
296
|
-
```
|
|
426
|
+
from lean_interact import PickleProofState, UnpickleProofState
|
|
297
427
|
|
|
298
|
-
|
|
428
|
+
# Save a proof state
|
|
429
|
+
server.run(PickleProofState(proof_state=2, pickle_to="proof_state.olean"))
|
|
430
|
+
|
|
431
|
+
# Restore a proof state
|
|
432
|
+
server.run(UnpickleProofState(unpickle_proof_state_from="proof_state.olean", env=1))
|
|
433
|
+
```
|
|
299
434
|
|
|
300
435
|
## Helper Commands
|
|
301
436
|
|
|
@@ -333,3 +468,42 @@ We recommend checking out these tools:
|
|
|
333
468
|
- **[leanclient](https://github.com/oOo0oOo/leanclient)**: Interact with the Lean LSP server.
|
|
334
469
|
|
|
335
470
|
LeanInteract is inspired by **[pylean](https://github.com/zhangir-azerbayev/repl)** and **[lean4_jupyter](https://github.com/utensil/lean4_jupyter)**.
|
|
471
|
+
|
|
472
|
+
## Troubleshooting
|
|
473
|
+
|
|
474
|
+
Common issues and their solutions:
|
|
475
|
+
|
|
476
|
+
1. **Out of memory errors**: Reduce parallel processing or increase system memory. Alternatively, use `AutoLeanServer` with conservative memory settings
|
|
477
|
+
|
|
478
|
+
2. **Timeout errors**: Currently, `LeanServer` simply stops the Lean REPL if a command times out. Use `AutoLeanServer` for automatic recovery.
|
|
479
|
+
|
|
480
|
+
3. **Long waiting times during first run**: This is expected as Lean REPL is being downloaded and built. Additionally, if you are importing Mathlib it will take even more time. Subsequent runs will be much faster.
|
|
481
|
+
|
|
482
|
+
## Contributing
|
|
483
|
+
|
|
484
|
+
Contributions are welcome! Here's how you can help:
|
|
485
|
+
|
|
486
|
+
1. Fork the repository
|
|
487
|
+
2. Create a feature branch: `git checkout -b feature-name`
|
|
488
|
+
3. Commit your changes: `git commit -am 'Add new feature'`
|
|
489
|
+
4. Push to the branch: `git push origin feature-name`
|
|
490
|
+
5. Submit a pull request
|
|
491
|
+
|
|
492
|
+
Please ensure your code includes appropriate tests.
|
|
493
|
+
|
|
494
|
+
## Citation
|
|
495
|
+
|
|
496
|
+
If you use LeanInteract in your research, please cite it as follows:
|
|
497
|
+
|
|
498
|
+
```bibtex
|
|
499
|
+
@software{leaninteract,
|
|
500
|
+
author = {Poiroux, Auguste},
|
|
501
|
+
title = {LeanInteract: A Python Interface for Lean 4},
|
|
502
|
+
url = {https://github.com/augustepoiroux/lean-interact},
|
|
503
|
+
year = {2025}
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## License
|
|
508
|
+
|
|
509
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
# LeanInteract
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/py/lean-interact)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
**LeanInteract** is a Python package designed to seamlessly interact with Lean 4 through the [Lean REPL](https://github.com/leanprover-community/repl).
|
|
4
8
|
|
|
5
9
|
## Key Features
|
|
6
10
|
|
|
7
11
|
- **🔗 Interactivity**: Execute Lean code and files directly from Python.
|
|
8
12
|
- **🚀 Ease of Use**: LeanInteract abstracts the complexities of Lean setup and interaction.
|
|
9
|
-
- **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.
|
|
13
|
+
- **🔧 Compatibility**: Supports all Lean versions between `v4.7.0-rc1` and `v4.19.0-rc2`.
|
|
14
|
+
- We backport the latest features of Lean REPL to older versions of Lean.
|
|
10
15
|
- **📦 Temporary Projects**: Easily instantiate temporary Lean environments.
|
|
11
16
|
- Useful for experimenting with benchmarks depending on [Mathlib](https://github.com/leanprover-community/mathlib4) like [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) and [MiniF2F](https://github.com/yangky11/miniF2F-lean4).
|
|
12
17
|
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Key Features](#key-features)
|
|
21
|
+
- [Installation and Setup](#installation-and-setup)
|
|
22
|
+
- [Script examples](#script-examples)
|
|
23
|
+
- [Usage](#usage)
|
|
24
|
+
- [Basic example](#basic-example)
|
|
25
|
+
- [Tactic mode](#tactic-mode)
|
|
26
|
+
- [Custom Lean configuration](#custom-lean-configuration)
|
|
27
|
+
- [Specific Lean version](#specific-lean-version)
|
|
28
|
+
- [Existing Lean projects](#existing-lean-projects)
|
|
29
|
+
- [Temporary project with dependencies](#temporary-project-with-dependencies)
|
|
30
|
+
- [Fine-grained temporary project](#fine-grained-temporary-project)
|
|
31
|
+
- [Available Queries](#available-queries)
|
|
32
|
+
- [Command](#command)
|
|
33
|
+
- [FileCommand](#filecommand)
|
|
34
|
+
- [ProofStep](#proofstep)
|
|
35
|
+
- [Environment Pickling](#environment-pickling)
|
|
36
|
+
- [ProofState Pickling](#proofstate-pickling)
|
|
37
|
+
- [Helper Commands](#helper-commands)
|
|
38
|
+
- [Advanced options](#advanced-options)
|
|
39
|
+
- [LeanServer](#leanserver)
|
|
40
|
+
- [Custom Lean REPL](#custom-lean-repl)
|
|
41
|
+
- [Similar tools](#similar-tools)
|
|
42
|
+
- [Troubleshooting](#troubleshooting)
|
|
43
|
+
- [Contributing](#contributing)
|
|
44
|
+
- [Citation](#citation)
|
|
45
|
+
- [License](#license)
|
|
46
|
+
|
|
13
47
|
## Installation and Setup
|
|
14
48
|
|
|
15
49
|
You can install the LeanInteract package using the following command:
|
|
@@ -22,11 +56,7 @@ Requirements:
|
|
|
22
56
|
|
|
23
57
|
- Python >= 3.10
|
|
24
58
|
- git
|
|
25
|
-
- [Lean 4](https://leanprover-community.github.io/get_started.html) (
|
|
26
|
-
|
|
27
|
-
> [!NOTE]
|
|
28
|
-
> This tool is still experimental and has been primarily tested on Linux. Compatibility with macOS is not guaranteed. For Windows, use WSL.
|
|
29
|
-
> Please report any issues you encounter.
|
|
59
|
+
- [Lean 4](https://leanprover-community.github.io/get_started.html) (Tip: use the `install-lean` command from LeanInteract)
|
|
30
60
|
|
|
31
61
|
## Script examples
|
|
32
62
|
|
|
@@ -35,11 +65,12 @@ In the `examples` directory, you will find a few scripts demonstrating how to us
|
|
|
35
65
|
- `proof_generation_and_autoformalization.py`: use [DeepSeek-Prover-V1.5](https://arxiv.org/abs/2408.08152), [Goedel-Prover](https://goedel-lm.github.io/), and other models on [MiniF2F](https://github.com/yangky11/miniF2F-lean4) and [ProofNet#](https://huggingface.co/datasets/PAug/ProofNetSharp) benchmarks.
|
|
36
66
|
- `beq_plus.py`: run the autoformalization [BEq+](https://arxiv.org/abs/2406.07222) metric on the [ProofNetVerif](https://huggingface.co/datasets/PAug/ProofNetVerif) benchmark.
|
|
37
67
|
- `type_check.py`: optimize type checking using environment states.
|
|
38
|
-
- `statement_autoformalization_sampling.py`: sampling-based method used in [Improving Autoformalization using Type Checking](https://arxiv.org/abs/2406.07222).
|
|
39
68
|
|
|
40
69
|
## Usage
|
|
41
70
|
|
|
42
|
-
###
|
|
71
|
+
### Basic example
|
|
72
|
+
|
|
73
|
+
The following code will use the default Lean version (latest available):
|
|
43
74
|
|
|
44
75
|
```python
|
|
45
76
|
from lean_interact import LeanREPLConfig, LeanServer, Command
|
|
@@ -53,7 +84,15 @@ server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
|
|
|
53
84
|
<summary>Output</summary>
|
|
54
85
|
|
|
55
86
|
```python
|
|
56
|
-
CommandResponse(
|
|
87
|
+
CommandResponse(
|
|
88
|
+
env=0,
|
|
89
|
+
messages=[
|
|
90
|
+
Message(start_pos=Pos(line=1, column=0),
|
|
91
|
+
end_pos=Pos(line=1, column=42),
|
|
92
|
+
data='Goals accomplished!',
|
|
93
|
+
severity='info')
|
|
94
|
+
]
|
|
95
|
+
)
|
|
57
96
|
```
|
|
58
97
|
|
|
59
98
|
</details>
|
|
@@ -68,21 +107,116 @@ server.run(Command(cmd="example (x : Nat) : x = 5 → x = 5 := by exact ex x", e
|
|
|
68
107
|
<summary>Output</summary>
|
|
69
108
|
|
|
70
109
|
```python
|
|
71
|
-
CommandResponse(
|
|
110
|
+
CommandResponse(
|
|
111
|
+
env=1,
|
|
112
|
+
messages=[
|
|
113
|
+
Message(start_pos=Pos(line=1, column=0),
|
|
114
|
+
end_pos=Pos(line=1, column=50),
|
|
115
|
+
data='Goals accomplished!',
|
|
116
|
+
severity='info')
|
|
117
|
+
]
|
|
118
|
+
)
|
|
72
119
|
```
|
|
73
120
|
|
|
74
121
|
</details>
|
|
75
122
|
|
|
123
|
+
See [Available Queries](#available-queries) for all available commands.
|
|
124
|
+
|
|
76
125
|
> [!NOTE]
|
|
77
126
|
> The initial invocation of `LeanREPLConfig` might take some time as it downloads and builds Lean REPL. Future executions with identical parameters will be significantly quicker due to caching.
|
|
78
127
|
|
|
79
|
-
###
|
|
128
|
+
### Tactic mode
|
|
129
|
+
|
|
130
|
+
> [!WARNING]
|
|
131
|
+
> This feature is experimental in Lean REPL and may not work as expected: some valid proofs might be incorrectly rejected. Please report any issues you encounter [here](https://github.com/leanprover-community/repl/issues).
|
|
132
|
+
|
|
133
|
+
First, let's run a command to create a theorem with a `sorry` proof:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := sorry"))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
<details>
|
|
140
|
+
<summary>Output</summary>
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
CommandResponse(
|
|
144
|
+
sorries=[
|
|
145
|
+
Sorry(start_pos=Pos(line=1, column=40),
|
|
146
|
+
end_pos=Pos(line=1, column=45),
|
|
147
|
+
goal='n : Nat\n⊢ n = 5 → n = 5',
|
|
148
|
+
proof_state=0)
|
|
149
|
+
],
|
|
150
|
+
env=0,
|
|
151
|
+
messages=[
|
|
152
|
+
Message(start_pos=Pos(line=1, column=8),
|
|
153
|
+
end_pos=Pos(line=1, column=10),
|
|
154
|
+
data="declaration uses 'sorry'",
|
|
155
|
+
severity='warning')
|
|
156
|
+
]
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
</details>
|
|
161
|
+
|
|
162
|
+
You can then iterate on the proof state by executing tactics:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from lean_interact import ProofStep
|
|
166
|
+
|
|
167
|
+
server.run(ProofStep(tactic="intro h", proof_state=0))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
<details>
|
|
171
|
+
<summary>Output</summary>
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
ProofStepResponse(
|
|
175
|
+
proof_state=1,
|
|
176
|
+
goals=['n : Nat\nh : n = 5\n⊢ n = 5'],
|
|
177
|
+
proof_status='Incomplete: open goals remain'
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
</details>
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
server.run(ProofStep(tactic="exact h", proof_state=1))
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
<details>
|
|
188
|
+
<summary>Output</summary>
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
ProofStepResponse(proof_state=2, goals=[], proof_status='Completed')
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
</details>
|
|
195
|
+
|
|
196
|
+
or by directly running the entire proof:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
server.run(ProofStep(tactic="(\nintro h\nexact h)", proof_state=0))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
<details>
|
|
203
|
+
<summary>Output</summary>
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
ProofStepResponse(proof_state=3, goals=[], proof_status='Completed')
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
</details>
|
|
210
|
+
|
|
211
|
+
### Custom Lean configuration
|
|
212
|
+
|
|
213
|
+
#### Specific Lean version
|
|
80
214
|
|
|
81
215
|
```python
|
|
82
216
|
config = LeanREPLConfig(lean_version="v4.7.0")
|
|
83
217
|
```
|
|
84
218
|
|
|
85
|
-
|
|
219
|
+
#### Existing Lean projects
|
|
86
220
|
|
|
87
221
|
```python
|
|
88
222
|
config = LeanREPLConfig(project=LocalProject("path/to/your/project"))
|
|
@@ -106,7 +240,7 @@ server.run(FileCommand(path="file.lean"))
|
|
|
106
240
|
> [!IMPORTANT]
|
|
107
241
|
> Ensure the project can be *successfully* built with `lake build` before using LeanInteract.
|
|
108
242
|
|
|
109
|
-
|
|
243
|
+
#### Temporary project with dependencies
|
|
110
244
|
|
|
111
245
|
```python
|
|
112
246
|
from lean_interact import TempRequireProject
|
|
@@ -138,21 +272,19 @@ theorem ex_mathlib (x : ℝ) (y : ℚ) :
|
|
|
138
272
|
|
|
139
273
|
```python
|
|
140
274
|
CommandResponse(
|
|
141
|
-
|
|
142
|
-
Message(
|
|
143
|
-
start_pos=Pos(line=2, column=8),
|
|
144
|
-
end_pos=Pos(line=2, column=18),
|
|
145
|
-
data="declaration uses 'sorry'",
|
|
146
|
-
severity='warning'
|
|
147
|
-
)],
|
|
275
|
+
env=0,
|
|
148
276
|
sorries=[
|
|
149
|
-
Sorry(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
277
|
+
Sorry(end_pos=Pos(line=3, column=51),
|
|
278
|
+
goal='x : ℝ\ny : ℚ\n⊢ Irrational x → Irrational (x + ↑y)',
|
|
279
|
+
start_pos=Pos(line=3, column=46),
|
|
280
|
+
proof_state=0)
|
|
281
|
+
],
|
|
282
|
+
messages=[
|
|
283
|
+
Message(end_pos=Pos(line=2, column=18),
|
|
284
|
+
data="declaration uses 'sorry'",
|
|
285
|
+
start_pos=Pos(line=2, column=8),
|
|
286
|
+
severity='warning')
|
|
287
|
+
]
|
|
156
288
|
)
|
|
157
289
|
```
|
|
158
290
|
|
|
@@ -163,7 +295,7 @@ CommandResponse(
|
|
|
163
295
|
> - Mathlib is a large library and may take some time to download and build.
|
|
164
296
|
> - A separate cache is used for each unique set of dependencies.
|
|
165
297
|
|
|
166
|
-
|
|
298
|
+
#### Fine-grained temporary project
|
|
167
299
|
|
|
168
300
|
For more control over the temporary project, you can use `TemporaryProject` to specify the content of the lakefile.
|
|
169
301
|
|
|
@@ -186,79 +318,82 @@ require mathlib from git
|
|
|
186
318
|
"""))
|
|
187
319
|
```
|
|
188
320
|
|
|
189
|
-
|
|
321
|
+
## Available Queries
|
|
190
322
|
|
|
191
|
-
|
|
192
|
-
server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := sorry"))
|
|
193
|
-
```
|
|
323
|
+
LeanInteract supports various types of queries to interact with the Lean REPL. We briefly describe them in this section. You can check the file [interface.py](src/lean_interact/interface.py) for more details.
|
|
194
324
|
|
|
195
|
-
|
|
196
|
-
|
|
325
|
+
### Command
|
|
326
|
+
|
|
327
|
+
Execute Lean code directly:
|
|
197
328
|
|
|
198
329
|
```python
|
|
199
|
-
|
|
200
|
-
messages=[
|
|
201
|
-
Message(
|
|
202
|
-
start_pos=Pos(line=1, column=8),
|
|
203
|
-
end_pos=Pos(line=1, column=10),
|
|
204
|
-
data="declaration uses 'sorry'",
|
|
205
|
-
severity='warning'
|
|
206
|
-
)],
|
|
207
|
-
sorries=[
|
|
208
|
-
Sorry(
|
|
209
|
-
start_pos=Pos(line=1, column=40),
|
|
210
|
-
end_pos=Pos(line=1, column=45),
|
|
211
|
-
goal='n : Nat\n⊢ n = 5 → n = 5',
|
|
212
|
-
proof_state=0
|
|
213
|
-
)],
|
|
214
|
-
env=0
|
|
215
|
-
)
|
|
216
|
-
```
|
|
330
|
+
from lean_interact import Command
|
|
217
331
|
|
|
218
|
-
|
|
332
|
+
# Execute a simple theorem
|
|
333
|
+
response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
|
|
219
334
|
|
|
220
|
-
|
|
335
|
+
# Execute with options to get tactics information
|
|
336
|
+
response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := by simp", all_tactics=True))
|
|
221
337
|
|
|
222
|
-
|
|
223
|
-
server.run(
|
|
338
|
+
# Continue in the same environment
|
|
339
|
+
response = server.run(Command(cmd="#check ex", env=response.env))
|
|
224
340
|
```
|
|
225
341
|
|
|
226
|
-
|
|
227
|
-
|
|
342
|
+
### FileCommand
|
|
343
|
+
|
|
344
|
+
Process Lean files:
|
|
228
345
|
|
|
229
346
|
```python
|
|
230
|
-
|
|
231
|
-
```
|
|
347
|
+
from lean_interact import FileCommand
|
|
232
348
|
|
|
233
|
-
|
|
349
|
+
# Execute a Lean file
|
|
350
|
+
response = server.run(FileCommand(path="myfile.lean"))
|
|
234
351
|
|
|
235
|
-
|
|
236
|
-
server.run(
|
|
352
|
+
# With options for more information
|
|
353
|
+
response = server.run(FileCommand(path="myfile.lean", root_goals=True))
|
|
237
354
|
```
|
|
238
355
|
|
|
239
|
-
|
|
240
|
-
|
|
356
|
+
### ProofStep
|
|
357
|
+
|
|
358
|
+
Work with proof tactics step by step:
|
|
241
359
|
|
|
242
360
|
```python
|
|
243
|
-
|
|
361
|
+
from lean_interact import ProofStep
|
|
362
|
+
|
|
363
|
+
# Apply a tactic to a proof state
|
|
364
|
+
response = server.run(ProofStep(proof_state=0, tactic="intro h"))
|
|
365
|
+
|
|
366
|
+
# Apply multiple tactics at once
|
|
367
|
+
response = server.run(ProofStep(proof_state=0, tactic="intro h\nexact h"))
|
|
244
368
|
```
|
|
245
369
|
|
|
246
|
-
|
|
370
|
+
### Environment Pickling
|
|
247
371
|
|
|
248
|
-
|
|
372
|
+
Save and restore environment states:
|
|
249
373
|
|
|
250
374
|
```python
|
|
251
|
-
|
|
375
|
+
from lean_interact import PickleEnvironment, UnpickleEnvironment
|
|
376
|
+
|
|
377
|
+
# Save an environment
|
|
378
|
+
server.run(PickleEnvironment(env=1, pickle_to="env_state.olean"))
|
|
379
|
+
|
|
380
|
+
# Restore an environment
|
|
381
|
+
server.run(UnpickleEnvironment(unpickle_env_from="env_state.olean"))
|
|
252
382
|
```
|
|
253
383
|
|
|
254
|
-
|
|
255
|
-
|
|
384
|
+
### ProofState Pickling
|
|
385
|
+
|
|
386
|
+
Save and restore proof states:
|
|
256
387
|
|
|
257
388
|
```python
|
|
258
|
-
|
|
259
|
-
```
|
|
389
|
+
from lean_interact import PickleProofState, UnpickleProofState
|
|
260
390
|
|
|
261
|
-
|
|
391
|
+
# Save a proof state
|
|
392
|
+
server.run(PickleProofState(proof_state=2, pickle_to="proof_state.olean"))
|
|
393
|
+
|
|
394
|
+
# Restore a proof state
|
|
395
|
+
server.run(UnpickleProofState(unpickle_proof_state_from="proof_state.olean", env=1))
|
|
396
|
+
```
|
|
262
397
|
|
|
263
398
|
## Helper Commands
|
|
264
399
|
|
|
@@ -296,3 +431,42 @@ We recommend checking out these tools:
|
|
|
296
431
|
- **[leanclient](https://github.com/oOo0oOo/leanclient)**: Interact with the Lean LSP server.
|
|
297
432
|
|
|
298
433
|
LeanInteract is inspired by **[pylean](https://github.com/zhangir-azerbayev/repl)** and **[lean4_jupyter](https://github.com/utensil/lean4_jupyter)**.
|
|
434
|
+
|
|
435
|
+
## Troubleshooting
|
|
436
|
+
|
|
437
|
+
Common issues and their solutions:
|
|
438
|
+
|
|
439
|
+
1. **Out of memory errors**: Reduce parallel processing or increase system memory. Alternatively, use `AutoLeanServer` with conservative memory settings
|
|
440
|
+
|
|
441
|
+
2. **Timeout errors**: Currently, `LeanServer` simply stops the Lean REPL if a command times out. Use `AutoLeanServer` for automatic recovery.
|
|
442
|
+
|
|
443
|
+
3. **Long waiting times during first run**: This is expected as Lean REPL is being downloaded and built. Additionally, if you are importing Mathlib it will take even more time. Subsequent runs will be much faster.
|
|
444
|
+
|
|
445
|
+
## Contributing
|
|
446
|
+
|
|
447
|
+
Contributions are welcome! Here's how you can help:
|
|
448
|
+
|
|
449
|
+
1. Fork the repository
|
|
450
|
+
2. Create a feature branch: `git checkout -b feature-name`
|
|
451
|
+
3. Commit your changes: `git commit -am 'Add new feature'`
|
|
452
|
+
4. Push to the branch: `git push origin feature-name`
|
|
453
|
+
5. Submit a pull request
|
|
454
|
+
|
|
455
|
+
Please ensure your code includes appropriate tests.
|
|
456
|
+
|
|
457
|
+
## Citation
|
|
458
|
+
|
|
459
|
+
If you use LeanInteract in your research, please cite it as follows:
|
|
460
|
+
|
|
461
|
+
```bibtex
|
|
462
|
+
@software{leaninteract,
|
|
463
|
+
author = {Poiroux, Auguste},
|
|
464
|
+
title = {LeanInteract: A Python Interface for Lean 4},
|
|
465
|
+
url = {https://github.com/augustepoiroux/lean-interact},
|
|
466
|
+
year = {2025}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lean-interact"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "LeanInteract is a Python package that allows you to interact with the Lean theorem prover."
|
|
5
5
|
keywords = ["Lean", "theorem proving", "autoformalization", "REPL"]
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -28,39 +28,93 @@ class BaseREPLQuery(REPLBaseModel):
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class CommandOptions(REPLBaseModel):
|
|
31
|
+
"""Options for commands.
|
|
32
|
+
Attributes:
|
|
33
|
+
all_tactics: If true, return all tactics used in the command with their associated information.
|
|
34
|
+
root_goals: If true, return root goals, i.e. initial goals of all declarations in the command, even if they already have a proof.
|
|
35
|
+
infotree: Return syntax information. Should be "full", "tactics", "original", or "substantive". Anything else is ignored.
|
|
36
|
+
"""
|
|
37
|
+
|
|
31
38
|
all_tactics: Annotated[bool | None, Field(alias="allTactics")] = None
|
|
39
|
+
root_goals: Annotated[bool | None, Field(alias="rootGoals")] = None
|
|
32
40
|
infotree: str | None = None
|
|
33
41
|
|
|
34
42
|
|
|
35
43
|
class Command(BaseREPLQuery, CommandOptions):
|
|
44
|
+
"""Command to be executed in the REPL.
|
|
45
|
+
Attributes:
|
|
46
|
+
cmd: The command to be executed.
|
|
47
|
+
env: The environment to be used (optional). If `env = None`, starts a new session (in which you can use `import`).
|
|
48
|
+
If `env` is set, the command is executed in the given environment.
|
|
49
|
+
all_tactics: If true, return all tactics used in the command with their associated information.
|
|
50
|
+
root_goals: If true, return root goals, i.e. initial goals of all declarations in the command, even if they already have a proof.
|
|
51
|
+
infotree: Return syntax information. Should be "full", "tactics", "original", or "substantive". Anything else is ignored.
|
|
52
|
+
"""
|
|
53
|
+
|
|
36
54
|
cmd: Annotated[str, Field(min_length=1)]
|
|
37
55
|
env: int | None = None
|
|
38
56
|
|
|
39
57
|
|
|
40
58
|
class FileCommand(BaseREPLQuery, CommandOptions):
|
|
59
|
+
"""Command for file operations in the REPL.
|
|
60
|
+
Attributes:
|
|
61
|
+
path: The path of the file to be operated on.
|
|
62
|
+
all_tactics: If true, return all tactics used in the command with their associated information.
|
|
63
|
+
root_goals: If true, return root goals, i.e. initial goals of all declarations in the command, even if they already have a proof.
|
|
64
|
+
infotree: Return syntax information. Should be "full", "tactics", "original", or "substantive". Anything else is ignored.
|
|
65
|
+
"""
|
|
66
|
+
|
|
41
67
|
path: Annotated[str, Field(min_length=1)]
|
|
42
68
|
|
|
43
69
|
|
|
44
70
|
class ProofStep(BaseREPLQuery, REPLBaseModel):
|
|
71
|
+
"""Proof step in the REPL.
|
|
72
|
+
Attributes:
|
|
73
|
+
proof_state: The proof state to start from.
|
|
74
|
+
tactic: The tactic to be applied.
|
|
75
|
+
"""
|
|
76
|
+
|
|
45
77
|
proof_state: Annotated[int, Field(alias="proofState")]
|
|
46
78
|
tactic: Annotated[str, Field(min_length=1)]
|
|
47
79
|
|
|
48
80
|
|
|
49
81
|
class PickleEnvironment(BaseREPLQuery):
|
|
82
|
+
"""Environment for pickling in the REPL.
|
|
83
|
+
Attributes:
|
|
84
|
+
env: The environment to be used.
|
|
85
|
+
pickle_to: The path to save the pickle file.
|
|
86
|
+
"""
|
|
87
|
+
|
|
50
88
|
env: int
|
|
51
89
|
pickle_to: Annotated[str, Field(min_length=1, alias="pickleTo")]
|
|
52
90
|
|
|
53
91
|
|
|
54
92
|
class UnpickleEnvironment(BaseREPLQuery):
|
|
93
|
+
"""Environment for unpickling in the REPL.
|
|
94
|
+
Attributes:
|
|
95
|
+
unpickle_env_from: The path to the pickle file.
|
|
96
|
+
"""
|
|
97
|
+
|
|
55
98
|
unpickle_env_from: Annotated[str, Field(min_length=1, alias="unpickleEnvFrom")]
|
|
56
99
|
|
|
57
100
|
|
|
58
101
|
class PickleProofState(BaseREPLQuery):
|
|
102
|
+
"""Proof state for pickling in the REPL.
|
|
103
|
+
Attributes:
|
|
104
|
+
proof_state: The proof state to be pickled.
|
|
105
|
+
pickle_to: The path to save the pickle file.
|
|
106
|
+
"""
|
|
107
|
+
|
|
59
108
|
proof_state: Annotated[int, Field(alias="proofState")]
|
|
60
109
|
pickle_to: Annotated[str, Field(min_length=1, alias="pickleTo")]
|
|
61
110
|
|
|
62
111
|
|
|
63
112
|
class UnpickleProofState(BaseREPLQuery):
|
|
113
|
+
"""Environment for unpickling in the REPL.
|
|
114
|
+
Attributes:
|
|
115
|
+
unpickle_proof_state_from: The path to the pickle file.
|
|
116
|
+
"""
|
|
117
|
+
|
|
64
118
|
unpickle_proof_state_from: Annotated[str, Field(min_length=1, alias="unpickleProofStateFrom")]
|
|
65
119
|
env: int | None = None
|
|
66
120
|
|
|
@@ -84,6 +138,14 @@ class Pos(REPLBaseModel):
|
|
|
84
138
|
|
|
85
139
|
|
|
86
140
|
class Message(REPLBaseModel):
|
|
141
|
+
"""Message in the REPL.
|
|
142
|
+
Attributes:
|
|
143
|
+
start_pos: The starting position of the message.
|
|
144
|
+
end_pos: The ending position of the message.
|
|
145
|
+
severity: The severity of the message.
|
|
146
|
+
data: The data associated with the message.
|
|
147
|
+
"""
|
|
148
|
+
|
|
87
149
|
start_pos: Annotated[Pos, Field(alias="pos")]
|
|
88
150
|
end_pos: Annotated[Pos | None, Field(alias="endPos")] = None
|
|
89
151
|
severity: Literal["error", "warning", "info", "trace"]
|
|
@@ -91,6 +153,14 @@ class Message(REPLBaseModel):
|
|
|
91
153
|
|
|
92
154
|
|
|
93
155
|
class Sorry(REPLBaseModel):
|
|
156
|
+
"""Sorry message in the REPL.
|
|
157
|
+
Attributes:
|
|
158
|
+
start_pos: The starting position of the sorry message.
|
|
159
|
+
end_pos: The ending position of the sorry message.
|
|
160
|
+
goal: The proof goal at the sorry location.
|
|
161
|
+
proof_state: The proof state associated to the sorry.
|
|
162
|
+
"""
|
|
163
|
+
|
|
94
164
|
start_pos: Annotated[Pos, Field(alias="pos")]
|
|
95
165
|
end_pos: Annotated[Pos, Field(alias="endPos")]
|
|
96
166
|
goal: str
|
|
@@ -98,6 +168,16 @@ class Sorry(REPLBaseModel):
|
|
|
98
168
|
|
|
99
169
|
|
|
100
170
|
class Tactic(REPLBaseModel):
|
|
171
|
+
"""Tactic in the REPL.
|
|
172
|
+
Attributes:
|
|
173
|
+
start_pos: The starting position of the tactic.
|
|
174
|
+
end_pos: The ending position of the tactic.
|
|
175
|
+
goals: The goals associated with the tactic.
|
|
176
|
+
tactic: The applied tactic.
|
|
177
|
+
proof_state: The proof state associated with the tactic.
|
|
178
|
+
used_constants: The constants used in the tactic.
|
|
179
|
+
"""
|
|
180
|
+
|
|
101
181
|
start_pos: Annotated[Pos, Field(alias="pos")]
|
|
102
182
|
end_pos: Annotated[Pos, Field(alias="endPos")]
|
|
103
183
|
goals: str
|
|
@@ -119,6 +199,12 @@ def message_intersects_code(msg: Message | Sorry, start_pos: Pos | None, end_pos
|
|
|
119
199
|
|
|
120
200
|
|
|
121
201
|
class BaseREPLResponse(REPLBaseModel):
|
|
202
|
+
"""Base class for all Lean responses.
|
|
203
|
+
Attributes:
|
|
204
|
+
messages: List of messages in the response.
|
|
205
|
+
sorries: List of sorries found in the submitted code.
|
|
206
|
+
"""
|
|
207
|
+
|
|
122
208
|
messages: list[Message] = Field(default_factory=list)
|
|
123
209
|
sorries: list[Sorry] = Field(default_factory=list)
|
|
124
210
|
|
|
@@ -157,12 +243,32 @@ class BaseREPLResponse(REPLBaseModel):
|
|
|
157
243
|
|
|
158
244
|
|
|
159
245
|
class CommandResponse(BaseREPLResponse):
|
|
246
|
+
"""Response to a command in the REPL.
|
|
247
|
+
Attributes:
|
|
248
|
+
env: The environment state after running the code in the command
|
|
249
|
+
tactics: List of tactics in the code. Returned only if `all_tactics` is true.
|
|
250
|
+
infotree: The infotree of the code. Returned only if `infotree` is true.
|
|
251
|
+
messages: List of messages in the response.
|
|
252
|
+
sorries: List of sorries found in the submitted code.
|
|
253
|
+
"""
|
|
254
|
+
|
|
160
255
|
env: int
|
|
161
256
|
tactics: list[Tactic] = Field(default_factory=list)
|
|
162
257
|
infotree: dict | None = None
|
|
163
258
|
|
|
164
259
|
|
|
165
260
|
class ProofStepResponse(BaseREPLResponse):
|
|
261
|
+
"""Response to a proof step in the REPL.
|
|
262
|
+
Attributes:
|
|
263
|
+
proof_status: The proof status of the whole proof. Possible values: `Completed`, `Incomplete`, `Error`.
|
|
264
|
+
proof_state: The proof state after the proof step.
|
|
265
|
+
goals: List of goals after the proof step.
|
|
266
|
+
traces: List of traces in the proof step.
|
|
267
|
+
messages: List of messages in the response.
|
|
268
|
+
sorries: List of sorries found in the submitted code.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
proof_status: Annotated[str, Field(alias="proofStatus")]
|
|
166
272
|
proof_state: Annotated[int, Field(alias="proofState")]
|
|
167
273
|
goals: list[str] = Field(default_factory=list)
|
|
168
274
|
traces: list[str] = Field(default_factory=list)
|
|
@@ -90,17 +90,17 @@ class LeanServer:
|
|
|
90
90
|
assert self._proc is not None
|
|
91
91
|
if verbose:
|
|
92
92
|
logger.info("Sending query: %s", json_query)
|
|
93
|
-
self._proc.
|
|
94
|
-
self._proc.
|
|
95
|
-
_ = self._proc.expect_exact("\r\n\r\n", timeout=timeout)
|
|
93
|
+
self._proc.send(json_query + "\n\n")
|
|
94
|
+
self._proc.expect_exact("\r\n\r\n", timeout=timeout)
|
|
96
95
|
return self._proc.before or ""
|
|
97
96
|
|
|
98
97
|
def _parse_repl_output(self, raw_output: str, verbose: bool) -> dict:
|
|
99
98
|
"""Clean up raw REPL output and parse JSON response."""
|
|
99
|
+
if verbose:
|
|
100
|
+
logger.info("Server raw output: `%s", raw_output)
|
|
100
101
|
output = raw_output.replace("\r\n", "\n")
|
|
101
102
|
output = output[output.find('{"') :] if '{"' in output else ""
|
|
102
103
|
if verbose:
|
|
103
|
-
logger.info("Server raw output: `%s", raw_output)
|
|
104
104
|
logger.info("Server cleaned output: `%s", output)
|
|
105
105
|
try:
|
|
106
106
|
return json.loads(output)
|
|
@@ -21,7 +21,7 @@ logger.addHandler(handler)
|
|
|
21
21
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
22
22
|
DEFAULT_CACHE_DIR = os.path.join(ROOT_DIR, "cache")
|
|
23
23
|
DEFAULT_REPL_GIT_URL = "https://github.com/augustepoiroux/repl"
|
|
24
|
-
DEFAULT_REPL_VERSION = "v1.0.
|
|
24
|
+
DEFAULT_REPL_VERSION = "v1.0.6"
|
|
25
25
|
|
|
26
26
|
os.makedirs(DEFAULT_CACHE_DIR, exist_ok=True)
|
|
27
27
|
|
|
@@ -139,7 +139,7 @@ class TestLeanServer(unittest.TestCase):
|
|
|
139
139
|
),
|
|
140
140
|
)
|
|
141
141
|
tactic_result = server.run(ProofStep(tactic="rfl", proof_state=0))
|
|
142
|
-
self.assertEqual(tactic_result, ProofStepResponse(proof_state=1, goals=[]))
|
|
142
|
+
self.assertEqual(tactic_result, ProofStepResponse(proof_state=1, goals=[], proof_status="Completed"))
|
|
143
143
|
|
|
144
144
|
def test_run_file_nonexistent(self):
|
|
145
145
|
server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
|
|
@@ -197,14 +197,7 @@ class TestLeanServer(unittest.TestCase):
|
|
|
197
197
|
),
|
|
198
198
|
)
|
|
199
199
|
result = server.run(ProofStep(tactic="rfl", proof_state=0))
|
|
200
|
-
self.assertEqual(result, ProofStepResponse(proof_state=1, goals=[]))
|
|
201
|
-
|
|
202
|
-
def test_run_multiple_commands(self):
|
|
203
|
-
server = AutoLeanServer(config=LeanREPLConfig(memory_hard_limit_mb=4096, verbose=True))
|
|
204
|
-
|
|
205
|
-
for i in range(100):
|
|
206
|
-
cmd = Command(cmd=f"theorem womp{i} (a{i} b c : Nat) : (a{i} + b) + c = c + a{i} + b := by sorry")
|
|
207
|
-
server.run(cmd)
|
|
200
|
+
self.assertEqual(result, ProofStepResponse(proof_state=1, goals=[], proof_status="Completed"))
|
|
208
201
|
|
|
209
202
|
def test_lean_version(self):
|
|
210
203
|
server = AutoLeanServer(config=LeanREPLConfig(lean_version="v4.14.0", verbose=True))
|
|
@@ -258,7 +251,7 @@ class TestLeanServer(unittest.TestCase):
|
|
|
258
251
|
),
|
|
259
252
|
)
|
|
260
253
|
result = server.run(ProofStep(tactic="apply irrational_add_rat_iff.mpr", proof_state=0))
|
|
261
|
-
self.assertEqual(result, ProofStepResponse(proof_state=1, goals=[]))
|
|
254
|
+
self.assertEqual(result, ProofStepResponse(proof_state=1, goals=[], proof_status="Completed"))
|
|
262
255
|
|
|
263
256
|
def test_restart_with_env(self):
|
|
264
257
|
server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
|
|
@@ -298,12 +291,12 @@ class TestLeanServer(unittest.TestCase):
|
|
|
298
291
|
# Prepare restart_persistent_session_cache
|
|
299
292
|
server._restart_persistent_session_cache[-2] = _SessionState(-2, 20, "", True)
|
|
300
293
|
with unittest.mock.patch.object(server, "_get_repl_state_id", return_value=20):
|
|
301
|
-
mock_super.return_value = {"proofState": 20}
|
|
294
|
+
mock_super.return_value = {"proofState": 20, "goals": [], "proofStatus": "Completed"}
|
|
302
295
|
result = server.run(ProofStep(proof_state=-2, tactic="test"))
|
|
303
296
|
mock_super.assert_called_with(
|
|
304
297
|
request={"proofState": 20, "tactic": "test"}, verbose=False, timeout=DEFAULT_TIMEOUT
|
|
305
298
|
)
|
|
306
|
-
self.assertEqual(result, ProofStepResponse(proof_state=20, goals=[]))
|
|
299
|
+
self.assertEqual(result, ProofStepResponse(proof_state=20, goals=[], proof_status="Completed"))
|
|
307
300
|
|
|
308
301
|
@unittest.mock.patch("lean_interact.server.LeanServer.run_dict", return_value={})
|
|
309
302
|
@unittest.mock.patch("lean_interact.server.psutil.virtual_memory")
|
|
@@ -409,13 +402,23 @@ class TestLeanServer(unittest.TestCase):
|
|
|
409
402
|
step1 = server.run(ProofStep(tactic="intro x", proof_state=0))
|
|
410
403
|
assert not isinstance(step1, LeanError)
|
|
411
404
|
step2 = server.run(ProofStep(tactic="rfl", proof_state=step1.proof_state))
|
|
412
|
-
self.assertEqual(step2, ProofStepResponse(proof_state=2, goals=[]))
|
|
405
|
+
self.assertEqual(step2, ProofStepResponse(proof_state=2, goals=[], proof_status="Completed"))
|
|
406
|
+
|
|
407
|
+
def test_run_multiple_commands(self):
|
|
408
|
+
# Test this issue: https://github.com/leanprover-community/repl/issues/77
|
|
409
|
+
server = AutoLeanServer(config=LeanREPLConfig(memory_hard_limit_mb=4096, verbose=True))
|
|
410
|
+
|
|
411
|
+
with self.assertRaises(ConnectionAbortedError):
|
|
412
|
+
for i in range(1000):
|
|
413
|
+
cmd = Command(cmd=f"theorem womp{i} (a{i} b c : Nat) : (a{i} + b) + c = c + a{i} + b := by sorry")
|
|
414
|
+
server.run(cmd)
|
|
413
415
|
|
|
414
416
|
def test_run_lots_of_commands(self):
|
|
417
|
+
# Test this issue: https://github.com/leanprover-community/repl/issues/77
|
|
415
418
|
server = LeanServer(LeanREPLConfig(verbose=True))
|
|
416
419
|
|
|
417
420
|
init_env = server.run(Command(cmd="#eval 1"))
|
|
418
|
-
assert isinstance(init_env,
|
|
421
|
+
assert not isinstance(init_env, LeanError)
|
|
419
422
|
for i in range(1000):
|
|
420
423
|
cmd = Command(
|
|
421
424
|
cmd=f"theorem womp{i} (a{i} b c : Nat) : (a{i} + b) + c = c + a{i} + b := by sorry", env=init_env.env
|
|
@@ -543,7 +546,7 @@ class TestLeanServer(unittest.TestCase):
|
|
|
543
546
|
|
|
544
547
|
# Test that we can continue the proof from the unpickled proof state
|
|
545
548
|
tactic_result = new_server.run(ProofStep(tactic="rfl", proof_state=unpickled_proof_state_id))
|
|
546
|
-
self.assertEqual(tactic_result, ProofStepResponse(proof_state=1, goals=[]))
|
|
549
|
+
self.assertEqual(tactic_result, ProofStepResponse(proof_state=1, goals=[], proof_status="Completed"))
|
|
547
550
|
|
|
548
551
|
def test_pickle_fails_with_invalid_env(self):
|
|
549
552
|
server = AutoLeanServer(config=LeanREPLConfig(verbose=True))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lean_interact-0.3.2 → lean_interact-0.4.0}/examples/proof_generation_and_autoformalization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|