SSPUR in 5 minutes
This page goes from hello world to a function whose contract is fuzzed and proved. It assumes sspur is installed (Installation), or you can try it in the playground first, which runs the programs in your browser. Every file and terminal session here is a real file under docs/snippets/get-started/, and CI runs each command against the current compiler.
Hello, world
Save this as hello.ssp:
fn main() -> Unit ! log
= log("hello, world")
$ sspur run hello.ssp
hello, world
fn main() -> Unit ! log says that main returns nothing and performs one effect, log. The body comes after =. If main also wrote a file or read the clock, the checker would refuse the program until the signature said so.
A function with a contract
Save this as price.ssp:
// Apply a percentage discount to a price in cents.
fn discount(price: Int, pct: Int) -> Int
pre price >= 0 and pct >= 0 and pct <= 100
post r >= 0 and r <= price
= price - price * pct / 100
fn main() -> Unit ! log
= log("a 2000 cent book at 15% off costs {discount(2000, 15)}")
test half_off = discount(1000, 50) == 500
test no_discount = discount(999, 0) == 999
test free = discount(10, 100) == 0
pre is what callers must guarantee and post is what the function promises about its result r. Both are checked every time the function runs. "{discount(2000, 15)}" puts an expression’s value into a string, and a test is a named Bool expression.
$ sspur run price.ssp
a 2000 cent book at 15% off costs 1700
$ sspur test price.ssp
3 passed, 0 failed
Fuzz it, then prove it
You wrote the contract once, and SSPUR uses it twice more. sspur fuzz generates arguments that satisfy pre and checks post on every result. sspur verify hands the contracts to Z3 and tries to prove them for all inputs:
$ sspur fuzz price.ssp
ok discount (200 cases, 1210 discarded)
skip main (no parameters)
$ sspur verify price.ssp
fn discount
pre price >= 0 and pct >= 0 and pct <= 100 proved: at 4 call sites
post r >= 0 and r <= price proved
2 proved, 0 counterexample, 0 unknown; 3 of 4 runtime checks proved safe
verify proved the postcondition, proved that all 4 calls in the file meet the precondition, and found that 3 of the 4 runtime checks can never fail, so native code leaves them out.
Now break the contract. Save a copy as price_bug.ssp that claims a discount always lowers the price:
post r < price
$ sspur fuzz price_bug.ssp
FAIL discount(0, 0): contract violated: post r < price in discount (r = 0)
skip main (no parameters)
The fuzzer found the counterexample: a price of 0 with no discount stays 0.
Records and pattern matching
type Point = {x: Int, y: Int}
type Shape = Circle{center: Point, r: Int where _ > 0} | Rect{corner: Point, w: Int, h: Int} | Dot
fn area(s: Shape) -> Int
= match s
| Circle{r} => 3 * r * r
| Rect{w, h} => w * h
| Dot => 0
fn describe(s: Shape) -> Str
= match s
| Circle{center, r} => "circle at ({center.x}, {center.y}), radius {r}"
| Rect{w, h} if w == h => "square {w}x{h}"
| Rect{w, h} => "rect {w}x{h}"
| Dot => "dot"
fn main() -> Unit ! log
= do
origin = Point{x: 0, y: 0}
shapes = [Circle{center: origin, r: 2}, Rect{corner: origin, w: 3, h: 3}, Rect{corner: origin with x := 5, w: 4, h: 1}, Dot]
for s in shapes
log("{describe(s)}: area {area(s)}")
log("total area {shapes.map(area).sum}")
test square = describe(Rect{corner: Point{x: 1, y: 1}, w: 2, h: 2}) == "square 2x2"
test dot_area = area(Dot) == 0
Point is a record and Shape a sum type with three variants. match must cover every variant, or the checker names the missing one. Circle{r} binds only the fields it names, if w == h is a guard, and origin with x := 5 is a copy of origin with one field changed. Values never change in place.
$ sspur run shapes.ssp
circle at (0, 0), radius 2: area 12
square 3x3: area 9
rect 4x1: area 4
dot: area 0
total area 25
$ sspur test shapes.ssp
2 passed, 0 failed
Where next
- The handbook teaches the language in order, from the basics to packages.
- If you know another language, the pages for TypeScript, Python and Go and Rust programmers map what you know onto SSPUR.
- Build a CRUD service runs a small HTTP service with a table on your machine.
- SSPUR for AI agents shows how an agent works on a codebase with
sspur start,qandedit --test. - SSPUR Pulse is a larger program to read: a live dashboard built by SSPUR programs every 3 hours, explained in how it works.