frml 0.1.0__py3-none-any.whl
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.
- frml/__init__.py +3 -0
- frml/__main__.py +4 -0
- frml/ast_nodes.py +347 -0
- frml/cli.py +120 -0
- frml/errors.py +61 -0
- frml/interpreter.py +580 -0
- frml/lexer.py +187 -0
- frml/parser.py +428 -0
- frml/prover.py +785 -0
- frml/typechecker.py +484 -0
- frml/types.py +49 -0
- frml-0.1.0.dist-info/METADATA +258 -0
- frml-0.1.0.dist-info/RECORD +16 -0
- frml-0.1.0.dist-info/WHEEL +5 -0
- frml-0.1.0.dist-info/entry_points.txt +2 -0
- frml-0.1.0.dist-info/top_level.txt +1 -0
frml/__init__.py
ADDED
frml/__main__.py
ADDED
frml/ast_nodes.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Abstract-syntax-tree node definitions for Frml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
from .types import Type
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Node:
|
|
11
|
+
"""Base class carrying a source location."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, line: int, col: int):
|
|
14
|
+
self.line = line
|
|
15
|
+
self.col = col
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Top level
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Function(Node):
|
|
24
|
+
name: str
|
|
25
|
+
params: list[Parameter]
|
|
26
|
+
return_type: Type | None # None means a void procedure
|
|
27
|
+
requires: list[Expr]
|
|
28
|
+
ensures: list[Expr]
|
|
29
|
+
decreases: Expr | None
|
|
30
|
+
body: list[Stmt]
|
|
31
|
+
line: int = 0
|
|
32
|
+
col: int = 0
|
|
33
|
+
|
|
34
|
+
def __init__(self, name, params, return_type, requires, ensures, decreases, body, line, col):
|
|
35
|
+
Node.__init__(self, line, col)
|
|
36
|
+
self.name = name
|
|
37
|
+
self.params = params
|
|
38
|
+
self.return_type = return_type
|
|
39
|
+
self.requires = requires
|
|
40
|
+
self.ensures = ensures
|
|
41
|
+
self.decreases = decreases
|
|
42
|
+
self.body = body
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class Parameter(Node):
|
|
47
|
+
name: str
|
|
48
|
+
type: Type
|
|
49
|
+
line: int = 0
|
|
50
|
+
col: int = 0
|
|
51
|
+
|
|
52
|
+
def __init__(self, name: str, type_: Type, line: int, col: int):
|
|
53
|
+
Node.__init__(self, line, col)
|
|
54
|
+
self.name = name
|
|
55
|
+
self.type = type_
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class Program:
|
|
60
|
+
functions: list[Function] = field(default_factory=list)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# Statements
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Stmt(Node):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class StmtArrayAssign(Stmt):
|
|
74
|
+
array: Expr
|
|
75
|
+
index: Expr
|
|
76
|
+
value: Expr
|
|
77
|
+
line: int = 0
|
|
78
|
+
col: int = 0
|
|
79
|
+
|
|
80
|
+
def __init__(self, array, index, value, line, col):
|
|
81
|
+
Node.__init__(self, line, col)
|
|
82
|
+
self.array = array
|
|
83
|
+
self.index = index
|
|
84
|
+
self.value = value
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class StmtAssign(Stmt):
|
|
89
|
+
name: str
|
|
90
|
+
expr: Expr
|
|
91
|
+
line: int = 0
|
|
92
|
+
col: int = 0
|
|
93
|
+
|
|
94
|
+
def __init__(self, name, expr, line, col):
|
|
95
|
+
Node.__init__(self, line, col)
|
|
96
|
+
self.name = name
|
|
97
|
+
self.expr = expr
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class StmtAssert(Stmt):
|
|
102
|
+
expr: Expr
|
|
103
|
+
line: int = 0
|
|
104
|
+
col: int = 0
|
|
105
|
+
|
|
106
|
+
def __init__(self, expr, line, col):
|
|
107
|
+
Node.__init__(self, line, col)
|
|
108
|
+
self.expr = expr
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class StmtCall(Stmt):
|
|
113
|
+
name: str
|
|
114
|
+
args: list[Expr]
|
|
115
|
+
line: int = 0
|
|
116
|
+
col: int = 0
|
|
117
|
+
|
|
118
|
+
def __init__(self, name, args, line, col):
|
|
119
|
+
Node.__init__(self, line, col)
|
|
120
|
+
self.name = name
|
|
121
|
+
self.args = args
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class StmtIf(Stmt):
|
|
126
|
+
cond: Expr
|
|
127
|
+
then: list[Stmt]
|
|
128
|
+
else_: list[Stmt] | None
|
|
129
|
+
line: int = 0
|
|
130
|
+
col: int = 0
|
|
131
|
+
|
|
132
|
+
def __init__(self, cond, then, else_, line, col):
|
|
133
|
+
Node.__init__(self, line, col)
|
|
134
|
+
self.cond = cond
|
|
135
|
+
self.then = then
|
|
136
|
+
self.else_ = else_
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class StmtLet(Stmt):
|
|
141
|
+
name: str
|
|
142
|
+
type: Type
|
|
143
|
+
init: Expr
|
|
144
|
+
line: int = 0
|
|
145
|
+
col: int = 0
|
|
146
|
+
|
|
147
|
+
def __init__(self, name, type_, init, line, col):
|
|
148
|
+
Node.__init__(self, line, col)
|
|
149
|
+
self.name = name
|
|
150
|
+
self.type = type_
|
|
151
|
+
self.init = init
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class StmtReturn(Stmt):
|
|
156
|
+
expr: Expr
|
|
157
|
+
line: int = 0
|
|
158
|
+
col: int = 0
|
|
159
|
+
|
|
160
|
+
def __init__(self, expr, line, col):
|
|
161
|
+
Node.__init__(self, line, col)
|
|
162
|
+
self.expr = expr
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class StmtWhile(Stmt):
|
|
167
|
+
cond: Expr
|
|
168
|
+
invariants: list[Expr]
|
|
169
|
+
decreases: Expr | None
|
|
170
|
+
body: list[Stmt]
|
|
171
|
+
line: int = 0
|
|
172
|
+
col: int = 0
|
|
173
|
+
|
|
174
|
+
def __init__(self, cond, invariants, decreases, body, line, col):
|
|
175
|
+
Node.__init__(self, line, col)
|
|
176
|
+
self.cond = cond
|
|
177
|
+
self.invariants = invariants
|
|
178
|
+
self.decreases = decreases
|
|
179
|
+
self.body = body
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Expressions
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class Expr(Node):
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class ExprArrayAccess(Expr):
|
|
193
|
+
array: Expr
|
|
194
|
+
index: Expr
|
|
195
|
+
line: int = 0
|
|
196
|
+
col: int = 0
|
|
197
|
+
|
|
198
|
+
def __init__(self, array, index, line, col):
|
|
199
|
+
Node.__init__(self, line, col)
|
|
200
|
+
self.array = array
|
|
201
|
+
self.index = index
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class ExprArrayLiteral(Expr):
|
|
206
|
+
elements: list[Expr]
|
|
207
|
+
line: int = 0
|
|
208
|
+
col: int = 0
|
|
209
|
+
|
|
210
|
+
def __init__(self, elements, line, col):
|
|
211
|
+
Node.__init__(self, line, col)
|
|
212
|
+
self.elements = elements
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class ExprBinary(Expr):
|
|
217
|
+
op: str
|
|
218
|
+
left: Expr
|
|
219
|
+
right: Expr
|
|
220
|
+
line: int = 0
|
|
221
|
+
col: int = 0
|
|
222
|
+
|
|
223
|
+
def __init__(self, op, left, right, line, col):
|
|
224
|
+
Node.__init__(self, line, col)
|
|
225
|
+
self.op = op
|
|
226
|
+
self.left = left
|
|
227
|
+
self.right = right
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@dataclass
|
|
231
|
+
class ExprCall(Expr):
|
|
232
|
+
name: str
|
|
233
|
+
args: list[Expr]
|
|
234
|
+
line: int = 0
|
|
235
|
+
col: int = 0
|
|
236
|
+
|
|
237
|
+
def __init__(self, name, args, line, col):
|
|
238
|
+
Node.__init__(self, line, col)
|
|
239
|
+
self.name = name
|
|
240
|
+
self.args = args
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@dataclass
|
|
244
|
+
class ExprLength(Expr):
|
|
245
|
+
arg: Expr
|
|
246
|
+
line: int = 0
|
|
247
|
+
col: int = 0
|
|
248
|
+
|
|
249
|
+
def __init__(self, arg, line, col):
|
|
250
|
+
Node.__init__(self, line, col)
|
|
251
|
+
self.arg = arg
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@dataclass
|
|
255
|
+
class ExprOld(Expr):
|
|
256
|
+
arg: Expr
|
|
257
|
+
line: int = 0
|
|
258
|
+
col: int = 0
|
|
259
|
+
|
|
260
|
+
def __init__(self, arg, line, col):
|
|
261
|
+
Node.__init__(self, line, col)
|
|
262
|
+
self.arg = arg
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass
|
|
266
|
+
class ExprQuantifier(Expr):
|
|
267
|
+
quant: str # "forall" or "exists"
|
|
268
|
+
var_name: str
|
|
269
|
+
var_type: Type
|
|
270
|
+
body: Expr
|
|
271
|
+
line: int = 0
|
|
272
|
+
col: int = 0
|
|
273
|
+
|
|
274
|
+
def __init__(self, quant, var_name, var_type, body, line, col):
|
|
275
|
+
Node.__init__(self, line, col)
|
|
276
|
+
self.quant = quant
|
|
277
|
+
self.var_name = var_name
|
|
278
|
+
self.var_type = var_type
|
|
279
|
+
self.body = body
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass
|
|
283
|
+
class ExprStringify(Expr):
|
|
284
|
+
operand: Expr
|
|
285
|
+
line: int = 0
|
|
286
|
+
col: int = 0
|
|
287
|
+
|
|
288
|
+
def __init__(self, operand, line, col):
|
|
289
|
+
Node.__init__(self, line, col)
|
|
290
|
+
self.operand = operand
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass
|
|
294
|
+
class ExprUnary(Expr):
|
|
295
|
+
op: str
|
|
296
|
+
operand: Expr
|
|
297
|
+
line: int = 0
|
|
298
|
+
col: int = 0
|
|
299
|
+
|
|
300
|
+
def __init__(self, op, operand, line, col):
|
|
301
|
+
Node.__init__(self, line, col)
|
|
302
|
+
self.op = op
|
|
303
|
+
self.operand = operand
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class ExprVar(Expr):
|
|
308
|
+
name: str
|
|
309
|
+
line: int = 0
|
|
310
|
+
col: int = 0
|
|
311
|
+
|
|
312
|
+
def __init__(self, name, line, col):
|
|
313
|
+
Node.__init__(self, line, col)
|
|
314
|
+
self.name = name
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass
|
|
318
|
+
class LiteralBool(Expr):
|
|
319
|
+
value: bool
|
|
320
|
+
line: int = 0
|
|
321
|
+
col: int = 0
|
|
322
|
+
|
|
323
|
+
def __init__(self, value, line, col):
|
|
324
|
+
Node.__init__(self, line, col)
|
|
325
|
+
self.value = value
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@dataclass
|
|
329
|
+
class LiteralInt(Expr):
|
|
330
|
+
value: int
|
|
331
|
+
line: int = 0
|
|
332
|
+
col: int = 0
|
|
333
|
+
|
|
334
|
+
def __init__(self, value, line, col):
|
|
335
|
+
Node.__init__(self, line, col)
|
|
336
|
+
self.value = value
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@dataclass
|
|
340
|
+
class LiteralString(Expr):
|
|
341
|
+
value: str
|
|
342
|
+
line: int = 0
|
|
343
|
+
col: int = 0
|
|
344
|
+
|
|
345
|
+
def __init__(self, value, line, col):
|
|
346
|
+
Node.__init__(self, line, col)
|
|
347
|
+
self.value = value
|
frml/cli.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Command-line interface for Frml.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
frml check FILE.frml parse, type-check and verify
|
|
5
|
+
frml run FILE.frml type-check and execute main()
|
|
6
|
+
frml verify-run FILE.frml verify, then execute main()
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from . import ast_nodes as ast
|
|
14
|
+
from .errors import FrmlError
|
|
15
|
+
from .interpreter import Interpreter
|
|
16
|
+
from .parser import parse
|
|
17
|
+
from .prover import verify_program
|
|
18
|
+
from .typechecker import TypeChecker
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main(argv: list[str] | None = None) -> int:
|
|
22
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
23
|
+
if not argv:
|
|
24
|
+
print(__doc__.strip(), file=sys.stderr)
|
|
25
|
+
return 1
|
|
26
|
+
|
|
27
|
+
command = argv[0]
|
|
28
|
+
if command in ("-h", "--help", "help"):
|
|
29
|
+
print(__doc__.strip())
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
if command not in ("check", "run", "verify-run"):
|
|
33
|
+
print(f"unknown command {command!r}", file=sys.stderr)
|
|
34
|
+
print(__doc__.strip(), file=sys.stderr)
|
|
35
|
+
return 1
|
|
36
|
+
|
|
37
|
+
if len(argv) != 2:
|
|
38
|
+
print(f"usage: frml {command} FILE.frml", file=sys.stderr)
|
|
39
|
+
return 1
|
|
40
|
+
|
|
41
|
+
filename = argv[1]
|
|
42
|
+
if command == "check":
|
|
43
|
+
return do_check(filename)
|
|
44
|
+
if command == "run":
|
|
45
|
+
return do_run(filename)
|
|
46
|
+
return do_verify_run(filename)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def do_check(filename: str, timeout_ms: int = 10000) -> int:
|
|
50
|
+
try:
|
|
51
|
+
program = load_program(filename)
|
|
52
|
+
except FrmlError as e:
|
|
53
|
+
print(e.format(filename), file=sys.stderr)
|
|
54
|
+
return 1
|
|
55
|
+
|
|
56
|
+
_, outcomes = verify_program(program, timeout_ms=timeout_ms)
|
|
57
|
+
|
|
58
|
+
failed = [o for o in outcomes if o.status == "FAILED"]
|
|
59
|
+
unknown = [o for o in outcomes if o.status == "UNKNOWN"]
|
|
60
|
+
|
|
61
|
+
for o in failed:
|
|
62
|
+
print(_format_outcome(filename, o), file=sys.stderr)
|
|
63
|
+
for o in unknown:
|
|
64
|
+
print(_format_outcome(filename, o), file=sys.stderr)
|
|
65
|
+
|
|
66
|
+
if failed:
|
|
67
|
+
print("FAILED", file=sys.stderr)
|
|
68
|
+
return 1
|
|
69
|
+
if unknown:
|
|
70
|
+
print("UNKNOWN", file=sys.stderr)
|
|
71
|
+
return 2
|
|
72
|
+
print("VERIFIED")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def do_run(filename: str) -> int:
|
|
77
|
+
try:
|
|
78
|
+
program = load_program(filename)
|
|
79
|
+
except FrmlError as e:
|
|
80
|
+
print(e.format(filename), file=sys.stderr)
|
|
81
|
+
return 1
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
code = Interpreter(program).run_main()
|
|
85
|
+
except FrmlError as e:
|
|
86
|
+
print(e.format(filename), file=sys.stderr)
|
|
87
|
+
return 1
|
|
88
|
+
|
|
89
|
+
return int(code)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def do_verify_run(filename: str, timeout_ms: int = 10000) -> int:
|
|
93
|
+
status = do_check(filename, timeout_ms=timeout_ms)
|
|
94
|
+
if status != 0:
|
|
95
|
+
return status
|
|
96
|
+
return do_run(filename)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def load_program(filename: str) -> ast.Program:
|
|
100
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
101
|
+
source = f.read()
|
|
102
|
+
program = parse(source)
|
|
103
|
+
TypeChecker(program).check()
|
|
104
|
+
return program
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _format_outcome(filename: str, outcome) -> str:
|
|
108
|
+
ob = outcome.obligation
|
|
109
|
+
loc = filename
|
|
110
|
+
if ob.line is not None:
|
|
111
|
+
loc += f":{ob.line}"
|
|
112
|
+
if ob.col is not None:
|
|
113
|
+
loc += f":{ob.col}"
|
|
114
|
+
if outcome.status == "FAILED":
|
|
115
|
+
return f"{loc}: FAILED\n{ob.kind} may not hold:\n {ob.description}"
|
|
116
|
+
return f"{loc}: UNKNOWN\n{ob.kind} could not be decided:\n {ob.description}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__": # pragma: no cover
|
|
120
|
+
sys.exit(main())
|
frml/errors.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Exception types used throughout the Frml compiler, interpreter and prover."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FrmlError(Exception):
|
|
7
|
+
"""Base class for all Frml errors.
|
|
8
|
+
|
|
9
|
+
Each error carries a human-readable category (SyntaxError, TypeError, ...)
|
|
10
|
+
and an optional source location so the command-line tool can print
|
|
11
|
+
`file.frml:line:col: message` diagnostics.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
category = "Error"
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str, line: int | None = None, col: int | None = None):
|
|
17
|
+
self.message = message
|
|
18
|
+
self.line = line
|
|
19
|
+
self.col = col
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
|
|
22
|
+
def format(self, filename: str | None = None) -> str:
|
|
23
|
+
prefix = ""
|
|
24
|
+
if filename:
|
|
25
|
+
prefix = filename
|
|
26
|
+
if self.line is not None:
|
|
27
|
+
loc = str(self.line)
|
|
28
|
+
if self.col is not None:
|
|
29
|
+
loc += f":{self.col}"
|
|
30
|
+
prefix = f"{prefix}:{loc}" if prefix else loc
|
|
31
|
+
if prefix:
|
|
32
|
+
return f"{prefix}: {self.category}: {self.message}"
|
|
33
|
+
return f"{self.category}: {self.message}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FrmlContractError(FrmlError):
|
|
37
|
+
category = "ContractError"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FrmlNameError(FrmlError):
|
|
41
|
+
category = "NameError"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FrmlRuntimeError(FrmlError):
|
|
45
|
+
category = "RuntimeError"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FrmlSyntaxError(FrmlError):
|
|
49
|
+
category = "SyntaxError"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FrmlTerminationError(FrmlError):
|
|
53
|
+
category = "TerminationError"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FrmlTypeError(FrmlError):
|
|
57
|
+
category = "TypeError"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FrmlVerificationError(FrmlError):
|
|
61
|
+
category = "VerificationError"
|