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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lean-interact
3
- Version: 0.3.2
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
+ [![PyPI version](https://badge.fury.io/py/lean-interact.svg)](https://badge.fury.io/py/lean-interact)
41
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.18.0`.
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) (or use the `install-lean` command from LeanInteract)
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
- ### Default Lean version (latest available)
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(env=0)
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(env=1)
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
- ### Specific Lean version
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
- ### Existing Lean projects
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
- ### Temporary project with dependencies
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
- messages=[
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
- start_pos=Pos(line=3, column=46),
188
- end_pos=Pos(line=3, column=51),
189
- goal='x : ℝ\ny : ℚ\n⊢ Irrational x → Irrational (x + ↑y)',
190
- proof_state=0
191
- )],
192
- env=0
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
- ### Fine-grained temporary project
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
- ### Tactic mode (experimental)
358
+ ## Available Queries
227
359
 
228
- ```python
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
- <details>
233
- <summary>Output</summary>
362
+ ### Command
363
+
364
+ Execute Lean code directly:
234
365
 
235
366
  ```python
236
- CommandResponse(
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
- </details>
369
+ # Execute a simple theorem
370
+ response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
256
371
 
257
- You can then iterate on the proof state by executing tactics:
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
- ```python
260
- server.run(ProofStep(tactic="intro h", proof_state=0))
375
+ # Continue in the same environment
376
+ response = server.run(Command(cmd="#check ex", env=response.env))
261
377
  ```
262
378
 
263
- <details>
264
- <summary>Output</summary>
379
+ ### FileCommand
380
+
381
+ Process Lean files:
265
382
 
266
383
  ```python
267
- ProofStepResponse(goals=['n : Nat\nh : n = 5\n⊢ n = 5'], proof_state=1)
268
- ```
384
+ from lean_interact import FileCommand
269
385
 
270
- </details>
386
+ # Execute a Lean file
387
+ response = server.run(FileCommand(path="myfile.lean"))
271
388
 
272
- ```python
273
- server.run(ProofStep(tactic="exact h", proof_state=1))
389
+ # With options for more information
390
+ response = server.run(FileCommand(path="myfile.lean", root_goals=True))
274
391
  ```
275
392
 
276
- <details>
277
- <summary>Output</summary>
393
+ ### ProofStep
394
+
395
+ Work with proof tactics step by step:
278
396
 
279
397
  ```python
280
- ProofStepResponse(goals=[], proof_state=2)
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
- </details>
407
+ ### Environment Pickling
284
408
 
285
- or by directly running the entire proof:
409
+ Save and restore environment states:
286
410
 
287
411
  ```python
288
- server.run(ProofStep(tactic="(\nintro h\nexact h)", proof_state=0))
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
- <details>
292
- <summary>Output</summary>
421
+ ### ProofState Pickling
422
+
423
+ Save and restore proof states:
293
424
 
294
425
  ```python
295
- ProofStepResponse(goals=[], proof_state=3)
296
- ```
426
+ from lean_interact import PickleProofState, UnpickleProofState
297
427
 
298
- </details>
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
+ [![PyPI version](https://badge.fury.io/py/lean-interact.svg)](https://badge.fury.io/py/lean-interact)
4
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.18.0`.
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) (or use the `install-lean` command from LeanInteract)
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
- ### Default Lean version (latest available)
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(env=0)
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(env=1)
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
- ### Specific Lean version
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
- ### Existing Lean projects
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
- ### Temporary project with dependencies
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
- messages=[
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
- start_pos=Pos(line=3, column=46),
151
- end_pos=Pos(line=3, column=51),
152
- goal='x : ℝ\ny : ℚ\n⊢ Irrational x → Irrational (x + ↑y)',
153
- proof_state=0
154
- )],
155
- env=0
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
- ### Fine-grained temporary project
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
- ### Tactic mode (experimental)
321
+ ## Available Queries
190
322
 
191
- ```python
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
- <details>
196
- <summary>Output</summary>
325
+ ### Command
326
+
327
+ Execute Lean code directly:
197
328
 
198
329
  ```python
199
- CommandResponse(
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
- </details>
332
+ # Execute a simple theorem
333
+ response = server.run(Command(cmd="theorem ex (n : Nat) : n = 5 → n = 5 := id"))
219
334
 
220
- You can then iterate on the proof state by executing tactics:
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
- ```python
223
- server.run(ProofStep(tactic="intro h", proof_state=0))
338
+ # Continue in the same environment
339
+ response = server.run(Command(cmd="#check ex", env=response.env))
224
340
  ```
225
341
 
226
- <details>
227
- <summary>Output</summary>
342
+ ### FileCommand
343
+
344
+ Process Lean files:
228
345
 
229
346
  ```python
230
- ProofStepResponse(goals=['n : Nat\nh : n = 5\n⊢ n = 5'], proof_state=1)
231
- ```
347
+ from lean_interact import FileCommand
232
348
 
233
- </details>
349
+ # Execute a Lean file
350
+ response = server.run(FileCommand(path="myfile.lean"))
234
351
 
235
- ```python
236
- server.run(ProofStep(tactic="exact h", proof_state=1))
352
+ # With options for more information
353
+ response = server.run(FileCommand(path="myfile.lean", root_goals=True))
237
354
  ```
238
355
 
239
- <details>
240
- <summary>Output</summary>
356
+ ### ProofStep
357
+
358
+ Work with proof tactics step by step:
241
359
 
242
360
  ```python
243
- ProofStepResponse(goals=[], proof_state=2)
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
- </details>
370
+ ### Environment Pickling
247
371
 
248
- or by directly running the entire proof:
372
+ Save and restore environment states:
249
373
 
250
374
  ```python
251
- server.run(ProofStep(tactic="(\nintro h\nexact h)", proof_state=0))
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
- <details>
255
- <summary>Output</summary>
384
+ ### ProofState Pickling
385
+
386
+ Save and restore proof states:
256
387
 
257
388
  ```python
258
- ProofStepResponse(goals=[], proof_state=3)
259
- ```
389
+ from lean_interact import PickleProofState, UnpickleProofState
260
390
 
261
- </details>
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.
@@ -2,7 +2,7 @@
2
2
  # requires-python = ">=3.10"
3
3
  # dependencies = [
4
4
  # "datasets",
5
- # "lean-interact @ file:///home/poiroux/Documents/EPFL/PhD/Lean/LeanInteract",
5
+ # "lean-interact",
6
6
  # "rich",
7
7
  # "tqdm",
8
8
  # "scikit-learn",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lean-interact"
3
- version = "0.3.2"
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.sendline(json_query)
94
- self._proc.sendline()
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.4"
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, CommandResponse)
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