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

Enabling the Dialect

The default is strict-ink

If you don’t ask for anything, brink compile compiles plain ink. Every construct this chapter describes — ~ { … } blocks, #[…]/#{…} literals, postfix indexing, push/insert/remove/remove_at — is a compile error under the default dialect, strict-ink:

brink compile story.ink -o story.inkb

If story.ink contains, say, ~ x = #[1, 2, 3], that command fails and the CLI reports:

ERROR brink: story.ink:6..16 [E051] `#[…]` array literal is a brink extension — this project compiles strict ink (dialect = brink to enable)
ERROR brink: 1 diagnostic(s) prevented compilation

The CLI renders every resolved diagnostic — the source path, its byte range, the [CODE], and the message — with the count still printing as a trailing summary underneath. Driving the compiler as a library and reading CompileError::Diagnostics (see below), or using brink ide / @brink-lang/web, gets you the same resolved set programmatically.

Opt in explicitly with --dialect brink:

brink compile story.ink -o story.inkb --dialect brink

With that flag, the same source compiles, and the extension surface described in the rest of this chapter is available.

Why strict by default

This is a deliberate choice, not an oversight. Two things are true about brink at once:

  • Its correctness is anchored to a C# ink oracle — thousands of golden transcripts generated by the reference inklecate/ink-engine implementation. A story that only uses plain ink constructs can be checked, mechanically, against what real ink does.
  • The dialect extensions have no such oracle. Nothing in the ink ecosystem has ever run a multi-line ~ { … } block or a #[…] array literal, so there is no reference implementation to diff against — only the spec’s own hand-derived expected output (see Conformance).

Defaulting to strict-ink means a project has to make a visible, one-time choice to leave the oracle-anchored subset. You don’t fall out of conformance by accident because you typed # in the wrong place; you fall out of it by passing --dialect brink (or setting the equivalent brink.toml [project] dialect), and that’s exactly the choice this flag exists to make explicit.

The compiler’s own test suite follows the same rule: the entire oracle corpus — every .ink file with a golden C# transcript — compiles under strict-ink. If a dialect extension ever leaked into that corpus, or if strict-ink ever started accepting extension syntax, the CI gate that pins 5,598 passing oracle episodes would fail immediately.

What doesn’t change

The dialect is a compile-time-only setting:

  • One grammar regardless of dialect. brink-syntax always parses the full superset. The dialect only changes whether analysis accepts or rejects the extension constructs it finds — parsing itself never fails on them. This is why the IDE, formatter, and diagnostics don’t need a second grammar mode: they see the same tree either way.
  • Never embedded in the compiled story. Dialect is an AnalysisOptions input, consumed entirely by the compiler pipeline. It has no representation in .inkb, and the runtime has no concept of it — a compiled brink-dialect story and a compiled strict-ink story are indistinguishable bytecode as far as brink-runtime is concerned. Loading and playing back .inkb never depends on which dialect produced it.
  • Per-compile, not per-file. There’s one dialect setting for the whole compilation (entry point plus every INCLUDEd file). You can’t mix strict-ink and brink-dialect files in the same build.

If you’re driving the compiler as a library rather than through the CLI, the equivalent is AnalysisOptions::dialect:

#![allow(unused)]
fn main() {
extern crate brink_compiler;
use std::path::Path;
use brink_compiler::{AnalysisOptions, Dialect, compile_path_with_options};

let options = AnalysisOptions {
    dialect: Dialect::Brink,
    ..AnalysisOptions::default()
};
let output = compile_path_with_options(Path::new("story.ink"), options)?;
Ok::<(), Box<dyn std::error::Error>>(())
}

AnalysisOptions::default() — and therefore plain compile_path / compile — always means Dialect::StrictInk.