Syntax cheat sheet

Every form of the language in one place: declarations, expressions, patterns, types and the call rules, with the exact spelling of each.

Every form the language has, with its exact spelling. Each line is the canonical form: it is what torb format writes and what the compiler accepts.

Example#

use File from "std/fs"

type Config {
  var host: String = "localhost"
  var port: Int = 8080
}

type ConfigError {
  case Missing(path: String)
  case Malformed(line: Int)
}

fn load(path: String): Result<Config, ConfigError> {
  if !File.exists(path) {
    return Fail ConfigError.Missing(path)
  }
  Config()
}

match load("project.trb") {
  Ok(config) => print "{config.host}:{config.port}"
  Fail(problem) => printError "{problem}"
}

Syntax#

Declarations#

const name = value                          an immutable binding
var name: Type = value                      a mutable binding with an annotation
fn name(a: Int, b: Int = 1): Int { ... }     a function; a default is evaluated at every call
fn name(): Int { ... }                       a method, inside a type body; it does not list `self`
var fn name() { ... }                        a method that changes its receiver in place
static fn name(): Self { ... }               a member of the type itself, reached as `Type.name()`
static name = value                          a constant of the type
fn name(var target: Counter) { ... }         a parameter the function may change
fn name(...rest: Int): Int { ... }           a variadic parameter; `rest` is a `List<Int>`
type Name { ... }                            a type
type Name = Other                            an alias for an existing type
case Name = 1                                inside a type body: a case that stands for a number, `rawValue()`
type Name with Trait by field { ... }        the trait's required members are forwarded to field
type Name with (Trait, Trait) by field { ... }  several traits delegated to the same field
shared type Name { ... }                     a type with an identity instead of a value
trait Name { ... }                           a capability
extend Name with Trait { ... }               an implementation written afterwards
extend Name { ... }                          members added to a type
public fn name() { ... }                     exported from its file
private var field: Int = 0                   invisible outside its type
protected var field: Int = 0                 write-protected: read by everyone, written by its own file only
deprecated("why", replacement: "other")      on the line above a declaration: kept, warned about, rewritten by lint
use Name from "std/core"                     an import
use Type.Case from "./module"                a case, by its path
use Type.member from "acme/text"             a member another package's `extend` adds
use * as console from "std/console"          a namespace import
use Name as Other from "./module"            an import under a local name
public use Name from "./module"              a re-export

Expressions#

f a, b                                       a command call: the canonical form
f(a, b)                                      needed when a rule of the canon says so
f()                                          no arguments: always parentheses
f(a) { x => x }                              a trailing closure
f a { x => x }                               a command call with a trailing closure
f(label: value)                              a labelled argument; labels follow the positional ones
f(...values)                                 a spread of any `Iterate`
{ x: Int => x * 2 }                          a closure with a typed parameter
{ var point => point.x = 0 }                 a closure that binds a `var` parameter, the caller's value
{ _ * 2 }                                    a closure with implicit parameters `_`, `_2`, `_3`
Type.Case(field)                             a case, written out
.Case(field)                                 a case where the expected type is known
Case(field)                                  a case that the file imports
value.member                                 a method reference, bound to `value`
Type.member                                  the member itself: `(self: Type) => ...`
value?                                       unwrap an `Ok` or a `Some`, or return early
value ?? fallback                            `OrElse.orElse`: the value, or a lazy fallback
a ** b                                       `Power.power`: tighter than `*`, to the right; `-x ** 2` is an error
a & b   a | b   a ^ b   ~a                   the bit operators of `Bits`: `&` binds like `*`, `|` and `^` like `+`
a << n   a >> n                              shifts, between `*` and `**`; arithmetic on a signed type
value?.member                                `Option.map`, or `flatMap` for an optional member
value.into()                                 a conversion chosen by the expected type
value.to<Target>()                           a conversion with the target written out
do { ... }                                   a block evaluated immediately
if condition { a } else { b }                an expression
match subject { pattern => value }           an expression, and exhaustive
"text {expression}"                          interpolation; a literal brace is `\{`
raw"text"                                    a raw string: no escapes, no interpolation
const page: Uri = "https://example.test/"     a literal the compiler reads: `Path`, `Uri`, `Regex`, ...
const digits: Regex = "\d{2,4}"               a `Regex` or `UriTemplate` literal is read verbatim
"""                                          a multi-line string, dedented by its first line
  text
  """

Patterns#

_                                            the wildcard
name                                         binds, because it starts lowercase; a refutable arm has to read it
_name                                        binds and keeps the name; nothing has to read it
Case(field)                                  an imported case; never binds
.Case(field)                                 a case of the type being matched
Type.Case(field)                             a case written out
Type(field, other)                           a type read backwards, field by field
Type(label: value)                           a labeled sub-pattern; the labeled ones follow the positional ones
Type(field, ...)                             `...` stands for every field the pattern does not name
(a, b)                                       a tuple
[first, ...rest]                             a list, with a rest
[]                                           the empty list
1 | 2 | 3                                    alternatives
4..=9                                        a range
n if n < 0                                   a binding with a guard

Types#

Int Int8 Int16 Int32 Int64                   signed; `Int` is an alias for `Int64`
UInt UInt8 UInt16 UInt32 UInt64              unsigned
Float Float32 Float64                        `Float` is an alias for `Float64`
Bool Char String Void Never                  the rest of the built-ins
Value?                                       `Option<Value>`
List<Item> Map<Key, Value> Set<Item>         collection traits
Array<Item, 16>                              a fixed size in the type
Range<Int>                                   `0..10`, `0..=10`, `0..`, `..10`
(Int, String)                                a tuple
(lowest: Int, highest: Int)                  a labelled tuple; a label is not part of the type
(value: Int) => Int                          a function type
(var self: Config) => Void                   a receiver closure
lazy Value                                   evaluated at most once, on first use
Expression<Bool>                             the value and its syntax tree
Show & Encode                                an intersection of traits
"tcp" | "udp"                                a union of literals of one base type

Statements#

return value                                 an early return
for item in items { ... }                     over anything `Iterate`
for (key, value) in table { ... }             destructuring in a loop
for var item in items { ... }                 every slot in place: `item` is `items[key]` of the turn
for (key, var value) in table { ... }         every value of a map in place, the key a copy
while condition { ... }                      `break` and `continue` work
loop { ... }                                 endless: `Never` without a `break`, `Void` with one
if const Some(user) = find(id) { ... }        a pattern in a condition
if var Some(cursor) = current { ... }         binds into the place, not into a copy
place = value                                 a statement, never an expression
panic "message"                               aborts with exit code 101

Rules#

  1. A statement ends at the end of its line. There are no semicolons, and two statements never share a line.
  2. A call is a command wherever the grammar allows it: in command position, with a path as the callee, at least one argument whose first token is not (, [, -, ! or ., no operator at the top level of an argument, and all arguments on one line. Parentheses everywhere else.
  3. A case is never bare unless the file imports it. Some, None, Ok and Fail are bare because the prelude imports them.
  4. In a pattern, a lowercase name binds and an uppercase name never does. An uppercase binding is a compile error.
  5. A binding of a refutable pattern has to be read. In an arm of a match, an if const/if var and a while const, a binding the guard and the body never read is an error: write _, or _name to keep the name.
  6. A name is ASCII, and its first letter is a rule. [A-Za-z_][A-Za-z0-9_]*; A-Z starts a type, a trait, a case, a type parameter and a type alias, and everything else starts lowercase. There is no MAX_SIZE.
  7. const is deep. Through a const binding nothing changes.
  8. A field is const unless marked var; a member is public unless marked private; a top-level declaration is private to its file unless marked public.
  9. A public function and a trait method never infer their result type. Without one they answer Void.
  10. { in expression position is always a closure, never a block. do { ... } evaluates a block immediately.
  11. A match is exhaustive, and an unreachable arm is an error.
  12. An expression statement has to be Void or Never, unless the call has a var receiver or a var argument.
  13. Mutation needs a var path from the binding down: a var binding, var parameter or a var fn receiver, then var fields.
  14. A var that is changed and never read afterwards is a compile error, and so is the discarded result of a method that reads its receiver.
  15. An endless loop is loop. loop { ... } has the type Never without a break that targets it and Void with one, a break carries no value, and while true is an error that names loop.
  16. A use names what it imports, and a member of a foreign extend is one of those names. use String.shout from "acme/text" is the same form as a case import; what the type's own package attaches to it needs no import, and what a trait puts on a foreign type needs the trait.

What this is not#

This page is every form that is right. The forms that look right and are not - let, a semicolon, a bare case, Err, text.length, a & b, x as Int, MAX_SIZE, while true, impl Trait for Type and the rest - are one list, each with the diagnostic it produces: What a model trained on other languages gets wrong.

Esc