frml 0.1.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.
frml-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: frml
3
+ Version: 0.1.0
4
+ Summary: A minimal contract-based verification language backed by Z3
5
+ License: MIT
6
+ Project-URL: Repository, https://github.com/gvwilson/mccole
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: z3-solver>=4.12.0
10
+
11
+ # Tiny
12
+
13
+ Tiny is a small, statically typed, contract-based programming language for
14
+ teaching formal verification. It feels like a simplified combination of Python
15
+ and Dafny: ordinary imperative programs, plus `requires` / `ensures` contracts,
16
+ `assert`, loop `invariant`s, `decreases` termination measures, `old(...)`, and
17
+ `forall` / `exists` quantifiers. Verification is fully automatic: Tiny
18
+ translates each program into verification conditions and asks the [Z3][z3] SMT
19
+ solver to check them. There are no interactive proof tactics and no handwritten
20
+ SMT formulas.
21
+
22
+ A program that verifies looks like this:
23
+
24
+ ```
25
+ fn abs(x: Int) -> Int
26
+ ensures result >= 0
27
+ {
28
+ if x >= 0 {
29
+ return x;
30
+ } else {
31
+ return -x;
32
+ }
33
+ }
34
+
35
+ fn main() -> Int
36
+ {
37
+ return abs(-7);
38
+ }
39
+ ```
40
+
41
+ Running the verifier prints `VERIFIED`; running the program prints nothing and
42
+ uses `main`'s return value as the process exit code.
43
+
44
+ ## Installation
45
+
46
+ Tiny requires:
47
+
48
+ - Python 3.13 or newer, and
49
+ - the Python `z3` bindings (`z3-solver`).
50
+
51
+ ### 1. Install the Z3 Python bindings
52
+
53
+ ```bash
54
+ uv venv
55
+ source .venv/bin/activate
56
+ uv add z3-solver
57
+ ```
58
+
59
+ ### 2. Install Tiny
60
+
61
+ From this directory:
62
+
63
+ ```bash
64
+ uv pip install -e .
65
+ ```
66
+
67
+ This installs a `tiny` command on your `PATH`. You can also run Tiny without
68
+ installing it, using the module form:
69
+
70
+ ```bash
71
+ python3 -m tiny check examples/abs.tiny
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ Tiny exposes three subcommands.
77
+
78
+ ### `tiny check FILE.tiny`
79
+
80
+ Parse, type-check, and verify the program. Prints `VERIFIED` on success, or
81
+ `FAILED`/`UNKNOWN` with source locations on failure.
82
+
83
+ ### `tiny run FILE.tiny`
84
+
85
+ Type-check the program (without static verification) and execute `main()`. The
86
+ exit status is `main`'s return value.
87
+
88
+ ### `tiny verify-run FILE.tiny`
89
+
90
+ Verify first, and then execute `main()` if verification succeeded.
91
+
92
+ ## The language
93
+
94
+ ### Types
95
+
96
+ Tiny has three basic types:
97
+
98
+ ```
99
+ Int arbitrary-precision integers (no overflow)
100
+ Bool true or false
101
+ String a sequence of characters, written in double quotes
102
+ ```
103
+
104
+ Tiny supports homogeneous one-dimensional arrays:
105
+
106
+ ```
107
+ Array<Int> mutable, fixed-length, zero-indexed arrays
108
+ Array<Bool> mutable, fixed-length, zero-indexed arrays
109
+ Array<String> mutable, fixed-length, zero-indexed arrays of strings
110
+ ```
111
+
112
+ Nested arrays (`Array<Array<Int>>`) are not allowed, and Tiny does not (yet)
113
+ have a mapping (dictionary) type.
114
+
115
+ ### Functions
116
+
117
+ ```
118
+ fn max(a: Int, b: Int) -> Int
119
+ ensures result >= a
120
+ ensures result >= b
121
+ ensures result == a or result == b
122
+ {
123
+ if a >= b {
124
+ return a;
125
+ } else {
126
+ return b;
127
+ }
128
+ }
129
+ ```
130
+
131
+ - `requires` clauses are assumptions about the caller's inputs.
132
+ - `ensures` clauses are guarantees about the result.
133
+ - `result` names the return value.
134
+ - `old(e)` refers to the value of `e` at function entry.
135
+ - Multiple `requires`/`ensures` clauses are implicitly ANDed.
136
+ - A function may omit `-> TYPE`, making it a void procedure:
137
+
138
+ ```
139
+ fn increment_first(a: Array<Int>)
140
+ requires length(a) > 0
141
+ ensures a[0] == old(a[0]) + 1
142
+ {
143
+ a[0] = a[0] + 1;
144
+ }
145
+ ```
146
+
147
+ - Recursive functions must declare a `decreases` measure.
148
+
149
+ ### Statements
150
+
151
+ ```
152
+ let NAME: TYPE = expr; // local variable declaration
153
+ NAME = expr; // scalar assignment
154
+ a[i] = expr; // array element assignment
155
+ if expr { ... } else { ... } // conditional (else optional)
156
+ while expr
157
+ invariant expr // loop invariants (optional)
158
+ decreases expr // termination measure (optional)
159
+ { ... }
160
+ return expr; // (value-returning functions only)
161
+ assert expr; // runtime + static assertion
162
+ f(args); // procedure call statement
163
+ ```
164
+
165
+ ### Expressions
166
+
167
+ Operators, from lowest to highest precedence: `=>`, `or`, `and`, `==`/`!=`,
168
+ `<`/`<=`/`>`/`>=`, `+`/`-`/`++`, `*`/`/`/`%`, unary `!`/`-`/`` ` ``, then indexing
169
+ and calls.
170
+
171
+ `/` and `%` use Euclidean integer division compatible with Z3 (the remainder is
172
+ always non-negative). Division and modulo by zero are runtime errors and
173
+ verification obligations.
174
+
175
+ String literals are written in double quotes and support the usual C-style
176
+ backslash escapes (`\n`, `\t`, `\"`, `\\`, and so on). The `++` operator
177
+ concatenates two strings, and a single backtick before a value converts it to a
178
+ string (`5` is `"5"`, `x` is the string form of `x`'s value):
179
+
180
+ ```
181
+ let greeting: String = "hello" ++ " " ++ "world";
182
+ let n: Int = 42;
183
+ let label: String = `n; // "42"
184
+ ```
185
+
186
+ Quantifiers are available in specifications:
187
+
188
+ ```
189
+ forall i: Int :: 0 <= i and i < length(a) => a[i] >= 0
190
+ exists i: Int :: 0 <= i and i < length(a) and a[i] == 0
191
+ ```
192
+
193
+ `length(a)` is the single built-in function.
194
+
195
+ ## Examples
196
+
197
+ The `examples/` directory contains small programs:
198
+
199
+ - `abs.tiny`, `max.tiny`, `count.tiny`: the core required examples.
200
+ - `required.tiny`: all three core examples plus a `main`.
201
+ - `factorial.tiny`: recursive function with `decreases`.
202
+ - `all_nonnegative.tiny`: array property proved with a quantifier loop invariant.
203
+ - `increment_first.tiny`: a procedure mutating an array, with `old`.
204
+ - `bad.tiny`: a deliberately unprovable postcondition (`FAILED`).
205
+ - `precondition.tiny`: a runtime precondition violation.
206
+
207
+ Try them:
208
+
209
+ ```bash
210
+ tiny check examples/required.tiny
211
+ tiny verify-run examples/factorial.tiny
212
+ tiny check examples/bad.tiny
213
+ tiny run examples/precondition.tiny
214
+ ```
215
+
216
+ ## Intentional limitations
217
+
218
+ These choices keep the verifier sound and the implementation small; they match
219
+ the specification's own "simplest recommended model":
220
+
221
+ Arrays are references, not values.
222
+ : `let b: Array<Int> = a;` (array-to-array assignment) is rejected to avoid
223
+ aliasing ambiguity. Create fresh arrays with array literals instead.
224
+
225
+ Functions return `Int` or `Bool`.
226
+ : Array-returning functions are not supported. Array mutation is expressed
227
+ with procedures, as shown earlier.
228
+
229
+ A call that takes array arguments must appear on its own.
230
+ : It may be a statement, or the whole right-hand side of, `let`, `return`, or
231
+ assignment. It may not be nested inside a larger expression (`f(a) + g(a)`
232
+ is rejected during verification).
233
+
234
+ Runtime quantifier checking is best-effort.
235
+ : Quantified postconditions over a finite array index range (`0 <= i and i <
236
+ length(a)`) are evaluated at runtime. Other quantified expressions are
237
+ skipped during runtime checking but still fully verified statically.
238
+
239
+ The verifier never claims success on a program it cannot prove. If Z3 cannot
240
+ decide an obligation it reports `UNKNOWN`, and a genuinely unprovable program is
241
+ reported as `FAILED`: it is never silently accepted.
242
+
243
+ ## Project layout
244
+
245
+ ```
246
+ tiny/
247
+ errors.py error types and source-location formatting
248
+ lexer.py tokenizer
249
+ parser.py recursive-descent parser -> AST
250
+ ast_nodes.py AST node definitions
251
+ types.py Int / Bool / Array<T> types
252
+ typechecker.py name resolution + static type checking
253
+ interpreter.py concrete executor with runtime checks
254
+ prover.py verification-condition generation + Z3 proof checking
255
+ cli.py the `tiny` command-line interface
256
+ ```
257
+
258
+ [z3]: https://github.com/Z3Prover/z3
frml-0.1.0/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # Tiny
2
+
3
+ Tiny is a small, statically typed, contract-based programming language for
4
+ teaching formal verification. It feels like a simplified combination of Python
5
+ and Dafny: ordinary imperative programs, plus `requires` / `ensures` contracts,
6
+ `assert`, loop `invariant`s, `decreases` termination measures, `old(...)`, and
7
+ `forall` / `exists` quantifiers. Verification is fully automatic: Tiny
8
+ translates each program into verification conditions and asks the [Z3][z3] SMT
9
+ solver to check them. There are no interactive proof tactics and no handwritten
10
+ SMT formulas.
11
+
12
+ A program that verifies looks like this:
13
+
14
+ ```
15
+ fn abs(x: Int) -> Int
16
+ ensures result >= 0
17
+ {
18
+ if x >= 0 {
19
+ return x;
20
+ } else {
21
+ return -x;
22
+ }
23
+ }
24
+
25
+ fn main() -> Int
26
+ {
27
+ return abs(-7);
28
+ }
29
+ ```
30
+
31
+ Running the verifier prints `VERIFIED`; running the program prints nothing and
32
+ uses `main`'s return value as the process exit code.
33
+
34
+ ## Installation
35
+
36
+ Tiny requires:
37
+
38
+ - Python 3.13 or newer, and
39
+ - the Python `z3` bindings (`z3-solver`).
40
+
41
+ ### 1. Install the Z3 Python bindings
42
+
43
+ ```bash
44
+ uv venv
45
+ source .venv/bin/activate
46
+ uv add z3-solver
47
+ ```
48
+
49
+ ### 2. Install Tiny
50
+
51
+ From this directory:
52
+
53
+ ```bash
54
+ uv pip install -e .
55
+ ```
56
+
57
+ This installs a `tiny` command on your `PATH`. You can also run Tiny without
58
+ installing it, using the module form:
59
+
60
+ ```bash
61
+ python3 -m tiny check examples/abs.tiny
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ Tiny exposes three subcommands.
67
+
68
+ ### `tiny check FILE.tiny`
69
+
70
+ Parse, type-check, and verify the program. Prints `VERIFIED` on success, or
71
+ `FAILED`/`UNKNOWN` with source locations on failure.
72
+
73
+ ### `tiny run FILE.tiny`
74
+
75
+ Type-check the program (without static verification) and execute `main()`. The
76
+ exit status is `main`'s return value.
77
+
78
+ ### `tiny verify-run FILE.tiny`
79
+
80
+ Verify first, and then execute `main()` if verification succeeded.
81
+
82
+ ## The language
83
+
84
+ ### Types
85
+
86
+ Tiny has three basic types:
87
+
88
+ ```
89
+ Int arbitrary-precision integers (no overflow)
90
+ Bool true or false
91
+ String a sequence of characters, written in double quotes
92
+ ```
93
+
94
+ Tiny supports homogeneous one-dimensional arrays:
95
+
96
+ ```
97
+ Array<Int> mutable, fixed-length, zero-indexed arrays
98
+ Array<Bool> mutable, fixed-length, zero-indexed arrays
99
+ Array<String> mutable, fixed-length, zero-indexed arrays of strings
100
+ ```
101
+
102
+ Nested arrays (`Array<Array<Int>>`) are not allowed, and Tiny does not (yet)
103
+ have a mapping (dictionary) type.
104
+
105
+ ### Functions
106
+
107
+ ```
108
+ fn max(a: Int, b: Int) -> Int
109
+ ensures result >= a
110
+ ensures result >= b
111
+ ensures result == a or result == b
112
+ {
113
+ if a >= b {
114
+ return a;
115
+ } else {
116
+ return b;
117
+ }
118
+ }
119
+ ```
120
+
121
+ - `requires` clauses are assumptions about the caller's inputs.
122
+ - `ensures` clauses are guarantees about the result.
123
+ - `result` names the return value.
124
+ - `old(e)` refers to the value of `e` at function entry.
125
+ - Multiple `requires`/`ensures` clauses are implicitly ANDed.
126
+ - A function may omit `-> TYPE`, making it a void procedure:
127
+
128
+ ```
129
+ fn increment_first(a: Array<Int>)
130
+ requires length(a) > 0
131
+ ensures a[0] == old(a[0]) + 1
132
+ {
133
+ a[0] = a[0] + 1;
134
+ }
135
+ ```
136
+
137
+ - Recursive functions must declare a `decreases` measure.
138
+
139
+ ### Statements
140
+
141
+ ```
142
+ let NAME: TYPE = expr; // local variable declaration
143
+ NAME = expr; // scalar assignment
144
+ a[i] = expr; // array element assignment
145
+ if expr { ... } else { ... } // conditional (else optional)
146
+ while expr
147
+ invariant expr // loop invariants (optional)
148
+ decreases expr // termination measure (optional)
149
+ { ... }
150
+ return expr; // (value-returning functions only)
151
+ assert expr; // runtime + static assertion
152
+ f(args); // procedure call statement
153
+ ```
154
+
155
+ ### Expressions
156
+
157
+ Operators, from lowest to highest precedence: `=>`, `or`, `and`, `==`/`!=`,
158
+ `<`/`<=`/`>`/`>=`, `+`/`-`/`++`, `*`/`/`/`%`, unary `!`/`-`/`` ` ``, then indexing
159
+ and calls.
160
+
161
+ `/` and `%` use Euclidean integer division compatible with Z3 (the remainder is
162
+ always non-negative). Division and modulo by zero are runtime errors and
163
+ verification obligations.
164
+
165
+ String literals are written in double quotes and support the usual C-style
166
+ backslash escapes (`\n`, `\t`, `\"`, `\\`, and so on). The `++` operator
167
+ concatenates two strings, and a single backtick before a value converts it to a
168
+ string (`5` is `"5"`, `x` is the string form of `x`'s value):
169
+
170
+ ```
171
+ let greeting: String = "hello" ++ " " ++ "world";
172
+ let n: Int = 42;
173
+ let label: String = `n; // "42"
174
+ ```
175
+
176
+ Quantifiers are available in specifications:
177
+
178
+ ```
179
+ forall i: Int :: 0 <= i and i < length(a) => a[i] >= 0
180
+ exists i: Int :: 0 <= i and i < length(a) and a[i] == 0
181
+ ```
182
+
183
+ `length(a)` is the single built-in function.
184
+
185
+ ## Examples
186
+
187
+ The `examples/` directory contains small programs:
188
+
189
+ - `abs.tiny`, `max.tiny`, `count.tiny`: the core required examples.
190
+ - `required.tiny`: all three core examples plus a `main`.
191
+ - `factorial.tiny`: recursive function with `decreases`.
192
+ - `all_nonnegative.tiny`: array property proved with a quantifier loop invariant.
193
+ - `increment_first.tiny`: a procedure mutating an array, with `old`.
194
+ - `bad.tiny`: a deliberately unprovable postcondition (`FAILED`).
195
+ - `precondition.tiny`: a runtime precondition violation.
196
+
197
+ Try them:
198
+
199
+ ```bash
200
+ tiny check examples/required.tiny
201
+ tiny verify-run examples/factorial.tiny
202
+ tiny check examples/bad.tiny
203
+ tiny run examples/precondition.tiny
204
+ ```
205
+
206
+ ## Intentional limitations
207
+
208
+ These choices keep the verifier sound and the implementation small; they match
209
+ the specification's own "simplest recommended model":
210
+
211
+ Arrays are references, not values.
212
+ : `let b: Array<Int> = a;` (array-to-array assignment) is rejected to avoid
213
+ aliasing ambiguity. Create fresh arrays with array literals instead.
214
+
215
+ Functions return `Int` or `Bool`.
216
+ : Array-returning functions are not supported. Array mutation is expressed
217
+ with procedures, as shown earlier.
218
+
219
+ A call that takes array arguments must appear on its own.
220
+ : It may be a statement, or the whole right-hand side of, `let`, `return`, or
221
+ assignment. It may not be nested inside a larger expression (`f(a) + g(a)`
222
+ is rejected during verification).
223
+
224
+ Runtime quantifier checking is best-effort.
225
+ : Quantified postconditions over a finite array index range (`0 <= i and i <
226
+ length(a)`) are evaluated at runtime. Other quantified expressions are
227
+ skipped during runtime checking but still fully verified statically.
228
+
229
+ The verifier never claims success on a program it cannot prove. If Z3 cannot
230
+ decide an obligation it reports `UNKNOWN`, and a genuinely unprovable program is
231
+ reported as `FAILED`: it is never silently accepted.
232
+
233
+ ## Project layout
234
+
235
+ ```
236
+ tiny/
237
+ errors.py error types and source-location formatting
238
+ lexer.py tokenizer
239
+ parser.py recursive-descent parser -> AST
240
+ ast_nodes.py AST node definitions
241
+ types.py Int / Bool / Array<T> types
242
+ typechecker.py name resolution + static type checking
243
+ interpreter.py concrete executor with runtime checks
244
+ prover.py verification-condition generation + Z3 proof checking
245
+ cli.py the `tiny` command-line interface
246
+ ```
247
+
248
+ [z3]: https://github.com/Z3Prover/z3
@@ -0,0 +1,3 @@
1
+ """Frml: a minimal contract-based verification language."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())