Dev Log · 0.0.6
Records, address type, and a real wallet
2025-12-05 · DX
The core runtime finally understands something more interesting than raw ints and floats. This drop adds
nominal records, a first-class address type, and a tiny wallet
example that runs end-to-end through DX-IR.
- Runtime values grew a Record variant with a nominal type_name and field map.
- Everything implements Display, so print doesn’t just dump Debug noise anymore.
- address exists as a real type at runtime, with a small coercion from string when binding params / locals.
- A Wallet model plus new_wallet, credit, and debit functions serve as the canonical “safe state update” example.
- Three tiny test-style functions assert the behaviour of the wallet using require, and main just runs them.
The current reference sample looks like this:
model Wallet {
owner: address
balance: int
}
fn new_wallet(owner: address) -> Wallet {
let w: Wallet = Wallet {
owner: owner,
balance: 0,
};
require(w.balance == 0, "new wallet should start at 0");
return w;
}
fn credit(w: Wallet, amount: int) -> Wallet {
require(amount > 0, "amount must be positive");
let mut w2: Wallet = w;
let new_balance: int = w2.balance + amount;
w2 = Wallet {
owner: w2.owner,
balance: new_balance,
};
return w2;
}
fn debit(w: Wallet, amount: int) -> Wallet {
require(amount > 0, "amount must be positive");
require(w.balance >= amount, "insufficient funds");
let new_balance: int = w.balance - amount;
let w2: Wallet = Wallet {
owner: w.owner,
balance: new_balance,
};
return w2;
}
fn test_new_wallet_zero_balance() {
let w: Wallet = new_wallet("addr:test");
require(w.balance == 0, "new wallet should start at 0");
return;
}
fn test_credit_increases_balance() {
let w: Wallet = new_wallet("addr:test");
let w2: Wallet = credit(w, 10);
require(w2.balance == 10, "credit should increase balance by amount");
return;
}
fn test_debit_decreases_balance() {
let w: Wallet = new_wallet("addr:test");
let w2: Wallet = credit(w, 10);
let w3: Wallet = debit(w2, 3);
require(w3.balance == 7, "debit should decrease balance by amount");
return;
}
To run this from the repo root right now:
cargo run -- examples/wallet.dexa
The tour page now shows this program directly, so the website and the interpreter stop lying about what
the language can actually do.
Dev Log · 0.0.5
Loops, models/contracts, and real errors
2025-12-04 · DX
The core language finally behaves like something you can reason about over time: structured loops, typechecked
models/contracts, and error messages that don’t feel like a slap in the dark.
- while, loop, break, continue wired end-to-end (parser → typechecker → IR → interpreter).
- Model and contract bodies are now typechecked — bad field types are rejected.
- Parser and typechecker errors include line/column and a caret, e.g. error at 5:11: expected '{{' (found Colon).
- Negative tests like bad_parse.dexa, bad_type.dexa, and bad_model.dexa drive the shape of the diagnostics.
The canonical “does this even work?” sample right now is a tiny loop harness:
fn main() -> int {
let mut i: int = 0;
let mut acc: int = 0;
while i < 10 {
acc = acc + i;
i = i + 1;
}
require(acc == 45, "expected 45 from 0..9");
let mut j: int = 0;
let mut hits: int = 0;
loop {
if j == 3 {
break;
}
hits = hits + 1;
j = j + 1;
}
require(hits == 3, "loop/break broken");
return 0;
}
On top of this, models/contracts get their own typechecked bodies — you can’t sneak an
int into a field that declared as bool anymore
without the compiler yelling.
Dev Log · 0.0.4
Mutability, block scopes, and a real IR
2025-12-01 · DX
The compiler finally grew up a bit. Locals aren’t just nameless slots hanging off an AST anymore — they live in real
scopes, can be let or let mut, and lower into
a small IR the interpreter can actually execute.
- let mut support with type-checked assignments.
- Block-scoped locals with shadowing inside if blocks and nested blocks.
- Assignments lower to IR Store ops instead of hacky rewrites.
- Interpreter now runs against DX-IR, not the AST, which keeps future backends sane.
The following program runs end-to-end and demonstrates let mut, shadowing,
and the IR interpreter:
fn main() -> int {
let mut x: int = 0;
x = x + 10;
if x > 5 {
let mut x: int = 1;
x = x + 1;
}
return x;
}
This returns 10 from the outer x.
The inner x is a separate binding that lives and dies inside the
if block — which is exactly what you want before you start doing anything
serious with state.
From pretty syntax to a real typechecked core
2025-12-01 · DX
The first version of the DEXA site talked a big game about AI, smart contracts and deterministic compute.
Underneath, the compiler was basically a parser that printed ASTs. Cute, but useless.
The last few iterations turned DEXA into an actual language core:
- Structured AST for functions, blocks,
if / else, and expressions.
- A typechecker that understands
int, bool, float, string, and unit.
- Function signatures, argument typechecking, and return type validation.
let vs let mut with enforced immutability.
- Block scopes + shadowing (inner
x can hide outer x safely).
- IR and an interpreter that can actually execute simple programs and produce a value.
Example of what currently runs end-to-end:
fn max(a: int, b: int) -> int {
if a > b {
return a;
} else {
return b;
}
}
fn main() -> int {
let mut x: int = 0;
x = max(1 + 2, 3 * 4); // 12
return x;
}
The CLI is still intentionally minimal:
cargo run -- ../examples/hello.dexa
Next steps: extend the type system toward tensors and asset types, and start mapping this IR to a verifiable VM
instead of just an in-process interpreter.
More devlogs will land as the compiler grows (IR passes, DX-VM, GPU backend, tensors, contracts).