______ _
/_ __/___(_)_ __
/ / / __/ /\ \/ / Stack-Based Interpreter & VM
/ / / / / / > · < C++23 · Single-Header Library
/_/ /_/ /_/ /_/\_\ Copyright 2026 Mark Guidarelli
Embeddable stack-based scripting VM with cooperative concurrency and fault tolerance.
C++23 | Single-Header | ASan/UBSan clean | Apache 2.0
3 4 add = % 7
/square { dup mul } def
5 square = % 25
[1 2 3] { square } map == % [1i 4i 9i]
Sample output from the Trix program
examples/amazing.trx — ten maze algorithms across five
grid topologies. More in the gallery.
Going big — a 400×400 maze (160,000 cells) as a magma
distance heatmap. The full million-cell --monster lives in the
gallery.
Lua gives you embeddable scripting but no concurrency beyond coroutines. Erlang/BEAM gives you actors and supervision but won't fit in your binary. Trix sits in the gap: an actor/supervision model as a header-only C++23 library, deterministic memory on the local arena (no GC there -- save/restore reclaims it) backed by a durable global region with a precise mark-sweep GC, and a serializable whole-VM state.
Trix is also safe by construction. Every value is strongly typed with
zero implicit coercion: a type mismatch raises a catchable error instead of
silently reinterpreting bits. Arithmetic never silently produces a garbage
value -- integer add/sub/mul raise /numerical-overflow rather than
wrapping (widen to Long or Int128 to extend the range), and a float
overflow or inf/nan propagation raises /numerical-inf by default. And
ordinary programs are memory-safe: the mark-sweep GC and the transactional
arena leave no dangling references or use-after-free.
{ 2147483647 1 add } try = % /numerical-overflow (Integer maxed out, caught)
2147483647l 1l add = % 2147483648 (widen to Long, fine)
{ 3.4e38 3.4e38 mul } try = % /numerical-inf (Real overflow, caught)
{ 3 (hello) add } try = % /type-check (no silent coercion, caught)
And the name? It's a nod to the author's cat: the (_) eye and >·<
whiskered nose in the logo are an homage to her. Trixie is the cat
in concatenative.
Trix is a concatenative (PostScript/Forth-style) stack language: values go on the stack,
operators consume them. It is implemented as a single-header C++23 library
(68 .inl files assembled by trix.h).
Output conventions in the examples below: = pops and prints a value
(shows the type name for composites like arrays), while == pretty-prints
the full structure. The i suffix on printed integers (e.g., 1i) is
the numeric-type tag -- Trix distinguishes Integer, UInteger, Long,
ULong, and others in the tagged-union value representation.
% Variables and procs
/double { 2 mul } def
[1 2 3] { double } map == % [2i 4i 6i]
% Recursion
/factorial { dup 1 le { pop 1 } { dup 1 sub factorial mul } if-else } def
10 factorial = % 3628800
% Strings with interpolation
/name (world) def
(hello \{name}!) = % hello world!
% Infix math
$(3 + 4 * 2) = % 11
% Dicts
<< /name (Alice) /age 30 >>
/name get = % Alice
Infinite data structures with streaming evaluation -- nothing materializes until you ask for it:
% Infinite sequence: generate all even squares, take the first 5
1 lazy-from { dup mul } lazy-map
{ 2 mod 0 eq } lazy-filter
5 lazy-take lazy-to-array == % [4i 16i 36i 64i 100i]
Spreadsheet-style incremental computation -- derived values recompute automatically when their dependencies change:
/width 10 cell def
/doubled { width cell-get 2 mul } cell-computed def
doubled cell-get = % 20
15 width cell-set
doubled cell-get = % 30 (recomputed automatically)
Isolated processes with mailboxes, supervised under Erlang/OTP-style
restart policies. This worker prints messages, throws on /crash,
and is restarted by its supervisor:
<<
/strategy /one-for-one
/intensity 5 /period 1000
/children [
<< /id /printer
/start { { actor-recv dup /crash eq { /crash throw } if (got: ) print = } loop }
/restart /permanent >>
]
>> supervisor /sup exch def
sup /printer supervisor-get-child pop /w exch def
(hello) w actor-send 100 coroutine-sleep % => got: hello
/crash w actor-send 200 coroutine-sleep % worker dies; supervisor restarts
sup /printer supervisor-get-child pop /w exch def % handle for the restarted actor
(after-restart) w actor-send 100 coroutine-sleep % => got: after-restart
Prolog-style unification and backtracking, built on the save/restore transaction mechanism:
% A tiny fact base of (parent child) pairs, queried by unification:
% "who are Bob's children?" child is the unknown; find-all collects every match.
/child logic-var def
[
{ [ /bob child ] [ /bob /amy ] unify guard child deref }
{ [ /bob child ] [ /dawn /amy ] unify guard child deref }
{ [ /bob child ] [ /bob /carl ] unify guard child deref }
{ [ /bob child ] [ /bob /eve ] unify guard child deref }
] find-all == % => [/amy /carl /eve] (/dawn fails to unify)
Both 32-bit Real and 64-bit Double are first-class, backed by ~99
floating-point operators: the full <cfenv> environment (rounding modes,
exception flags, atomic hold/update), IEEE 754-2019 total-ordering and quiet
comparisons, NaN payloads, ULP-based comparison, Kahan-compensated summation,
and the C++17 special functions (Bessel, elliptic integrals, Riemann zeta).
Because that lives in the REPL with exact IEEE-754 semantics, Trix doubles as a bench for prototyping a numeric algorithm before you commit it to C/C++:
2.5 round-even = % 2 (roundTiesToEven -- banker's rounding)
2.0 sqrt ulp = % 1.1920929e-07 (spacing to the next value)
[1.0 2.0 3.0] kahan-sum = % 6 (compensated summation)
42u nan-with-payload nan-payload = % 42 (diagnostic payload survives the NaN)
Trix lets you declare a value's storage class (the GC-free local arena vs.
the durable global VM), its mutability (read-only or writable), and a proc's
binding strategy (early or late) right at the literal -- not through separate
runtime operators. The $ / $$ sigils mean global / force-local in every
position (a name, a block, or a literal's suffix), and defaults follow the
enclosing scope, so you only reach for a sigil to override one.
(api-key)#r % read-only string -- mutation raises /read-only
${ /registry 64 dict def } % allocate the dict in the durable global VM
[1 2 3]#$$ % force this array into the GC-free local arena
{ dup mul }#e % early-bound proc -- names resolved once, at creation
The full sigil grammar -- storage (#= scratch, #$ / #$$ global / local),
access (#r / #w), and proc form and binding (#a / #p, #e / #l) -- is
in the scanner reference.
Trix provides cooperative concurrency primitives -- coroutines, pipelines, actors, and supervision trees -- without threads or external runtimes.
The local execution arena is GC-free: save creates a checkpoint and
restore rolls back to it, reclaiming everything deterministically. The
durable global region uses a precise mark-sweep GC. The entire VM state
can be serialized to disk and resumed later (snap-shot/thaw).
| Capability | Description |
|---|---|
| Coroutines | Cooperative scheduling with sleep, yield, join |
| Pipelines | Bounded-buffer pipelines with automatic backpressure |
| Actors | Isolated message-passing processes with mailboxes |
| Supervision | Erlang/OTP-style restart policies and fault isolation |
| Logic | Prolog-style unification and backtracking |
| Reactive cells | Spreadsheet-style incremental computation |
| Lazy sequences | Infinite streams with deferred evaluation |
| Durability | Serialize entire VM state to disk and resume |
A source-level interactive debugger ships with Trix: ./trix --inspect FILE opens a terminal TUI for single-stepping, plain/conditional/one-shot
breakpoints, watch expressions, and live inspection of every VM stack, plus a
sandboxed :p prompt for evaluating Trix at the halt point. Its entire
user-facing UI is written in Trix itself
(lib/debugger.trx, ~1900 lines) over a thin layer of C++
intrinsics -- a working demonstration of the language. See
docs/debugger.md.
Two dozen example programs in examples/ showcase the language end to end -- from logic solvers and reactive models to full emulators and interpreters. The highlights:
| Program | Demonstrates |
|---|---|
| zmachine.trx | Infocom Z-machine covering V1-V5, V7, V8 (no V6 graphical) -- runs interactive-fiction story files, fetched separately (none bundled) |
| amazing.trx | Maze zoo -- ten algorithms across five grid topologies, distance-field heatmaps, PNG output |
| mini-scheme.trx | Metacircular Scheme: CEK machine, first-class continuations, call/cc |
| tetrix.trx | Playable terminal falling-block game -- SRS rotation, T-spins, lookahead AI |
| chip8.trx | CHIP-8 / Super-CHIP emulator for the classic 1977 VM |
| raycaster.trx | First-person 3D dungeon via DDA ray casting |
| regex.trx | Thompson-NFA + Pike-VM regex engine with a ReDoS head-to-head |
| minidb.trx | In-memory SQL: records-as-schemas, save/restore transactions |
| symdiff.trx | Symbolic differentiator -- tagged-AST rewrite to a fixpoint |
| sudoku.trx | Logic solver -- unify / choice / backtracking with automatic rollback |
| genealogy.trx | Prolog-style knowledge engine -- find-all, unify-match, negation-as-failure |
| heist.trx | Set-cover optimizer -- greedy vs. exhaustive optima over Trix's Set primitives |
| village.trx | Actors + supervision + reactive cells, snapshotted and resumed in forked processes |
| supervised-word-count.trx | Fault-tolerant pipeline -- a poisoned worker crashes and the supervisor restarts it |
| reactive-financial-model.trx | Reactive spreadsheet cells + transactional save/restore what-if scenarios |
| effects_mini_scheme.trx | Algebraic effects -- swap evaluator semantics by stacking handlers |
See examples/README.md for the full catalog and run instructions.
Concurrency Computation
+---------------------------------+ +---------------------------+
| Layer 3: Supervision | | Reactive Cells |
| monitor, link, restart trees | | incremental recompute |
+---------------------------------+ +---------------------------+
| Layer 2: Actors | | Logic / Backtracking |
| mailboxes, send/recv, match | | unify, choice, guard |
+---------------------------------+ +---------------------------+
| Layer 1: Pipelines |
| bounded buffers, backpressure |
+---------------------------------+
| Coroutines |
| cooperative scheduling |
+---------------------------------+
| Snap-shot / Thaw (durability) |
| serialize entire VM to bytes |
+---------------------------------+
The concurrency stack is the core of the project: an embeddable actor/supervision model in the style of Erlang/OTP. The computation side adds Prolog-style constraint solving and reactive dataflow, both useful in scripting and difficult to add retroactively.
All scheduling is cooperative and single-threaded. There is no preemptive multitasking.
curl -LO https://github.com/mcguidarelli/trix/releases/latest/download/trix-linux-x86_64.tar.gz
curl -LO https://github.com/mcguidarelli/trix/releases/latest/download/trix-linux-x86_64.sha256
sha256sum -c trix-linux-x86_64.sha256
tar xzf trix-linux-x86_64.tar.gz
cd trix-v*-linux-x86_64
./trix --versionEvery release also ships a versioned tarball (trix-vX.Y.Z-linux-x86_64.tar.gz) for archival; the unversioned name above always points to the latest release. See the releases page for older versions and full source snapshots.
Runtime dependencies: libreadline and zlib (both optional — a
--no-readline / --no-zlib build drops them). On Ubuntu/Debian:
sudo apt-get install libreadline8 zlib1g.
git clone https://github.com/mcguidarelli/trix.git
cd trix
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/trix # interactive REPL
./build/trix script.trx # run a script
./build.sh && ./runtests.sh # quick rebuild at repo root + run the test suiteRequires: C++23 compiler (gcc-15 or clang-20+), libreadline-dev, zlib1g-dev, cmake 3.25+
(readline and zlib are optional — see --no-readline / --no-zlib in BUILDING.md).
See BUILDING.md for the full build reference -- build.sh modes,
CMake options, the build directories, and testing/fuzzing/benchmarking.
Platforms: Linux (tested, gcc-15 and clang-20). macOS is expected to work (POSIX-based, no platform-specific code) but is not regularly tested. Windows is not currently supported.
CMake options:
| Option | Default | Effect |
|---|---|---|
TRIX_SANITIZERS |
ON | Enable ASan + UBSan |
TRIX_WERROR |
ON | Treat warnings as errors |
TRIX_FUZZ |
OFF | Build libFuzzer harnesses (requires clang) |
Release build (no sanitizers, optimized):
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRIX_SANITIZERS=OFF
cmake --build buildtrix # interactive REPL (no filename)
trix script.trx # run a script, then exit
trix script.trx a b c # pass args (script reads them via command-line-args)
echo "1 2 add =" | trix --stdin # run a program piped on stdin
trix --image snap.trix # thaw and resume a saved VM snapshot
trix --version # print version and feature flags
trix --help # full flag listCommon flags: -i / --stdedit (force the REPL), -q / --quiet
(suppress diagnostics), --sandbox (disable filesystem / system / raw-memory
ops), --module-path DIR:DIR (extra require search dirs), and the VM-tuning
knobs (--vm-size, --operand-depth, ...). See
docs/cli.md for every flag, the five startup modes, the module
search order, and the exit-code mapping.
#include "trix.h"
int main() {
Trix trx; // default 1 MB VM, interactive REPL
return 0;
}Or with configuration:
#include "trix.h"
int main(int argc, char *argv[]) {
auto result = Trix::parse_args(argc, argv);
if (result.should_exit) return result.exit_code;
Trix trx(result.vm_size, result.config);
return 0;
}Add custom operators:
static void my_square_op(Trix *trx) {
trx->verify_operands(Trix::VerifyInteger);
auto val = trx->m_op_ptr->integer_value();
*trx->m_op_ptr = Trix::Object::make_integer(val * val);
}
static constexpr Trix::Operator user_ops[] = {
{my_square_op, "my-square"},
{nullptr, {}}, // null terminator
};
// ...
result.config.m_useroperators = user_ops;The interpreter runs synchronously inside the Trix constructor -- the script
(or REPL) has fully executed by the time the constructor returns, and
exit_code() is read after it.
For a long-lived compute server, pass --resident (or set
Config::m_resident): instead of exiting when its startup script drains, the
instance parks and serves work delivered from host threads (invoke() runs a
Trix buffer; raise_interrupt() fires a handler), until a quit or
Trix::ExitIRQ stops it. See
Host Integration for the full embedding surface:
the VM lifecycle and resident/server mode,
the thread-safe raise_interrupt / raise_error / invoke methods, the
user-operator and interrupt APIs, and the complete Config table.
| Category | Operators |
|---|---|
| Core (stack, math, string, array, dict, set, coroutines, lazy, ...) | 661 |
| Pipelines (bounded buffers, backpressure) | 21 |
| Actors (mailboxes, selective receive) | 18 |
| Supervision (monitor, link, restart trees) | 18 |
| Logic / Backtracking (unify, choice, cut, find-all) | 20 |
| Reactive Cells (incremental computation) | 21 |
| Composability (protocols, matching, GenServer, transducers, ...) | 36 |
| Infix expressions, modules, contracts | 34 |
| Total | 829 |
| Metric | Value |
|---|---|
| Source | ~85,000 lines C++23 (68 .inl files) |
| Operators | 829 |
| Test assertions | 20,200+ across 274 test files |
| Fuzz testing | libFuzzer harness (full interpreter) |
| Interpreter dispatch | ~47M ops/sec (representative mix; see benchmark/) |
| Default VM size | 1 MB (configurable, minimum 256KB) |
| Sanitizers | ASan + UBSan clean (zero undefined behavior) |
| Dependencies | readline, zlib (optional; --no-readline / --no-zlib) |
| License | Apache 2.0 |
Trix compiles cleanly under GCC 15 with -Werror and an extensive warning
set (47 flags across GCC and Clang). The principal GCC flags:
-Wall -Wextra -Wpedantic -Wformat=2 -Wformat-overflow=2
-Wformat-truncation=2 -Wformat-security -Wnull-dereference
-Wstack-protector -Wtrampolines -Walloca -Wvla -Warray-bounds=2
-Wimplicit-fallthrough=3 -Wshift-overflow=2 -Wcast-qual
-Wstringop-overflow=4 -Wconversion -Warith-conversion -Wlogical-op
-Wduplicated-cond -Wduplicated-branches -Wformat-signedness
-Wshadow -Wundef -Wstack-usage=16000 -Wswitch-enum -Wnrvo
Includes -Wconversion (no implicit narrowing), -Wswitch-enum
(every enum case handled), -Wshadow (no variable shadowing), and
-Wduplicated-cond / -Wduplicated-branches (no copy-paste bugs).
31 types in an 8-byte tagged union (30 user-visible + SourceLoc, 1 reserved slot). Every value on every stack and in every container is an Object.
Numeric: Byte, Integer, UInteger, Long, ULong, Int128, UInt128, Address, Real, Double | Logical: Boolean, Null | Containers: Array, Packed, String, Dict, Set | Named: Name, Operator, Mark | Composite: Tagged, Record, Curry, Thunk | Runtime: Stream, Coroutine, PipeBuffer, Cell, Continuation, OpaqueHandle (Screen), SourceLoc
64-bit values (Long, ULong, Double, Address) use heap-allocated extension slots with automatic save/restore journaling. 128-bit values (Int128, UInt128) use 16-byte wide-value slots.
See Type System for details.
| Trix | Lua | Wren | Janet | Squirrel | |
|---|---|---|---|---|---|
| Concurrency primitives | Coroutines + pipelines + actors + supervision | Coroutines | Fibers | Fibers | Threads (cooperative) |
| Fault tolerance | Supervision trees (Erlang/OTP-style) | Manual | Manual | try / protect |
Manual |
| Logic programming | Unify + backtrack | No | No | No | No |
| Reactive computation | Cells + watchers | No | No | No | No |
| Whole-VM durability | Snap-shot / thaw to disk | No | No | marshal (values only) |
No |
| Memory model | Local arena (save/restore) + mark-sweep GC for the global region | Incremental GC | Mark-sweep GC | Mark-sweep GC | Reference counting |
| Maturity | New (single author) | 30+ yrs, broad ecosystem | ~10 yrs | ~10 yrs | ~20 yrs |
Trix is positioned in the gap left by these languages: a header-only embeddable scripting VM with built-in actor/supervision primitives. The cost is a smaller ecosystem, a stack-based syntax that's unfamiliar to most modern developers, and single-author maturity.
Trix takes the Erlang/OTP supervision model and compiles it down to a header-only library you can embed in a C++ application. It is not a replacement for BEAM in production distributed systems: cooperative single-threaded scheduling, no clustering, no hot code reload, no preemptive process isolation across CPU cores. If you need any of those, use BEAM.
A libFuzzer harness covering the full interpreter is in fuzz/.
Requires clang (libFuzzer is LLVM-only):
./fuzz/build.sh
./fuzz/run.sh # single-shot; stops on first event
./fuzz/run.sh -max_total_time=300 # budget-aware loop, 5 minutes
./fuzz/run.sh -overnight # 8-hour run with 2MB VM heapSee fuzz/README.md for the full CLI, triage workflow, and corpus layout.
- Getting Started -- a from-zero tour of the language
- Cookbook -- task-oriented "how do I..." recipes, each a short runnable block
- Trix Reference -- all operators, stack effects, types
- Running Trix -- command-line reference: every flag, startup modes, exit codes
- From PostScript -- guide for PostScript programmers: what carries over, what's renamed, what's new
- Coroutines -- cooperative scheduling, yield, join
- Pipelines -- bounded buffers, backpressure
- Actors -- mailboxes, send/recv, selective receive
- Supervision -- monitors, links, restart strategies
- Logic -- unification, backtracking, choice points
- Reactive Cells -- incremental computation, watchers
- Lazy Sequences -- infinite streams, transformers
- Functional Programming -- map, filter, fold, curry
- Architecture Overview -- whole-engine map: source layout, execution pipeline, subsystem-to-file table
- VM Internals -- heap, stacks, object system
- Save/Restore -- transaction journaling
- Snapshot/Thaw -- VM serialization
- Type System -- 8-byte tagged union design
- Tagged Values -- discriminated unions, ADTs
- Records -- immutable named-field composites
- Pattern Matching -- let, destructure, match
- Protocols -- open type dispatch
- Modules -- scoped namespaces, require/use/import
- Transducers -- composable data transformations
- Contracts -- precondition/postcondition checking
- Dates and Times -- instants, calendar dates, formatting
- Source-Level Debugger -- interactive
--inspectTUI, self-hosted inlib/debugger.trx
- Architecture Overview -- start here: how the source is organised and how a program flows from text to result
- Adding Operators -- step-by-step checklist
- Control Operators and the Coroutine Trampoline -- the
@-control-op mechanism behind every blocking/yielding/resumable op - Invariants -- critical rules for contributors
- Glossary -- terminology reference
The language and runtime are feature-complete. All concurrency layers (coroutines, pipelines, actors, supervision) and computation features (logic, reactive cells) are implemented and tested.
Current release: 0.9.0 -- the release-readiness pass is complete (documentation audit, sad-path test expansion, fuzzing campaigns, example showcase hardening, and a performance pass). Remaining before 1.0: an API-stability soak.
60 reference documents cover all subsystems, indexed at docs/index.md. See docs/vm-internals.md and docs/gvm-heap-gc.md for the VM architecture, CONTRIBUTING.md for how to build and test, STYLE.md for code conventions, and CHANGELOG.md for release notes.
Copyright 2026 Mark Guidarelli. Licensed under the Apache License 2.0. See LICENSE for details.

