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

Run using `lake exe repl`.
Communicates via JSON on stdin and stdout.
Commands should be separated by blank lines.

The REPL works both in "command" mode and "tactic" mode.

## Command mode

In command mode, you send complete commands (e.g. declarations) to the REPL.

Commands may be of the form

```json
{ "cmd" : "def f := 2" }
```

```json
{ "cmd" : "example : f = 2 := rfl", "env" : 1 }
```

The `env` field, if present,
must contain a number received in the `env` field of a previous response,
and causes the command to be run in the existing environment.

If there is no `env` field, a new environment is created.

You can only use `import` commands when you do not specify the `env` field.

You can backtrack simply by using earlier values for `env`.

The response includes:
* A numeric label for the `Environment` after your command,
  which you can use as the starting point for subsequent commands.
* Any messages generated while processing your command.
* A list of the `sorry`s in your command, including
  * their expected type, and
  * a numeric label for the proof state at the `sorry`, which you can then use in tactic mode.

Example output:

```json
{"sorries":
 [{"pos": {"line": 1, "column": 18},
   "endPos": {"line": 1, "column": 23},
   "goal": "⊢ Nat",
   "proofState": 0}],
 "messages":
 [{"severity": "error",
   "pos": {"line": 1, "column": 23},
   "endPos": {"line": 1, "column": 26},
   "data":
   "type mismatch\n  rfl\nhas type\n  f = f : Prop\nbut is expected to have type\n  f = 2 : Prop"}],
 "env": 6}
```

showing any messages generated, and sorries with their goal states.

## File mode

There is a simple wrapper around command mode that allows reading in an entire file.

If `test/file.lean` contains
```lean
def f : Nat := 37

def g := 2

theorem h : f + g = 39 := by exact rfl
```

then
```
echo '{"path": "test/file.lean", "allTactics": true}' | lake exe repl
```
results in output
```json
{"tactics":
 [{"tactic": "exact rfl",
   "proofState": 0,
   "pos": {"line": 5, "column": 29},
   "goals": "⊢ f + g = 39",
   "endPos": {"line": 5, "column": 38}}],
 "env": 0}
 ```

## Tactic mode (experimental)

To enter tactic mode issue a command containing a `sorry`,
and then use the `proofState` index returned for each `sorry`.

Example usage:
```json
{"cmd" : "def f (x : Unit) : Nat := by sorry"}

{"sorries":
 [{"proofState": 0,
   "pos": {"line": 1, "column": 29},
   "goal": "x : Unit\n⊢ Nat",
   "endPos": {"line": 1, "column": 34}}],
 "messages":
 [{"severity": "warning",
   "pos": {"line": 1, "column": 4},
   "endPos": {"line": 1, "column": 5},
   "data": "declaration uses 'sorry'"}],
 "env": 0}

{"tactic": "apply Int.natAbs", "proofState": 0}

{"proofState": 1, "goals": ["x : Unit\n⊢ Int"]}

{"tactic": "exact -37", "proofState": 1}

{"proofState": 2, "goals": []}
```

You can use `sorry` in tactic mode.
The result will contain additional `proofState` identifiers for the goal at each sorry.

At present there is nothing you can do with a completed proof state:
we would like to extend this so that you can replace the original `sorry` with your tactic script,
and obtain the resulting `Environment`

## Pickling

The REPL supports pickling environments and proof states to disk as `.olean` files.
As long as the same imports are available, it should be possible to move such an `.olean` file
to another machine and unpickle into a new REPL session.

The commands are

```json
{"pickleTo": "path/to/file.olean", "env": 7}

{"pickleTo": "path/to/file.olean", "proofState": 17}

{"unpickleEnvFrom": "path/to/file.olean"}

{"unpickleProofStateFrom": "path/to/file.olean"}
```

The unpickling commands will report the new "env" or "proofState" identifier that
you can use in subsequent commands.

Pickling is quite efficient:
* we don't record full `Environment`s, only the changes relative to imports
* unpickling uses memory mapping
* file sizes are generally small, but see https://github.com/digama0/leangz if compression is
  desirable

## Using the REPL from another project

Set up your project as usual using `lake new` or `lake init`
(or the interactive setup GUI available via the VSCode extension under the `∀` menu).

In that project, add `require` statements in the `lakefile.lean` for any dependencies you need
(e.g. Mathlib). (You probably should verify that `lake build` works as expected in that project.)

Now you can run the REPL as:
```shell
lake env ../path/to/repl/.lake/build/bin/repl < commands.in
```
(Here `../path/to/repl/` represents the path to your checkout of this repository,
in which you've already run `lake build`.)

The `lake env` prefix sets up the environment associated to your local project, so that the REPL
can find needed imports.

## Future work

* Replay tactic scripts from tactic mode back into the original `sorry`.
* Currently if you create scoped environment extensions (e.g. scoped notations) in a session
  these are not correctly pickled and unpickled in later sessions.