tripon[github]

The specification

Three files define the language, and they are not documentation of the compiler — the compiler is an implementation of them. When the two disagree, the files are right and the code is a bug.

filefixes
spec/grammar.ebnfthe syntax, in plain EBNF, one file
spec/tripon.mdthe semantics: identity, tiers, and the type contract T1–T9
spec/errors.csvthe diagnostic table: code, severity, name, hint

The grammar

spec/grammar.ebnf uses plain EBNF — concatenation, *, +, ? and grouping — so that any parser generator, or any agent, can read it without a dialect. A file is a sequence of declarations:

ebnf
File = { Declaration } ;

Declaration = TierDecl | UseDecl | FnDecl | StructDecl | EnumDecl | ConstDecl ;

TierDecl = "use" "tier" Ident "tier" ";" ;         (* e.g. use tier std; *)

FnDecl = "fn" Ident "(" [ ParamList ] ")" [ "->" Type ] Block ;

StructDecl = "struct" Ident "{" { Field } "}" ;
Field = Ident ":" Type ";" ;

EnumDecl = "enum" Ident [ "<" TypeParams ">" ] "{" Variant { "," Variant } [ "," ] "}" ;
Variant = Ident [ "(" Type { "," Type } ")" ] ;

Expressions are C-family precedence, and the file ends with the precedence list rather than leaving it to be discovered:

ebnf
(* ================= Precedence (high to low) =================
  ()  [] .        primary/postfix
  **              power
  unary ! - &     
  * / %
  + -
  < > <= >=
  == !=
  &&
  ||
  spread ?        (T1, lowest unary force-pop)
  = assignments   (statement-level)
============================================================= *)

The scope is tier 0 and tier 1 syntax. Tier 2 and tier 3 grammar is additive and must never change a v0 production, which is what keeps code written against the smallest tier valid as the language grows.

The type contract

Section 3 of spec/tripon.md is the part of the language that is not negotiable, and it is written as nine rules:

rulewhat it fixes
T1fully static typing: locals infer, signatures are explicit
T2no implicit conversions, ever — even widening is cast<i64>(x)
T3no null; opt<T> with some and none, unwrapped by ? or match
T4errors are values; an ignored result is E0205
T5match over an enum covers every variant, or uses _ deliberately
T6no undefined behaviour: overflow traps, indexing is bounds-checked
T7raw pointers exist only inside an extern "C" fence
T8const cannot be reassigned; single-owner moves and read-only borrows
T9type width is explicit, and the default literal types are i32 and f64

The rules are numbered so that a diagnostic can cite one, and so that a discussion about a change can be about a rule rather than about a line of C. Section 7 lists what is deliberately still open — closure capture, the async slot, the full borrow checker, the generics ABI — so that "not decided yet" is a recorded state rather than an omission.

The error table

spec/errors.csv is one row per code, and every consumer reads the same four columns: the compiler emits the hint from it, the goldens in tests/expected/ assert the whole JSON object built from it, and the diagnostic codes page renders it. Adding a code means appending a row — never inserting one, never renumbering, never reusing a retired code.

How a change is made

The order is the point, and it is the same for a semantic change, a new feature or a new diagnostic:

  1. Change the spec — the grammar, the contract, or the table.
  2. Change the compiler.
  3. Change the goldens and the tests.

A pull request that reverses that order is a bug report about the spec waiting to happen. Beyond the ordering, two gates are absolute: make test — 42 checks — and sh tests/determinism.sh must both pass, under gcc and clang, because the language's promise that the same source compiles the same way is only worth something if it holds on more than one compiler.

every acceptance run, every phase
make test
42 checks passing, gcc and clang
sh tests/determinism.sh
byte-identical on every build

Why a spec and not just a compiler

A language whose behaviour is defined by its implementation can only be read by reading its implementation, which is a thick book in C. Publishing the grammar, the contract and the codes makes three things checkable that otherwise are matters of opinion: whether a program is valid, what a diagnostic means, and whether a change to the compiler broke either. It is also what makes self-hosting a measurable goal rather than a vibe — the ports are held to the bootstrap's output, byte for byte.