Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Basics

An SSPUR program is a list of definitions: functions, types and tests. Their order doesn’t matter, and there is nothing outside a definition. Here is a whole program:

fn main() -> Unit ! log
= do
  name = "SSPUR"
  var count = 0
  for word in ["types", "effects", "contracts"]
    count := count + 1
    log("{count}. {name} checks {word}")
  log(greeting(3))
fn greeting(hour: Int) -> Str
= if hour < 12 then "good morning" else "good evening"

A few things to notice:

  • fn main() -> Unit ! log is the entry point. After -> comes the return type, and after ! the effects the function may perform. main writes to the log, so it says ! log. A function that does nothing observable has no ! at all.
  • The body comes after =. It is one expression, like greeting’s if, or do followed by an indented block. A block’s value is its last line.
  • Indentation is 2 spaces and it is the only structure. There are no braces and no semicolons.
  • name = "SSPUR" binds a name once. var count = 0 makes a variable you can change with :=. Plain = bindings can’t be reassigned, and the checker says so if you try.
  • for word in [...] loops over a list. "{count}. {name}" puts the value of an expression into a string.
  • if c then a else b is an expression and both branches have the same type. You can leave out else only when the then branch is Unit.
  • // starts a comment. Comments are fine in files, but a codebase managed by sspur edit doesn’t store them.

Values and operators

fn values() -> Str
= do
  n = 7 / 2
  x = 7.0 / 2.0
  big = 1_000_000
  mask = 0xff
  ok = n == 3 and not (x < 3.0)
  "{n} {x} {big} {mask} {ok}"

Int is a 64-bit integer and / on two Ints truncates. F64 is a double. and, or and not are words. Integer overflow and division by zero don’t wrap or return garbage: they stop the program with a message, in the interpreter and in native code alike.

Tests

A test is a named Bool expression next to the code it checks:

test morning = greeting(9) == "good morning"
test evening = greeting(20) == "good evening"
test values_print = values() == "3 3.5 1000000 255 true"

Running it

$ sspur run basics.ssp
1. SSPUR checks types
2. SSPUR checks effects
3. SSPUR checks contracts
good morning
$ sspur test basics.ssp
3 passed, 0 failed

sspur run compiles the file to native code (through clang, cached by content hash) and runs main; run again unchanged, it goes straight to the cached code. sspur test runs every test and prints only the failures and a count.

When it doesn’t check

There are no implicit conversions. Mixing Int and F64 is an error, and the message says how to fix it:

fn half(n: Int) -> F64
= n / 2.0
$ sspur check mixed.ssp
mixed.ssp:2:7 E_TYPE_MISMATCH expected Int, found F64
mixed.ssp:2:3 E_TYPE_MISMATCH expected F64, found Int
  hint: convert with '.to_f64'

Every error has a code (here E_TYPE_MISMATCH), a position and usually a hint:. The error reference lists them all. Next: Everyday Types.