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-syntaxalways 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.
Dialectis anAnalysisOptionsinput, 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 asbrink-runtimeis concerned. Loading and playing back.inkbnever 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.