Examples
Real programs,
really proved.
Every program on this site is a project that the current
byronic proves and
runs, and every terminal shows what it printed. Nothing is
retouched.
Leon and Noel
Pipelines with the invariants written for you
Reductions, filters, quantifiers and collections over arrays. In
Leon they're method chains, A.filter(X => X >= Mark).sum();
in Noel they're Ada 2022's 'Reduce and iterated
aggregates. Each pipeline becomes one fused loop whose invariants
Byron generates, so overflow and index checks are proved without a
single annotation. Where you write the loop yourself, as in
Running_Total, it is proved against the reduction it
computes.
// Pipelines over arrays: reductions, filters, quantifiers and collections, each one
// fused loop with generated invariants; reductions in contracts mean ghost folds.
package Stats {
pub subtype Score = Integer range 0..=100;
pub type Scores = [Positive..]Score;
// the sum, and the sum of those that pass
pub fn Total(A: Scores) -> Integer
requires A::Length <= 1000
= A::Reduce("+", 0);
pub fn Passing_Total(A: Scores, Mark: Score) -> Integer
requires A::Length <= 1000
= A.filter(X => X >= Mark).sum();
// how many pass: each counts one
pub fn Passing(A: Scores, Mark: Score) -> Natural
requires A::Length <= 1000
= A.filter(X => X >= Mark).count();
// a range's values, mapped (the range's bounds bound its values)
pub subtype Count = Natural range 0..=100;
pub fn Sum_Of_Squares(N: Count) -> Natural = [for I in 1..=N => I * I]::Reduce("+", 0);
// the largest, by a function reducer
pub fn Best(A: Scores) -> Score = A::Reduce(Score::Max, 0);
// quantifiers, with and without a filter
pub fn All_Valid(A: Scores) -> Boolean = A.all(X => X <= 100);
pub fn Any_Perfect(A: Scores) -> Boolean = A.any(X => X == 100);
// the scores that pass, collected into an array as long as their number
pub fn Above(A: Scores, Mark: Score) -> Scores
requires A::Length <= 1000
= A.filter(X => X > Mark).collect();
// each score curved, collected; nothing filtered, so as long as the source
pub fn Curved(A: Scores) -> Scores
requires (A::First == 1) & (A::Length <= 1000)
ensures result::Length == A::Length
= A.map(X => (X + 100) / 2).collect();
// a loop the programmer writes, proved against the reduction it computes
pub fn Running_Total(A: Scores) -> Integer
requires A::Length <= 1000
ensures result == A::Reduce("+", 0)
{
var S: Integer = 0;
for I in A::Range {
invariant (S == [for J in A::First..=I - 1 => A[J]]::Reduce("+", 0)) & (S <= 100 * (I - A::First));
S = S + A[I];
}
return S;
}
} import Ada.Text_IO;
use Ada.Text_IO;
import Stats;
use Stats;
fn Main() {
let Class: Scores = (72, 45, 100, 88, 61, 39, 95);
let High: Scores = Above(Class, 80);
let Fair: Scores = Curved(Class);
Put_Line("total" ++ Integer::Image(Total(Class)) ++ Integer::Image(Running_Total(Class)));
Put_Line("passing" ++ Integer::Image(Passing(Class, 60)) ++ Integer::Image(Passing_Total(Class, 60)));
Put_Line("squares" ++ Integer::Image(Sum_Of_Squares(10)));
Put_Line("best" ++ Integer::Image(Best(Class)));
Put_Line("valid " ++ Boolean::Image(All_Valid(Class)) ++ ", perfect " ++ Boolean::Image(Any_Perfect(Class)));
Put("above 80:");
for S in High {
Put(Integer::Image(S));
}
New_Line();
Put("curved:");
for S in Fair {
Put(Integer::Image(S));
}
New_Line();
} -- Pipelines over arrays: reductions, filters, quantifiers and collections, each one
-- fused loop with generated invariants; reductions in contracts mean ghost folds.
package Stats is
subtype Score is Integer range 0 .. 100;
type Scores is array (Positive range <>) of Score;
-- the sum, and the sum of those that pass
function Total (A : Scores) return Integer is (A'Reduce ("+", 0))
with Pre => A'Length <= 1000;
function Passing_Total (A : Scores; Mark : Score) return Integer is
([for X of A when X >= Mark => X]'Reduce ("+", 0))
with Pre => A'Length <= 1000;
-- how many pass: each counts one
function Passing (A : Scores; Mark : Score) return Natural is
([for X of A when X >= Mark => 1]'Reduce ("+", 0))
with Pre => A'Length <= 1000;
-- a range's values, mapped (the range's bounds bound its values)
subtype Count is Natural range 0 .. 100;
function Sum_Of_Squares (N : Count) return Natural is
([for I in 1 .. N => I * I]'Reduce ("+", 0));
-- the largest, by a function reducer
function Best (A : Scores) return Score is (A'Reduce (Score'Max, 0));
-- quantifiers, with and without a filter
function All_Valid (A : Scores) return Boolean is (for all X of A => X <= 100);
function Any_Perfect (A : Scores) return Boolean is (for some X of A => X = 100);
-- the scores that pass, collected into an array as long as their number
function Above (A : Scores; Mark : Score) return Scores is
([for X of A when X > Mark => X])
with Pre => A'Length <= 1000;
-- each score curved, collected; nothing filtered, so as long as the source
function Curved (A : Scores) return Scores is ([for X of A => (X + 100) / 2])
with Pre => A'First = 1 and A'Length <= 1000,
Post => Curved'Result'Length = A'Length;
-- a loop the programmer writes, proved against the reduction it computes
function Running_Total (A : Scores) return Integer
with Pre => A'Length <= 1000,
Post => Running_Total'Result = A'Reduce ("+", 0);
end Stats; package body Stats is
function Running_Total (A : Scores) return Integer is
S : Integer := 0;
begin
for I in A'Range loop
pragma Loop_Invariant
(S = [for J in A'First .. I - 1 => A (J)]'Reduce ("+", 0)
and S <= 100 * (I - A'First));
S := S + A (I);
end loop;
return S;
end Running_Total;
end Stats; $ byronic prove
proved 64 of 64 checks in 11 subprograms (6 statically, 58 by the solver)
$ byronic run
total 500 500
passing 5 416
squares 385
best 100
valid TRUE, perfect TRUE
above 80: 100 88 95
curved: 86 72 100 94 80 69 97 Leno
Python that is proved, then compiled
Lists, range, generators and all(...), with
contracts and loop invariants as ordinary calls. Byron proves that no
index is out of bounds and no sum overflows, then compiles to native
code. Run the same files with python3, with Byron's
byron module on the path, and they print the same lines.
from byron import ensures, invariant, requires, result
# the sum of at most 1000 scores of at most 100 points each
def total(xs: list[int]) -> int:
requires(len(xs) <= 1000 and all(0 <= xs[k] <= 100 for k in range(len(xs))))
ensures(0 <= result <= 100 * len(xs))
sum_so_far = 0
for i in range(len(xs)):
invariant(0 <= sum_so_far <= 100 * i)
sum_so_far += xs[i]
return sum_so_far
# the position of the best score (the first, if several are equal)
def best(xs: list[int]) -> int:
requires(len(xs) > 0)
ensures(0 <= result < len(xs))
position = 0
for i in range(1, len(xs)):
invariant(0 <= position < i)
if xs[i] > xs[position]:
position = i
return position from scores import best, total
def main() -> None:
xs: list[int] = [58, 72, 69, 91, 80]
print("total", total(xs))
print("best at", best(xs))
if __name__ == "__main__":
main() $ byronic prove
proved 44 of 44 checks in 3 subprograms (8 statically, 36 by the solver)
$ byronic run
total 370
best at 3
$ python3 src/main.py
total 370
best at 3 Concurrency
Tasks proved free of data races
A producer and a consumer share a bounded buffer, a protected object whose entries wait on their barriers. Each task body and protected operation is proved like sequential code, and Byron checks the rest of what makes a Jorvik program correct: no data races, no blocking in protected actions, and priority ceilings respected.
"""A bounded buffer between tasks."""
from byron import Array, Modular, Natural, Protected, entry, when
class Slot(Modular, modulus=4):
pass
class Ring(Array[int], index=Slot):
pass
class Buffer(Protected):
items: Ring = Ring([0, 0, 0, 0])
first: Slot = 0
count: Natural = 0
@entry
def put(self, x: int) -> None:
when(self.count < 4)
self.items[self.first + Slot(self.count)] = x
self.count = self.count + 1
@entry
def get(self) -> int:
when(self.count > 0)
x = self.items[self.first]
self.first = self.first + 1
self.count = self.count - 1
return x
shared = Buffer() """Two tasks: a producer of squares and a consumer that prints them, then ends the program."""
from byron import sleep, stop, task
from buffers import shared
@task
def producer() -> None:
for i in range(1, 6):
shared.put(i * i)
while True:
sleep(1.0)
@task
def consumer() -> None:
for _ in range(5):
x: int = shared.get()
print("got", x)
stop()
while True:
sleep(1.0) import workers
# the program is its tasks: the main function has nothing to do
def main() -> None:
pass $ byronic prove
proved 11 of 11 checks in 4 subprograms (8 statically, 3 by the solver)
$ byronic run
got 1
got 4
got 9
got 16
got 25 Let the producer print too
Add print("put", i * i) to the producer and both tasks
now write to standard output, which Byron treats as a shared
variable. The program is rejected before it can interleave its
output, with both uses shown and the remedy named.
$ byronic prove
error[B3019]: `Device` is shared by task `producer` and task `consumer` outside a protected object
--> ./src/workers.py:12:9
|
12 | print("put", i * i)
| ^^^^^^^^^^^^^^^^^^^ used by task `producer`
...
21 | print("got", x)
| --------------- used by task `consumer`
|
::: <predefined>/ada-text_io.adb:6:4
|
6 | Device : Boolean := False;
| ------ declared here
|
= note: tasks run at the same time: a variable more than one of them uses, one writing it, belongs in a protected object, whose actions hold its lock
= help: make `Device` a component of a protected object, and use it through that object's operations
1 error emitted