Skip to content
Menu
Contents

Start

Chapter 1 of 9

What Bert is, how to install it, and your first program.

What is Bert?

Bert is a small programming language. You write code, the Bert compiler turns it into a WebAssembly file, and that file runs almost anywhere.

WebAssembly (Wasm) is a compact program format. Browsers can run it. On your own computer, a small program called Wasmtime runs it. You build once and run it in any of those places.

Bert is built on a few simple ideas:

  • A name keeps its value. You make new values instead of editing old ones.
  • Everything gives back a value. Even if does.
  • You can keep a function under a name and hand it to another function, like a number.

Every value has a type: whole number, text, yes or no, and so on. Bert works out the types for you. You can write a whole program without writing a single type, and the compiler still catches your mistakes before the program runs.

Here is a taste. It makes a function called double and uses it:

double = (n) -> n * 2
result = double(21)
# => 42

Install

You need two things to begin:

  • Node.js 22.18 or newer. The Bert compiler is written in TypeScript and runs on Node. Bert is tested on Node 24.
  • Wasmtime. A small program that runs .wasm files from your terminal.

The web server tutorial also needs wasm-tools. You can skip it until then.

Get the compiler from the Download page. Its steps install Wasmtime (and wasm-tools, if you want it now), unpack the compiler and put the bert command on your path. Its last step runs the same hello program as the next section, so you know everything is in place.

Hello, world

Make a new folder for your project. Inside it, create two files.

First, an empty file called bert.toml. It marks the folder as a Bert project, and Bert needs it:

cd ~
mkdir hello && cd hello
touch bert.toml

Without it, Bert stops with this message:

error: no bert.toml found in any ancestor of hello.bert — a project root is needed to anchor module namespaces

Then hello.bert:

hello.bert
external export {
  _start : () -> Unit
  _start = () -> WasiCli.print("Hello, Bert!\n")
}

Run it:

bert hello.bert
Output
Hello, Bert!

Here is what each line does:

  • external export { … } lists where your program starts. Wasmtime calls _start when you run it.
  • _start is the name Wasmtime looks for. Without it, nothing would ever start your program.
  • () -> Unit says what _start takes and gives: no inputs, and Unit as the output. Unit means the function gives back no useful value; it only does something, like printing.
  • WasiCli.print writes text to the terminal. WasiCli is a module Bert gives you for talking to the terminal. (WASI is the standard way a Wasm program talks to the computer it runs on.) print does not add a new line, so we add \n ourselves.

A scratch file for the rest of this guide

Later chapters show small examples with their answer in a comment, like 2 + 3 * 4 followed by # => 14. You can run them yourself, and it helps to. There is one catch: Bert has no prompt where you type an expression and see its value. A file holds named values and a _start, and only _start can print.

So make a second file next to hello.bert and call it scratch.bert. To try an example, give it a name at the top of the file, then add a line to _start that prints that name. Here the three examples are total, half and name:

scratch.bert
total = 2 + 3 * 4
half = 7 / 2
name = "Bert"

external export {
  _start : () -> Unit
  _start = () -> {
    _ = WasiCli.print(Int.toString(total))
    _ = WasiCli.print("\n")
    _ = WasiCli.print(Debug.toString(half))
    _ = WasiCli.print("\n")
    _ = WasiCli.print(Debug.toString(name))
    WasiCli.print("\n")
  }
}
Output
14
3
"Bert"

Run it the way you ran the first program: bert scratch.bert. The output is the box under the code. Change the lines at the top, run it again, and you see the new answers. That is the whole loop, and you will use it throughout the guide.

A few things in that file are new:

  • Printing needs text, so a number goes through Int.toString first.
  • Debug.toString turns any value into text: numbers, lists, records, anything. Text comes out in quotes.
  • The { } after _start = () -> is a block: a list of steps that run from top to bottom. The last line gives the block's result.
  • Inside { }, every line but the last must start with a name and =. print gives back a result you do not need, and _ is a name for "I do not need this".

If you leave out a _ =, Bert reports This `{` is never closed. The message is misleading; it means you forgot one.

Reading an error

Mistakes are normal. Say you mistype a name:

double = (n) -> n * 2
r = dobule(2)

Bert stops and prints:

─── canon.unknown_identifier ──────────────────────────

Unknown name: `dobule` is not defined in this scope.

scratch.bert:2:5
2 | r = dobule(2)
        ^^^^^^
  • The top line is the error's name. It is handy for searching.
  • The sentence says what is wrong.
  • scratch.bert:2:5 is the file, the line (2) and the column (5).
  • The ^^^ marks point at the exact spot.