Three tools, one specification
Unicode lexer, LR(k)/LALR(k) parser, token channels and an explicit AST with named fields and variants.
Everything in .ag, plus a semantic contract and analysis: typed actions, inferred analyzer
signatures and generated Rust analyzers.
Everything in .sema, plus an IR executor, lowering and backends: C11 and LLVM for the
constructs they support.
Each file is self-contained: a .sema or .coge file carries its own grammar
and lexer.
Deterministic, and it tells you early
Like Bison, Borsuk builds LR tables, but with lookahead k greater than one when the grammar needs it. Conflicts are found when the table is built, for the grammar itself and without sample inputs, and the generated parser never backtracks. LL(*) and GLR parsers accept such grammars and show ambiguity only on a concrete input.
Like ANTLR, a grammar is a readable file with lexer rules, channels and an AST shape. Conflicts you accept
are resolved by name, as the classic dangling else here.
Parsers are built into versioned, integrity-checked packages with C++ and Rust runtimes, or exported as static Rust parsers, so a program loads a pinned parser instead of regenerating it at startup.
grammar ToyC;
options {
parser = LR;
lookahead = 1;
ast = explicit;
}
conflicts {
prefer shift ELSE over reduce ifStatement#IfStatement;
}
node ifStatement
: IF B thenBranch=statement #IfStatement
| IF B thenBranch=statement ELSE elseBranch=statement #IfElseStatement
;
inline statement
: value=ifStatement
| value=compoundStatement
;
IF : 'if';
ELSE : 'else';
WS : [ \t\r\n]+ -> channel(HIDDEN);
An excerpt of if-else-prefer-shift.ag.
What else it has
Unicode lexing
UTF-8 input, longest match, fragments, greedy and lazy quantifiers, token channels and lexer classes scoped by the parser.
Execution and code generation
Typed IR interpreters, execution profiles and a Structured Core IR. ToyC shows the C11 and LLVM backends; ToyCP shows interpretation with classes, inheritance, construction and cleanup.
Migration and inspection
Grammar and analysis projections, specification templates, completeness reports, inspection of checked models and the provenance of every output.
Honest limits
Unsupported constructs are rejected explicitly, never half-handled. The format guides list what each tool supports today.
Build and try
Borsuk needs a C++20 compiler, CMake 4.0 or newer, ICU (uc), nlohmann_json 3.11 or
newer, and the Żbik submodule. Generated Rust needs Cargo with the 2024 edition.
git clone --recursive https://github.com/parstools/borsuk.git
cd borsuk
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target ag sema coge -j2
build/bin/ag --check ag/grammars/examples/cmm.ag
build/bin/coge --check --contracts contracts/toyc-runtime-v1.json coge/examples/toyc/toyc.coge
Normal builds use pinned parser artifacts and need neither Java nor ANTLR. The README walks through ToyC and ToyCP from specification to running interpreter.
Documentation
Most documents also have a Polish version, with the .pl.md suffix.