kz80_ml
A Tiny ML compiler for the Z80 processor with Hindley-Milner type inference.
Features
- ML-like syntax with let bindings, pattern matching, and recursion
- Hindley-Milner type inference for automatic type checking
- Two numeric types: 16-bit integers and 4-byte packed BCD decimals (2 decimal places)
- Auto-coercion from int to decimal in mixed arithmetic
- Pattern matching on integers with wildcard support
- Recursive functions for algorithms like factorial and fibonacci
- Generates native Z80 machine code that runs directly on the CPU
Building
Usage
Options:
-o <file>: Output binary file (default:a.out)-: Read source from stdin
Language Reference
Types
int- 16-bit signed integerdecimal- Fixed-point decimal with 2 decimal places (stored as 4-byte BCD)bool- Boolean (true/false)
Literals
42 (* integer *)
3.14 (* decimal *)
true (* boolean *)
false
Operators
Arithmetic: +, -, *, /, mod
Comparison: ==, !=, <, <=, >, >=
Logical: not
Let Bindings
let x = 42
if n <= 1 then 1 else n * factorial
Functions
Functions are curried:
a + b
let main = print_int (* prints 7 *)
Conditionals
if condition then expr1 else expr2
Pattern Matching
match n 1
1
fib + fib
Built-in Functions
print_int e- Print an integerprint_decimal e- Print a decimal (displayed as integer, e.g., 3.14 shows as 314)
Examples
Factorial
if n <= 1 then 1
else n * factorial
let main = print_int (* prints 720 *)
Fibonacci
match n 0
1
fib + fib
let main = print_int (* prints 55 *)
Mixed Decimal Arithmetic
let price = 19.99
let quantity = 5
(* quantity (int) is auto-coerced to decimal *)
let subtotal = price * quantity
let main = print_decimal subtotal (* prints 9995, meaning 99.95 *)
Collatz Conjecture
match n 0
if n / 2 * 2 == n
then 1 + collatz
else 1 + collatz
let main = print_int (* prints 111 *)
Memory Layout
- ROM: 0x0000 - 0x1FFF (8KB)
- RAM: 0x2000 - 0x37FF (6KB)
- Stack: 0x37FF (grows downward)
- I/O: 0x01 (character output)
Target Platform
Designed for the RetroShield Z80, but should work on any Z80 system with the above memory layout and I/O port 0x01 for character output.
Testing
The project includes a comprehensive test suite with three levels of testing:
Rust Unit Tests
Unit tests for the lexer, parser, type inference, and code generator:
This runs 36 unit tests covering tokenization, parsing, type checking, and REPL code generation.
Rust Integration Tests
End-to-end tests that generate REPL binaries and run them through the Z80 emulator:
These 21 tests verify the REPL works correctly for arithmetic, variables, functions, conditionals, and more. Requires the emulator at ../emulator/retroshield.
ML Test Suite
Test programs written in the ML language itself:
This runs 29 ML programs through the batch compiler and emulator, covering:
| Category | Tests |
|---|---|
| Arithmetic | +, -, *, /, %, negation |
| Precedence | operator precedence, parentheses |
| Comparisons | <, >, <=, >=, == |
| Control Flow | nested if-then-else |
| Let Bindings | simple, multiple, let-in |
| Functions | single/multi-arg, composition |
| Recursion | factorial, fibonacci |
Each test file contains an expected result comment that the runner verifies:
(* Test: Addition *)
(* Expected: 8 *)
let main = print_int
REPL vs Batch Compiler Features
The on-target REPL supports additional features not available in the batch compiler:
| Feature | Batch Compiler | REPL |
|---|---|---|
| Arithmetic | Yes | Yes |
| Comparisons | Yes | Yes |
| Functions | Yes | Yes |
| Recursion | Yes | Yes |
Bitwise (&, ` |
, ^`) |
No |
Shifts (<<, >>) |
No | Yes |
Logical (and, or, not) |
No | Yes |
Hex literals (0xFF) |
No | Yes |
Not equal (!=) |
No | Yes |
License
BSD-3-Clause