The programming language that knows why.
Trace is a compiled, statically-typed, general-purpose programming language built around one radical idea: tracelity is a first-class feature.
Every value in Trace can carry a lightweight chain that records where it came from, why it has its current value, and what computations produced it. When something goes wrong, you don't add print statements and re-run — you ask the value itself.
let tracel result: tracel int = compute(user_input, db_record);
why result;
// → let result at line 3
// ← compute() returned 0
// ← db_record was null
// ← query timed out at 14:03:22
Modern software has a tracelity problem. Every major pain point in production systems reduces to the same question: why is this value wrong?
| Problem | Traditional answer | Trace answer |
|---|---|---|
| Debugging | Add print statements, re-run | why x; — the value explains itself |
| Distributed failures | Grep through logs | Tracel chain spans service boundaries |
| AI/ML wrong outputs | More logging frameworks | Chain propagates through every transformation |
| Data pipeline errors | Stack traces show where, not why | why result == null; |
| Security auditing | External audit trails | Chain is structural, in the binary |
Trace makes tracelity structural — part of the type system and the compiled binary — not bolted on through logging libraries or tracing tools.
// hello.tr
fn main() -> void {
println("Hello from Trace!");
let tracel x: tracel int = 42;
let tracel y: tracel int = x + 10;
why y; // prints: why y was 52, tracing back to x = 42
}
Compile and run:
./tracec hello.tr -o hello.ll
clang hello.ll runtime/trace_rt.c -o hello
./hello- GCC or Clang (C11) — to build the
trccompiler itself - Clang — to link generated
.lloutput into a native binary - Make (Linux/macOS) or PowerShell (Windows)
Platform note:
trcgenerates LLVM IR targetingx86_64Linux. The generated.llfiles can be compiled and run on Linux and Windows (via MinGW/Clang). Native Windows target support is planned for v0.2.
Linux / macOS:
cd trace
makeWindows (PowerShell):
.\build.ps1Both produce the trc (or trc.exe) compiler binary.
Linux / macOS:
make run-hello # compiles and runs examples/hello.tr
make run-demo # compiles and runs examples/causal_demo.tr
make run-calc # compiles and runs examples/calculator.trWindows:
.\build.ps1 run-hello
.\build.ps1 run-demo
.\build.ps1 run-calc./trc --lex examples/hello.tr # print all tokens
./trc --parse examples/hello.tr # parse and print AST
./trc --typecheck examples/hello.tr # type-check only| Type | Description | LLVM IR type |
|---|---|---|
int |
64-bit signed integer | i64 |
float |
64-bit IEEE 754 double | double |
bool |
Boolean (true / false) |
i1 |
string |
UTF-8 string | i8* |
void |
No value (functions only) | void |
tracel T |
Tracel wrapper around any type T |
{ T, %TracelNode* } |
StructName |
User-defined struct | %StructName |
let name: type = expression;
Variables are block-scoped and must be explicitly typed. Initialization is optional.
let x: int = 10;
let y: float = 3.14;
let flag: bool = true;
let greeting: string = "hello";
let unset: int; // declared but uninitialized
Reassignment uses =:
x = x + 1;
x += 5; // shorthand
x -= 2;
The core feature of Trace. A tracel value tracks its entire history — every assignment, function call, and computation that produced it.
let tracel score: tracel int = 0;
// equivalent:
let score: tracel int = 0;
The tracel keyword can appear before let or as part of the type. Both are equivalent.
Tracelity propagates automatically through tracel functions and operations:
fn compute(x: tracel int) -> tracel int {
return x * 2;
}
let tracel a: tracel int = 10;
let tracel b: tracel int = compute(a);
// b's chain: compute() called with a=10, a was 10 at line N
why x; // print the full tracel chain of x
why x == 0; // explain why x equals 0
why result != null; // explain why result is not null
why is a statement that prints the tracel chain to stdout at runtime. It is a no-op in release builds (when the chain is stripped).
Mark a block of code with a semantic label that gets embedded in the chain:
cause "reading from database" {
let tracel record: tracel int = db_fetch(user_id);
// record's chain includes: "reading from database"
}
Print a value and its current chain label inline, without interrupting execution:
let tracel result: tracel int = heavy_compute(input);
trace result;
// prints: [trace] 42 ← heavy_compute() at line 12
fn function_name(param: type, param2: type) -> return_type {
// body
return value;
}
A function marked (or whose signature uses) tracel types automatically participates in chain propagation:
tracel fn fetch_user(id: tracel int) -> tracel int {
// all values computed here are tagged with this function's identity
return id + 1;
}
Non-tracel functions can still receive and return tracel values — the chain is preserved.
fn add(a: int, b: int) -> int {
return a + b;
}
fn greet(name: string) -> void {
print("Hello, ");
println(name);
}
fn factorial(n: int) -> int {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}
if condition {
// then
} else if other_condition {
// else-if
} else {
// else
}
Condition must be bool. Parentheses are not required.
while condition {
// body
}
for item in collection {
// item is an int index for now; full iterator protocol coming in v0.2
}
Any { ... } creates a new scope:
{
let x: int = 5;
// x is only visible here
}
struct Point {
x: int,
y: int,
}
struct User {
id: tracel int, // tracel field — tracks where this ID came from
name: string,
score: tracel float,
}
Access fields with .:
let p: Point = Point { x: 10, y: 20 };
println(p.x);
Note: Struct literal initialization syntax is coming in v0.2. Currently structs are declared and their fields accessed via the
.operator.
| Function | Signature | Description |
|---|---|---|
print |
(any) -> void |
Print a value without newline |
println |
(any) -> void |
Print a value with newline |
read |
() -> string |
Read a line from stdin |
len |
(string) -> int |
Length of a string |
str |
(int) -> string |
Convert int to string |
int |
(string) -> int |
Parse string to int |
float |
(string) -> float |
Parse string to float |
| Keyword | Form | Description |
|---|---|---|
tracel |
type modifier | Marks a type or variable as tracelity-tracked |
why |
statement | Print tracel chain: why x; or why x == val; |
cause |
block annotation | Label a scope: cause "label" { ... } |
trace |
expression | Print value + chain inline: trace expr |
tracec [options] <source.tr>
Options:
-o <file> Output LLVM IR file (default: out.ll)
--lex Print all tokens and exit
--parse Parse only — report success or errors
--typecheck Type-check only — report errors
--help Show this help message
source.tr
│
▼ Lexer (lexer.c) → Token stream
│
▼ Parser (parser.c) → AST
│
▼ Type Checker (typechecker.c)→ Annotated AST
│
▼ Code Gen (codegen.c) → LLVM IR (.ll)
│
▼ clang/llc → Native binary
trace/
├── src/
│ ├── main.c — CLI driver: argument parsing, pipeline orchestration
│ ├── lexer.h/.c — Tokenizer: source text → token stream
│ ├── ast.h/.c — AST node definitions and memory management
│ ├── parser.h/.c — Recursive descent parser: tokens → AST
│ ├── typechecker.h/.c— Static type checker with scope chain
│ ├── codegen.h/.c — LLVM IR text emitter
│ └── tracel.h — TracelNode struct shared by compiler and runtime
├── runtime/
│ └── trace_rt.c — Runtime: tracel chains, why-printing, I/O built-ins
├── examples/
│ ├── hello.tr — Hello World
│ └── tracel_demo.tr — Full tracel features demo
├── Makefile
└── README.md
A tracel int compiles to:
%TracelNode = type { i8*, i32, i32, %TracelNode* }
%tracel_int = type { i64, %TracelNode* }Every assignment to a tracel variable:
- Stores the new value in field 0
- Calls
@trace_rt_node_newto allocate a new chain node with the source location - Stores the node pointer in field 1
why x; compiles to:
- GEP to extract the chain pointer from the tracel struct
- Call
@trace_rt_why(chain)which walks and prints the linked list
In release builds, tracel structs are replaced with their plain inner types and all chain operations are elided — zero overhead.
- Lexer with full token set
- Recursive descent parser
- Static type checker with scope chain
- LLVM IR code generator
- Tracel type (
tracel T) with chain tracking -
why,cause,tracekeywords - Runtime: TracelNode, why-printing, I/O built-ins
-
if/else,while,for-in,let,return - Built-in functions:
print,println,read,len
- Struct literal initialization syntax
- Arrays and slices
- String interpolation:
"hello {name}" -
tracelchains spanning function call boundaries with full stack capture -
why x == valdeep equality explanation - Release mode: strip all tracel chains (zero overhead)
- Error type with automatic tracel tagging
- Imports and multi-file compilation
- Generics / parametric types
- Pattern matching (
matchexpression) - Distributed tracel chains (chain serialization for RPC/message passing)
- REPL
- Self-hosted compiler (Trace written in Trace)
- Standard library
- Package manager
MIT — build freely, ship tracelly.
Trace — because "it crashed" is never the full story.