27 programs, 3 languages, 1 Z80 backend. All compile. All verified.
This directory contains showcase programs that demonstrate what MinZ can do on vintage Z80 hardware. Each program compiles to tight, hand-quality Z80 assembly through the MIR2 pipeline with PBQP register allocation and Z3-PFCCO calling convention optimization.
Compilation: mz fun/<file> -o build/<name>.a80
adt_option.nanz -- 5 asserts, all pass
enum Option { None, Some(u8) }
fun unwrap_or(opt: Option, def: u8) -> u8 {
return match opt {
Some(val) => val,
None => def,
}
}
fun safe_div(a: u8, b: u8) -> Option {
if b == 0 { return None }
return Some(a / b)
}
assert unwrap_or(safe_div(10, 3), 255) == 3
The compiler constant-folds through the entire ADT. safe_div(10,3) evaluates at compile time through Some(3) into unwrap_or(Some(3), 255) and out to 3. The Z80 output:
test_safe_div_ok: LD A, 3 / RET ; the entire safe_div + match + unwrap chain
test_unwrap_some: LD A, 42 / RET ; unwrap_or(Some(42), 0)
test_is_none: LD A, 0 / RET ; is_some(None)
test_unwrap_none: LD A, 77 / RET ; unwrap_or(None, 77)
124 optimization passes. 4 pruned lambdas. Zero-cost ADTs on an 8-bit CPU from 1976.
iterator_fusion.nanz
fun filter_map_explicit(buf: u16, n: u8, threshold: u8) -> void {
for x: u8 in buf[0..n] {
let doubled: u8 = x * 2
if doubled > threshold {
process(doubled)
}
}
}
Three operations -- load, double, filter -- fused into one tight loop with conditional CALL:
filter_map_explicit:
LD B, C ; loop counter = n
LD C, D ; threshold
.fe_head:
LD A, B / AND A / RET Z ; done?
.fe_body:
LD D, (HL) ; load element
LD A, D / ADD A, A ; x * 2 (map)
LD D, A
LD A, C / CP D ; threshold vs doubled (filter)
CALL C, process ; conditional CALL -- fires only when filter passes
.fe_cont:
INC HL ; advance pointer
...DEC B / JRS .fe_head ; next element
CALL C, process is a single Z80 instruction that replaces the usual JR NC, .skip / CALL process / .skip: pattern. MinZ detects the BrIf-over-single-call pattern at MIR2 level and emits a conditional CALL, saving 2 bytes and eliminating the branch.
pipes.frl (Frill -- ML-style functional language)
let double (x : u8) : u8 = x + x
let inc (x : u8) : u8 = x + 1
let pipe_dbl_inc (x : u8) : u8 = x |> double |> inc
The pipe chain x |> double |> inc compiles to:
pipe_dbl_inc:
ADD A, A ; double
INC A ; inc
RET
Both functions inlined. Zero-cost functional composition on Z80. Three instructions, zero overhead.
oop_shapes.nanz -- traits + impl blocks
struct Rect { kind: u8, w: u8, h: u8 }
trait Shape { area, perimeter }
impl Shape for Rect {
fun area(self) -> u8 { return self.w * self.h }
fun perimeter(self) -> u8 { return (self.w + self.h) * 2 }
}
No vtables, no dynamic dispatch. rect.area() is a direct CALL Rect_area:
Rect_area:
EX DE, HL ; save self ptr
LD BC, 2 / ADD HL, BC ; &self.w
LD B, (HL) ; w
LD H, D / LD L, E
LD BC, 3 / ADD HL, BC ; &self.h
LD E, (HL) ; h
LD A, B / LD B, E
JP __mul8 ; tail call: w * h, return in A
Rect_perimeter:
...
LD A, (HL) / RLA ; w * 2 (rotate left = shift left 1)
...
LD A, (HL) / RLA ; h * 2
ADD A, C ; w*2 + h*2
RET
JP __mul8 instead of CALL __mul8 / RET -- tail call optimization saves 17 T-states. RLA for *2 -- the compiler chooses the single-byte rotate.
tail_recursion.nanz -- asserts verified
fun fib_tail(n: u8, a: u8, b: u8) -> u8 {
if n == 0 { return a }
return fib_tail(n - 1, b, a + b)
}
assert fib_tail(10, 0, 1) == 55
Grace tail-recursion elimination converts the recursive call to a loop:
fib_tail:
AND A / RET Z ; n == 0? return a (in B)
DEC A ; n - 1
LD A, B / ADD A, C ; a + b
LD B, A ; new accumulator
...JRS fib_tail ; jump, not CALL -- O(1) stack
No CALL, no stack growth. Runs to n=255 on 256 bytes of stack.
widemath.nanz -- 26 functions, 31 asserts, all pass
fun *(a: u8, b: u8) -> u16 { ... } // widening multiply
assert sat_add(200, 100) == 255 // saturates at u8 max
assert abs_diff(200, 50) == 150 // |a - b|
assert brightness_blend(100, 200, 128) == 150 // linear interpolation
Scalar operator overloading: a * b transparently dispatches to widening multiply when types match. Saturating arithmetic, absolute difference, pixel blending -- all verified against MIR2 VM.
raymarcher.nanz -- 2816 lines of Z80 assembly output
struct Vec3 { x: i16, y: i16, z: i16 }
fun scene(p: Vec3) -> i16 {
let sphere: i16 = sdf_sphere(p, 150)
let box: i16 = sdf_box(p, Vec3{x: 100, y: 100, z: 100})
return sdf_subtract(sphere, box) // CSG: sphere minus box
}
Fixed-point 8.8 arithmetic, SDF primitives, CSG operations, normal estimation via central differences. Renders a sphere-minus-box scene on ZX Spectrum. The most complex MinZ program.
bit_intent.nanz
fun set_ptr_bit() -> u8 {
var p: ptr = &flags_g
p^.4 = 1 // set bit 4 via pointer
return p^.4 // read bit 4 back
}
x.N syntax for bit access. Compiles to Z80 native SET 4, (HL) / BIT 4, (HL).
tuple_return.nanz / triple_return_skip.nanz
fun minmax(a: u16, b: u16) -> (u16, u16) {
if a <= b { return (a, b) }
return (b, a)
}
let (lo, hi) = minmax(x, y)
let (first, _, last) = stats3(x, y, z) // blank identifier skips middle value
Multiple return values via register pairs. Blank _ identifier skips unused values at zero cost.
che_cascade.nanz / che_intro.nanz / che_nanz.nanz
fun xor_pixel(x: u8, y: u8) -> void {
let addr: u16 = 0x4000 + y7*2048 + y2_0*256 + y5_3*32 + xbyte
let p: ^u8 = addr
p^ = p^ xor mask // flip pixel via pointer XOR
}
Che Guevara portrait rendered by 64 LFSR-16 layers XOR'ing random pixels. Each layer uses a different seed from a precomputed table. Self-modifying code patches LFSR state into instruction immediates (TSMC tunnels).
sha256.nanz -- 15 functions, 271 lines of asm
32-bit arithmetic via u16 pairs (DEHL convention). GPU-proven optimal shift sequences. Implements SHA-256 message schedule and compression on 3.5 MHz hardware.
frill_graphics.frl -- ML-style functional
let sierpinski (x : u8) (y : u8) = if (x & y) == 0 then 1 else 0
let xor_texture (x : u8) (y : u8) = x ^ y
let checker (x : u8) (y : u8) = ((x / 8) + (y / 8)) & 1
Pattern generators for ZX Spectrum: Sierpinski triangles, XOR textures, checkerboards, diamonds, rings. Each is a pure function from (x, y) to pixel value.
| File | Description |
|---|---|
anim_player.nanz |
LFSR-16 AND-cascade renderer playing frame-based animation from binary data |
che_intro.nanz |
Enhanced Che intro with runtime layer table at 0xC000 |
replay.nanz |
Seed-table-driven LFSR cascade renderer with 768-byte block buffer |
All 27 programs compile successfully. Programs with assert statements are verified via MIR2 VM.
| File | Lang | Funcs | ASM Lines | Asserts | Status |
|---|---|---|---|---|---|
| adt_option.nanz | Nanz | 8 | 131 | 5/5 | ✅ |
| iterator_fusion.nanz | Nanz | 3 | 81 | -- | ✅ |
| oop_shapes.nanz | Nanz | 8 | 213 | -- | ✅ |
| tail_recursion.nanz | Nanz | 8 | 165 | ✓ | ✅ |
| raymarcher.nanz | Nanz | ~40 | 2816 | -- | ✅ |
| sha256.nanz | Nanz | 15 | 271 | -- | ✅ |
| widemath.nanz | Nanz | 26 | 566 | 31/31 | ✅ |
| vectors.nanz | Nanz | 16 | 836 | -- | ✅ |
| che_cascade.nanz | Nanz | 12 | 554 | -- | ✅ |
| che_intro.nanz | Nanz | 4 | 372 | -- | ✅ |
| che_nanz.nanz | Nanz | 5 | 387 | -- | ✅ |
| lfsr_decoder.nanz | Nanz | 12 | 334 | -- | ✅ |
| state_machine.nanz | Nanz | 3 | 115 | -- | ✅ |
| bit_intent.nanz | Nanz | 5 | 92 | -- | ✅ |
| pointer_threading.nanz | Nanz | 2 | 87 | -- | ✅ |
| tuple_return.nanz | Nanz | 4 | 89 | ✓ | ✅ |
| triple_return_skip.nanz | Nanz | 4 | 42 | -- | ✅ |
| addr_field_basic.nanz | Nanz | 2 | 38 | -- | ✅ |
| addr_field_method.nanz | Nanz | 3 | 44 | -- | ✅ |
| addr_field_aos_walk.nanz | Nanz | 2 | 56 | -- | ✅ |
| addr_index_basic.nanz | Nanz | 2 | 34 | -- | ✅ |
| pipes.frl | Frill | 10 | 108 | -- | ✅ |
| frill_showcase.frl | Frill | 39 | 634 | -- | ✅ |
| frill_graphics.frl | Frill | ~20 | 431 | -- | ✅ |
| fun/anim_player.nanz | Nanz | 9 | ~200 | -- | ✅ |
| fun/che_intro.nanz | Nanz | 8 | ~300 | -- | ✅ |
| fun/replay.nanz | Nanz | 6 | ~200 | -- | ✅ |
MinZ: Modern programming abstractions with zero-cost performance on vintage Z80 hardware.