Skip to content

Tools

The brine command

Everything in Brine goes through one command, brine: creating projects, checking, building, running, testing, formatting and debugging. Every command that reports diagnostics takes --json, and exit codes are stable: 0 success, 1 your code has a problem, 2 the command line or setup has a problem, and 101 a program panicked.

Terminal window
brine --help
brine — a verified, natively parallel systems language
Usage: brine <command> [options]
Project:
new <name> Create a new project in ./<name>
init Create a project in the current directory
Build and run:
run [file] [-- args] Build and run (default: the current project)
--release optimize more
--threads <n> worker threads for `par` (default: all cores)
--ranks <n> run <n> cooperating processes (the `world` module)
--target wasm build WebAssembly and run it with Node.js
build [file] Build a native executable
--release | --debug optimization profile (default: dev)
-o <path> output path
--emit-c also write the generated C next to the output
--target <t> host (default), wasm (a .wasm module) or web (a page)
check [file] Type-check and verify without building (fastest)
--report list every runtime check the prover could not remove
test [file] [filter] Run `test "...":` blocks in parallel (also --target wasm)
debug [file] [-- args] Debug build, then launch gdb or lldb
Tools:
fmt [files] [--check] Format source files (default: the whole project)
explain <code> Explain an error code, e.g. `brine explain E0601`
guide Print the language quick reference
doctor Check that your toolchain works
version Print the version
Global options:
--json Machine-readable output (one JSON object per line)
--color <when> auto | always | never
-q, --quiet Only print errors
Command What it does Flags
brine new <name> create a project in ./<name>
brine init create a project in the current directory
brine run [file] [-- args] build and run --release, --debug, --threads N, --ranks N, --target wasm, --typed
brine build [file] build an executable, a .wasm module or a web folder --release, --debug, -o PATH, --emit-c, --target T, --typed
brine check [file] type-check and prove, no code generation --report, --typed
brine test [file] [filter] run test "...": blocks in parallel --release, --target wasm, --typed
brine debug [file] [-- args] debug build, then gdb or lldb
brine fmt [files] format source files --check
brine explain [code] explain an error code, or list them all
brine guide print the language quick reference
brine doctor check the toolchain
brine version print the version

Every command also accepts the global options. A flag a command doesn’t know is an error, with a suggestion:

error: unknown option `--reprot` (did you mean `--report`?)

run, build, check, test and debug take an optional file:

You pass Brine uses
nothing the project around you: the nearest brine.toml in this directory or a parent, and its main file (src/main.br unless [build] main says otherwise)
file.br that file, as a single-file program (imports load next to it)
a directory with brine.toml that project
a directory with main.br and no brine.toml that folder’s main.br

Outside a project and without a file, Brine says what to do:

error: no brine.toml found in this directory or any parent; pass a file (`brine run hello.br`) or create a project with `brine new <name>`

See Projects and brine.toml for where each kind of build puts its output.

Terminal window
brine new hello
Created project `hello`
cd hello
brine run

Creates hello/ with brine.toml, src/main.br, AGENTS.md, README.md and .gitignore, and runs git init unless the folder is already inside a Git repository. The name must start with an ASCII letter or _ and contain only letters, digits, _ and -. The folder must not exist yet. With --json it prints {"created":"hello"}. Projects and brine.toml shows the files.

Terminal window
mkdir myapp && cd myapp
brine init
Created project `myapp` (brine.toml, src/main.br, .gitignore, AGENTS.md, README.md)

Like brine new, in the current directory. The project is named after the folder (characters that aren’t allowed become _). Files that already exist are kept, and the message lists only the ones it wrote. It fails if the directory already has a brine.toml.

Builds the program, then runs it. Status lines go to stderr; the program’s own output is untouched.

Terminal window
brine run
Built hello v0.1.0 [dev] in 2.05s
Running target/dev/hello.exe
Hello, brine!
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

A second run with nothing changed skips the compile and the link:

Fresh hello v0.1.0 [dev] in 0.02s
Running target/dev/hello.exe
Hello, brine!
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

(The first build of a project takes about two seconds because it compiles the runtime once; after that, builds take a fraction of a second.)

Flag Effect
--release build with the release profile (optimized C)
--debug build with the debug profile (no optimizations, debug info)
--threads <n> worker threads for par (sets BRINE_THREADS for the program); a positive number
--ranks <n> start n cooperating processes for the world module, 1 to 1024
--target wasm build a WebAssembly module and run it with Node.js, or with BRINE_WASM_RUNNER; needs wasi-sdk. Can’t be combined with --ranks. --target web is refused: open a web build in a browser. See WebAssembly
--typed require a written type on every variable, as [check] typed does

Everything after -- goes to the program:

argv.br
fn main():
print(args())
Terminal window
brine run argv.br -- one --two 3
Built argv [dev] in 0.10s
Running C:/Users/you/AppData/Local/Temp/brine-cache/465ef71a47ea2199/dev/argv.exe
["one", "--two", "3"]

With a file, plain words after it are passed too (brine run argv.br a b), but anything that starts with - would be read as a Brine flag, so use --. Inside a project, always use --: brine run foo means “run the file foo”.

Terminal window
brine run world.br --ranks 4
Built world [dev] in 0.10s
Running C:/Users/you/AppData/Local/Temp/brine-cache/88f5d4ab53aec8ad/dev/world.exe (4 ranks)
4 ranks, total = 499999500000, squares = [0, 1, 4, 9]
after one lap: token->0->1->2->3
rank 3 received [[0, 3], [1, 3], [2, 3], [3, 3]]

Each rank is a process on this machine, connected over loopback TCP. Only rank 0’s output is shown. If one rank fails, Brine stops the others half a second later and returns the first non-zero exit code. See Multiple processes.

brine run returns the program’s exit code: 0 when main returns normally, the value main returns if it returns an int (clamped to 0-255), 1 when main returns Err, and 101 after a panic:

panic.br
fn main():
let xs = [1, 2, 3]
let i = len(args())
print(xs[i + 5])
Built panic [dev] in 0.09s
Running C:/Users/you/AppData/Local/Temp/brine-cache/fce079b2338829bc/dev/panic.exe
panic: index 5 is out of bounds for an array of length 3
--> panic.br:4:11

That run exits with 101. If the program doesn’t compile, nothing runs and the exit code is 1.

Builds an executable without running it.

Terminal window
brine build --release
Built hello v0.1.0 [release] in 2.74s
Output target/release/hello.exe
Flag Effect
--release optimized build through C at -O3, one translation unit
--debug debug build: native code with DWARF on x86-64 Windows/Linux and AArch64 Linux, C with -O0 -g elsewhere (Debugging)
-o <path> where to write the executable; on Windows .exe is added when the name has no extension (native builds only)
--emit-c also write the program as one C file next to the executable (hello.exe → hello.c)
--target <t> host (the default, also native), wasm (a WASI module, also wasm32 or wasi) or web (a folder with index.html, brine.js and program.wasm). See WebAssembly
--typed require a written type on every variable

Where the executable goes:

Build Output
project, no -o target/<profile>/<name> (target/dev/hello.exe)
single file, no -o the current directory, named after the file (brine build tool.br → ./tool.exe)
-o path path

--target wasm writes <name>.wasm instead (target/dev-wasm/hello.wasm in a project), and --target web a folder named <name>-web (target/dev-web/hello-web/).

--emit-c writes the whole program as the C backend would generate it, even when the executable itself was built by the native backend:

Wrote target/dev/hello.c
Fresh hello v0.1.0 [dev] in 0.02s
Output target/dev/hello.exe

With --json, a successful build prints {"built":"target/dev/hello.exe"}.

Parses, type-checks and runs the prover, without generating code. It is the fastest feedback loop: run it after every edit.

sum.br
fn total(xs: [int]) -> int:
var s = 0
for i in 0..len(xs):
s += xs[i]
return s
fn pick(xs: [int], k: int) -> int:
return xs[k]
fn main():
let xs = [1, 2, 3, 4]
print(total(xs), pick(xs, 2))
Terminal window
brine check sum.br
Checked sum in 0.00s — 1 runtime check proven unnecessary, 1 remain (see --report)
Flag Effect
--report list every runtime check the prover couldn’t remove, one per line
--typed require a written type on every variable (E0337)
Terminal window
brine check sum.br --report
sum.br:8:12: index bounds check `xs[k]`
Checked sum in 0.00s — 1 runtime check proven unnecessary, 1 remain

xs[i] in total was proven in bounds; xs[k] in pick wasn’t, because nothing says k is in range. A requires 0 <= k < len(xs) would remove it. See Runtime checks.

brine check doesn’t need a main function, so it also works on library modules. It exits with 0 when there are no errors (warnings don’t count) and 1 when there are.

With --json:

{"runtime_check":"index bounds check","location":"sum.br:8:12","code":"xs[k]"}
{"ok":true,"checks_proven":1,"checks_remaining":1}

Builds the test "...": blocks of a file or project and runs each one in its own process, in parallel.

t.br
fn double(x: int) -> int:
return x * 2
test "double doubles":
assert double(4) == 8
test "double of zero":
assert double(0) == 1
fn main():
print(double(21))
Terminal window
brine test t.br
Built tests [dev] in 0.10s
test double doubles ... ok
test double of zero ... FAILED
---- double of zero ----
panic: assertion failed: double(0) == 1
--> t.br:8:5
test result: FAILED. 1 passed; 1 failed; finished in 0.03s
Argument or flag Effect
file a .br file or a directory; without one, the current project
filter run only the tests whose name contains this text (brine test t.br doubles, or brine test doubles in a project)
--release build the tests with the release profile
--target wasm build the tests as a WebAssembly module and run each one with Node.js (WebAssembly)
--typed require a written type on every variable

Details:

  • A test passes when it finishes without a panic. The exit code is 0 when every test passed and 1 when any failed or the tests didn’t compile.
  • Each test process gets BRINE_THREADS=2, so many tests can run at once without each starting a thread per core, and BRINE set to the path of the brine running the tests, so a test can run the compiler.
  • Results are printed in source order, whatever order the tests finish in. --quiet prints only the failures and the summary.
  • A filter that matches nothing is not an error: it prints (no tests match the filter; add one with test “name”:) and exits with 0.

With --json, one object per test:

{"test":"double doubles","ok":true,"seconds":0.021,"output":""}
{"test":"double of zero","ok":false,"seconds":0.026,"output":"\npanic: assertion failed: double(0) == 1\n --> t.br:8:5\n"}

Makes a debug build, then starts gdb (or lldb, if there is no gdb) on it. Arguments after -- go to the program. brine debug takes no other flags.

Terminal window
brine debug shapes.br

gdb gets Brine’s pretty-printers, so values print the way Brine prints them, and stepping skips the C runtime. Without gdb or lldb:

error: no debugger found (install gdb or lldb); the debug build is at <path>

Debugging has a full session.

Rewrites source files in the canonical format: 4-space indentation, one space around operators, at most one blank line, and lines up to 100 characters.

Terminal window
brine fmt # every .br file in the project
brine fmt src/main.br # just these files (or directories)
brine fmt --check # change nothing; exit 1 if a file would change
would reformat messy.br
1 file checked, 1 need formatting

Without arguments it formats every .br file under the project root (or the current directory outside a project), skipping target/ and folders whose names start with .. A file with a syntax error is reported and left alone. See Formatting for what it changes.

Flag Effect
--check report files that aren’t formatted (would reformat <file>, or {"unformatted":"<file>"} with --json) and exit with 1; write nothing
Terminal window
brine explain E0401
E0401: assigning to an immutable binding
Variables declared with `let` (and loop variables) can't change. Declare the
variable with `var` if it needs to change:
var total = 0
total += 1

Without a code, it lists every code with its title. With --json, it prints {"code":...,"title":...,"explanation":...} (or one {"code":...,"title":...} line per code for the list). An unknown code exits with 2. The same text is on the error codes page.

Prints the language quick reference: the whole language on one page, built into the compiler, so it works offline and always matches the installed version. It’s the same text as the quick reference page.

Terminal window
brine guide | less

Checks that Brine can build and run programs on this machine.

Terminal window
brine doctor
✓ brine 0.4.0 (windows x86_64)
✓ C compiler gcc — gcc (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1) 16.2.0
✓ parallelism 32 hardware threads
✓ local sockets needed for `brine run --ranks`
✓ build + run compiled and ran a parallel program
✓ debugger gdb
✓ WebAssembly C:/Users/you/tools/wasi-sdk-34.0-x86_64-windows\bin\clang.exe
Check What it tests
brine the version and the platform
C compiler BRINE_CC, CC, or the first of clang, gcc, cc that runs
parallelism how many hardware threads par will use by default
local sockets that a local TCP port can be reserved (for --ranks)
build + run builds and runs a small par sum program in a temp folder and checks its output
debugger gdb or lldb, for brine debug (optional: a missing debugger doesn’t fail)
WebAssembly wasi-sdk’s clang, through WASI_SDK_PATH or /opt/wasi-sdk, for --target wasm and web (optional)

It exits with 1 if a required check fails. With --json it prints one {"check":...,"ok":...,"detail":...} line per required check.

Terminal window
brine version
brine 0.4.0

brine --version and brine -V print the same; brine version --json prints {"version":"0.4.0"}. brine help, brine --help and brine -h print the help, as does brine alone.

These work with every command, before or after it (brine --json check and brine check --json are the same). Flags that take a value also accept =: --color=never.

Option Effect
--json machine-readable output on stdout, one JSON object per line; status lines are not printed
--color <when> auto (the default: color when stderr is a terminal and NO_COLOR isn’t set), always or never
-q, --quiet print only errors: no Built/Running lines, no warnings

In text mode, diagnostics and status lines go to stderr. With --json, each diagnostic is one line on stdout:

{"severity":"error","code":"E0401","message":"cannot assign to `x` because it was declared with `let`","file":"bad.br","line":3,"column":5,"end_line":3,"end_column":6,"label":"immutable","related":[{"file":"bad.br","line":2,"column":9,"end_line":2,"end_column":10,"label":"declared here"}],"notes":[],"help":["declare it with `var x = ...` to make it mutable"],"fixes":[]}
Field Meaning
severity "error" or "warning"
code the stable code ("E0401"), or null for command-line errors
message the one-line message
file, line, column, end_line, end_column the primary span (1-based; the end is exclusive)
label the text under the primary span
related other spans, each with its own location and label
notes, help the = note: and = help: lines
fixes machine-applicable edits: {line, column, end_line, end_column, replacement}

Errors in the command line itself also come out as JSON when --json is given:

{"severity":"error","code":null,"message":"unknown command `chek`; did you mean `brine check`? (see `brine help`)"}

Other JSON records each command prints:

Command Records
check {"ok":true,"checks_proven":N,"checks_remaining":N}; with --report, a {"runtime_check","location","code"} line per check
build {"built":"<path>"}
test {"test","ok","seconds","output"} per test
fmt --check {"unformatted":"<path>"} per file
new {"created":"<name>"}
explain {"code","title","explanation"}
doctor {"check","ok","detail"} per check
version {"version":"0.4.0"}

For run, the program’s own output is not wrapped: it passes through as is.

Code When
0 success
1 the program has errors (check, build, run, test, debug); a test failed; fmt --check found a file to reformat or fmt hit a syntax error; doctor found a problem; a program’s main returned Err
2 a problem with the command itself: unknown command or option, a missing or bad value, a file that doesn’t exist, no project found, an invalid brine.toml, an unknown error code for explain
101 a program panicked (overflow, an out-of-bounds index, a failed assert, …)
other brine run returns the program’s own exit code (fn main() -> int)

Warnings never change the exit code.

Three more commands exist for working on the compiler itself. They aren’t in --help and may change in any release:

Command Prints
brine lex [--comments] <file> the tokens
brine parse <file> the syntax tree
brine emit-c [--tests] [--release|--debug] <file> [-o out.c] the generated C (this is how the bootstrap seed is made)