Skip to content

Commit 393ec51

Browse files
authored
Merge pull request #1399 from informalsystems/1309/to-tla
2 parents 0912fea + f10ab04 commit 393ec51

8 files changed

+287
-25
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added polymorphic type declarations, allowing abstracting commonly used data
1313
types like `Option[a]` and `Result[err, ok]`. Note that this is not yet
1414
supported by `verify`. (#1298)
15+
- Added `compile` subcommand, allowing compiling specs to TLA+ (via Apalache)
16+
and to a JSON format. (#1309, #359)
1517

1618
### Changed
1719

doc/quint.md

+34
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and integration with other tools.
1414
The main commands of `quint` are as follows:
1515

1616
- [x] `repl` starts the REPL (Read-Eval-Print loop) for Quint
17+
- [x] `compile` parses, typechecks, and processes a quint specification
18+
into the `--target` format, writing the results to stdout
1719
- [x] `parse` parses a Quint specification and resolves names
1820
- [x] `typecheck` infers types in a Quint specification
1921
- [x] `run` executes a Quint specification via random simulation
@@ -65,6 +67,38 @@ The REPL is especially useful for learning the language. See the
6567
The verbosity levels 3 and 4 are used to show execution details. They are
6668
especially useful for debugging complex specifications.
6769

70+
## Command `compile`
71+
72+
```sh
73+
$ quint compile --help
74+
quint compile <input>
75+
76+
compile a Quint specification into the target, the output is written to
77+
stdout
78+
79+
Options:
80+
--help Show help [boolean]
81+
--version Show version number [boolean]
82+
--out output file (suppresses all console output) [string]
83+
--main name of the main module (by default, computed from filename)
84+
[string]
85+
--target the compilation target. Supported values: tlaplus, json
86+
[string] [default: "json"]
87+
--verbosity control how much output is produced (0 to 5)[number] [default: 2]
88+
```
89+
90+
Given a quint specification as input, this command parses, resolves imports,
91+
typechecks, and then "flattens" the specification into on module containing just
92+
the needed definitions.
93+
94+
The following compilation targets are supported
95+
96+
- `json`: The default target, this produces a json representation, in the same
97+
format which is described under [`parse`](#command-parse) and
98+
[`typecheck`](#command-typecheck), below.
99+
- `tlaplus`: Quint uses its integration with Apalache to compile the
100+
specification into TLA+.
101+
68102
## Command `parse`
69103

70104
*Warning: The language is still in active development, and breaking changes are

quint/apalache-tests.md

+51
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,54 @@ An example execution:
219219
__saved_n: 1,
220220
n: 1
221221
```
222+
223+
## Compiling to TLA+
224+
225+
### Test that we can compile to TLA+ of the expected form
226+
227+
<!-- !test in can convert booleans.qnt to TLA+ -->
228+
```
229+
quint compile --target tlaplus ../examples/language-features/booleans.qnt
230+
```
231+
232+
<!-- !test out can convert booleans.qnt to TLA+ -->
233+
```
234+
------------------------------- MODULE booleans -------------------------------
235+
236+
EXTENDS Integers, Sequences, FiniteSets, TLC, Apalache
237+
238+
VARIABLE b
239+
240+
step ==
241+
(b \/ TRUE)
242+
/\ ~(b /\ FALSE)
243+
/\ (b => b)
244+
/\ (b <=> b)
245+
/\ b = b
246+
/\ b /= (~b)
247+
/\ b' := (~b)
248+
249+
init == b' := TRUE
250+
251+
================================================================================
252+
```
253+
254+
### Test that we can compile a module with imports and instances to TLA+
255+
256+
257+
<!-- !test in can convert clockSync3.qnt to TLA+ -->
258+
```
259+
quint compile --target tlaplus ../examples/classic/distributed/ClockSync/clockSync3.qnt | head
260+
```
261+
262+
263+
TODO: This is an incorrect result, we are removing all "unused" declarations
264+
which leaves nothing, thanks to the way clockSync3 is instanced.
265+
<!-- !test out can convert clockSync3.qnt to TLA+ -->
266+
```
267+
------------------------------ MODULE clockSync3 ------------------------------
268+
269+
EXTENDS Integers, Sequences, FiniteSets, TLC, Apalache
270+
271+
================================================================================
272+
```

quint/io-cli-tests.md

+32
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,38 @@ Trying to unify bool and int
171171
error: typechecking failed
172172
```
173173

174+
## The `compile` commaind
175+
176+
### Reports in error for invalid `--target`
177+
178+
We pipe stderr to `tail` here. Following https://stackoverflow.com/a/52575213/1187277
179+
This is a clean CLI interface error, but we don't want to put the entire output
180+
in the test, lest it require fiddling when unrelated things are updated.
181+
182+
<!-- !test exit 1 -->
183+
<!-- !test in compile to invalid target -->
184+
```
185+
quint compile --target invalidTarget ../examples/language-features/booleans.qnt 2> >(tail -1 >&2)
186+
```
187+
188+
<!-- !test err compile to invalid target -->
189+
```
190+
Invalid option for --target: invalidTarget. Valid options: tlaplus, json
191+
```
192+
193+
194+
### Can compile `booleans.qnt` to JSON
195+
196+
<!-- !test in compile booleans.qnt to json -->
197+
```
198+
quint compile --target json ../examples/language-features/booleans.qnt | jq '.modules[0].name'
199+
```
200+
201+
<!-- !test out compile booleans.qnt to json -->
202+
```
203+
"booleans"
204+
```
205+
174206
## Use of `repl`, `test`, and `run`
175207

176208
### Repl loads a file with -r

quint/src/apalache.ts

+32-21
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ type ApalacheConfig = any
6969
type Apalache = {
7070
// Run the check command with the given configuration
7171
check: (c: ApalacheConfig) => Promise<ApalacheResult<void>>
72+
// Convert the provided input into formatted TLA
73+
tla: (c: ApalacheConfig) => Promise<ApalacheResult<string>>
7274
}
7375

7476
function handleVerificationFailure(failure: { pass_name: string; error_data: any }): ApalacheError {
@@ -97,28 +99,36 @@ function handleVerificationFailure(failure: { pass_name: string; error_data: any
9799
}
98100
}
99101

102+
async function handleResponse(response: RunResponse): Promise<ApalacheResult<any>> {
103+
if (response.result == 'success') {
104+
const success = JSON.parse(response.success)
105+
return right(success)
106+
} else {
107+
switch (response.failure.errorType) {
108+
case 'UNEXPECTED': {
109+
const errData = JSON.parse(response.failure.data)
110+
return err(errData.msg)
111+
}
112+
case 'PASS_FAILURE':
113+
return left(handleVerificationFailure(JSON.parse(response.failure.data)))
114+
default:
115+
// TODO handle other error cases
116+
return err(`${response.failure.errorType}: ${response.failure.data}`)
117+
}
118+
}
119+
}
120+
100121
// Construct the Apalache interface around the cmdExecutor
101122
function apalache(cmdExecutor: AsyncCmdExecutor): Apalache {
102123
const check = async (c: ApalacheConfig): Promise<ApalacheResult<void>> => {
103-
const response = await cmdExecutor.run({ cmd: 'CHECK', config: JSON.stringify(c) })
104-
if (response.result == 'success') {
105-
return right(void 0)
106-
} else {
107-
switch (response.failure.errorType) {
108-
case 'UNEXPECTED': {
109-
const errData = JSON.parse(response.failure.data)
110-
return err(errData.msg)
111-
}
112-
case 'PASS_FAILURE':
113-
return left(handleVerificationFailure(JSON.parse(response.failure.data)))
114-
default:
115-
// TODO handle other error cases
116-
return err(`${response.failure.errorType}: ${response.failure.data}`)
117-
}
118-
}
124+
return cmdExecutor.run({ cmd: 'CHECK', config: JSON.stringify(c) }).then(handleResponse)
125+
}
126+
127+
const tla = async (c: ApalacheConfig): Promise<ApalacheResult<string>> => {
128+
return cmdExecutor.run({ cmd: 'TLA', config: JSON.stringify(c) }).then(handleResponse)
119129
}
120130

121-
return { check }
131+
return { check, tla }
122132
}
123133

124134
// Alias for an async callback for values of type T used to annotate
@@ -127,17 +137,18 @@ function apalache(cmdExecutor: AsyncCmdExecutor): Apalache {
127137
type AsyncCallBack<T> = (err: any, result: T) => void
128138

129139
// The core grpc tooling doesn't support generation of typing info,
130-
// we therefore record the structer we require from the grpc generation
131-
// in the 6 following types.
140+
// we therefore record the structure we require from the grpc generation
141+
// in the following types.
132142
//
133143
// The types reflect https://github.com/informalsystems/apalache/blob/main/shai/src/main/protobuf/cmdExecutor.proto
134144

135145
type RunRequest = { cmd: string; config: string }
136146

137147
type RunResponse =
138148
| { result: 'failure'; failure: { errorType: string; data: string } }
139-
// The success data also includes the parsed module, but we don't need it
140-
| { result: 'success' }
149+
// The success data also includes the parsed module, either as JSON
150+
// representing the Apalache IR, or as a TLA string (if the `TLA` command is used)
151+
| { result: 'success'; success: string }
141152

142153
// The interface for the CmdExecutor service generated by the gRPC library
143154
type CmdExecutor = {

quint/src/cli.ts

+35-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
* @author Igor Konnov, Gabriela Moreira, Shon Feder, Informal Systems, 2021-2023
1010
*/
1111

12+
import { fail } from 'assert'
1213
import yargs from 'yargs/yargs'
1314

1415
import {
1516
CLIProcedure,
17+
compile,
1618
docs,
1719
load,
1820
outputResult,
@@ -58,7 +60,7 @@ const parseCmd = {
5860
desc: 'name of the source map',
5961
type: 'string',
6062
}),
61-
handler: async (args: any) => load(args).then(chainCmd(parse)).then(outputResult),
63+
handler: (args: any) => load(args).then(chainCmd(parse)).then(outputResult),
6264
}
6365

6466
// construct typecheck commands with yargs
@@ -69,6 +71,37 @@ const typecheckCmd = {
6971
handler: (args: any) => load(args).then(chainCmd(parse)).then(chainCmd(typecheck)).then(outputResult),
7072
}
7173

74+
const supportedTarges = ['tlaplus', 'json']
75+
// construct the compile subcommand
76+
const compileCmd = {
77+
command: 'compile <input>',
78+
desc: 'compile a Quint specification into the target, the output is written to stdout',
79+
builder: (yargs: any) =>
80+
defaultOpts(yargs)
81+
.option('main', {
82+
desc: 'name of the main module (by default, computed from filename)',
83+
type: 'string',
84+
})
85+
.option('target', {
86+
desc: `the compilation target. Supported values: ${supportedTarges.join(', ')}`,
87+
type: 'string',
88+
default: 'json',
89+
})
90+
.coerce('target', (target: string): string => {
91+
if (!supportedTarges.includes(target)) {
92+
fail(`Invalid option for --target: ${target}. Valid options: ${supportedTarges.join(', ')}`)
93+
}
94+
return target
95+
})
96+
.option('verbosity', {
97+
desc: 'control how much output is produced (0 to 5)',
98+
type: 'number',
99+
default: verbosity.defaultLevel,
100+
}),
101+
handler: (args: any) =>
102+
load(args).then(chainCmd(parse)).then(chainCmd(typecheck)).then(chainCmd(compile)).then(outputResult),
103+
}
104+
72105
// construct repl commands with yargs
73106
const replCmd = {
74107
command: ['repl', '*'],
@@ -292,6 +325,7 @@ async function main() {
292325
await yargs(process.argv.slice(2))
293326
.command(parseCmd)
294327
.command(typecheckCmd)
328+
.command(compileCmd)
295329
.command(replCmd)
296330
.command(runCmd)
297331
.command(testCmd)

0 commit comments

Comments
 (0)