A compiled language for low-resource systems.Built to be read by agents.
Tripon is a small, statically typed language for machines with very little room: firmware, microcontrollers, bootloaders. There is no null and no implicit conversion here, and the mistakes that are undefined behavior in C become traps that name their code and their line. The whole toolchain is one static binary, and every diagnostic it prints is also valid JSON.
git clone https://github.com/TriponOSS/tripon && cd tripon && makeNeeds a C99 toolchain and nothing else.
Red to green in one round trip
This is a real session against the compiler in this repository. Line 7 of main.tp multiplies an i32 by an f64, and ralt reports the exact span, so the fix is a computation rather than a guess.
Machine-readable by default
ralt check --json writes one array, in lex → parse → typecheck order. Nothing is batched behind the first real error.
Codes that never move
E0202 means the same thing today and in ten releases. The table is append-only: never renumbered, never reused.
A fix hint in the payload
Every diagnostic carries its healer's hint — or run ralt explain E0202.
ralt check main.tp --json [ { "code": "E0202", "kind": "error", "name": "type-mismatch", "file": "main.tp", "line": 7, "col": 25, "len": 7, "msg": "i32 and f64 do not share an operator; no implicit conversion exists", "hint": "types must match exactly; no implicit conversion exists; use cast<T>(x)" } ]
No undefined behavior, and not as a slogan
None of this is a promise in the documentation. The code generator emits these checks on both backends, so every program built by ralt carries them.
| the classic | in Tripon | outcome |
|---|---|---|
| integer overflow | traps, exit 101, E9001 — or wraps with -fast | defined |
| division and modulo by zero | traps, E9003 | defined |
| index out of bounds | bounds-checked on every access, traps E9002 | defined |
| read of an uninitialized local | locals are zero-initialized | defined |
| an ignored error | a discarded result is a compile error, E0205 | won't build |
| a silent narrowing or widening | cast<T>(x) or nothing; E0207 otherwise | won't build |
ralt run overflow.tp tripon trap E9001 integer-overflow-trap at overflow.tp:4 echo $? 101
A language you already know the shape of
Braces, semicolons, let and fn: there is nothing to relearn, and nothing underneath that an agent could reasonably read two ways.
use tier core; fn fib(n: i32) -> i32 { let a = 0; let b = 1; let idx = 0; while idx < n { let next = a + b; a = b; b = next; idx += 1; } return a; } fn main() -> i32 { let n = 0; while n < 10 { print_i32(fib(n)); n += 1; } return 0; }
ralt run examples/fib.tp 0 1 1 2 3 5 8 13 21 34
Data lives by value, variants carry payloads, and match is exhaustive — a missing arm is E0206. Lossy conversions are explicit: cast<i16>(x) truncates by definition.
i8throughu64,isize,usize,f32,f64,bool,str,voidresult<T, E>with?propagation; an ignored result will not buildopt<T>withsomeandnone; passing a null is E0204- fixed arrays are bounds-checked by construction, and len is compile-time
Pick how much runtime you can afford
Firmware is not a server, so you declare what each file is allowed to use. Reach for something above your tier and the build fails with E0301 — never “kinda works, probably”.
The kernel tier has no allocator at all, so malloc cannot exist there. A stdlib module declares its own tier, and a use raises your requirement rather than degrading it.
ralt check firmware.tp firmware.tp:12:9: error: feature [arena] requires tier core; current tier is kernel [E0301 tier-violation] feature requires a higher tier; raise the tier target or rewrite the call
Milliseconds to build, kilobytes to ship
We optimize build speed first, because it is what you pay on every iteration. The default pipeline runs no optimizer at all; -O fast turns the passes on once the types prove them safe.
build full pipeline : 333 ms check only : 4 ms emit c only : 5 ms binary size : 17728 bytes
Measured cold-cache by bench/build_bench.sh, which runs on any box. ralt check is the loop an agent lives in — four milliseconds.
- P0
- Spec — grammar, contract, frozen error table
- P1
- Bootstrap compiler in C99 — lex, parse, typecheck, codegen
- P2
- Agent tooling — fmt, doc, lsp, deps, tokens oracle
- P3
- Self-host — lexer and parser ports replay their oracles byte for byte; the typechecker port is next
- P4
- Native x86-64 — scalars and floats at byte parity; aggregates next
Phases are tracked in PLAN.md, not a roadmap slide. Determinism is tested: the same source compiles byte-identically on every build, with gcc and clang.
One binary, six directories
spec/ is the source of truth, not the C code. Each pass is one C file with no dependencies, and selfhost/ holds the same passes written in Tripon.
make test 42 checks passing, gcc and clang sh tests/determinism.sh byte-identical on every build
Changing a semantic means changing the spec first, then the code, then the goldens in tests/expected/ — and the suite must pass under both compilers.
Read on
Tripon is MIT licensed and developed in the open at TriponOSS/tripon. Four bands of diagnostics, 23 codes in total, and 4 exit codes to branch on.