| Crates.io | kz80_c |
| lib.rs | kz80_c |
| version | 0.1.1 |
| created_at | 2025-12-12 04:20:30.193909+00 |
| updated_at | 2025-12-12 14:23:25.062259+00 |
| description | A self-hosting C compiler for Z80 targeting RetroShield hardware |
| homepage | https://github.com/ajokela/kz80_c |
| repository | https://github.com/ajokela/kz80_c |
| max_upload_size | |
| id | 1980966 |
| size | 359,297 |
A C compiler targeting the Z80 processor, written in Rust. Designed for the RetroShield Z80 platform and now self-hosting - the compiler can compile itself and run on the target hardware.
char (8-bit), int (16-bit), float (BCD), pointers, arrays, structs3.14, 1e5, 2.5e-3 parsed to 6-byte BCD formatif/else, while, for, return+ - * / % & | ^ << >> == != < > <= >= && ||#define and #includeputchar(c) - Output character to serialgetchar() - Read character from serialputs(str) - Output null-terminated stringbcd_from_int(ptr, value) - Convert 16-bit int to 4-byte BCDbcd_to_int(ptr) - Convert BCD back to intbcd_add(result, a, b) - Add two BCD numbers with DAAbcd_sub(result, a, b) - Subtract BCD numbersbcd_cmp(a, b) - Compare BCD numbers (-1, 0, 1)bcd_print(ptr) - Print BCD number6-byte format: [sign][exponent][4-byte mantissa]
bcdf_from_int(ptr, value) - Convert int to BCD floatbcdf_copy(dest, src) - Copy a BCD float (6 bytes)bcdf_add(result, a, b) - Add BCD floatsbcdf_sub(result, a, b) - Subtract BCD floatsbcdf_mul(result, a, b) - Multiply BCD floats (repeated addition with DAA)bcdf_div(result, a, b) - Divide BCD floatsbcdf_cmp(a, b) - Compare BCD floats (returns 1, 0, or -1)bcdf_neg(ptr) - Negate BCD float in place (flip sign bit)bcdf_abs(ptr) - Absolute value in place (clear sign bit)bcdf_normalize(ptr) - Normalize mantissa (stub - not yet implemented)bcdf_print(ptr) - Print BCD float with exponent# Build the compiler
cargo build --release
# Compile a C program
./target/release/kz80_c input.c -o output.bin
# With preprocessor defines and includes
./target/release/kz80_c -DMAX=100 -I./include input.c -o output.bin
# Show preprocessed output
./target/release/kz80_c -E input.c
# Show tokens (lexer output)
./target/release/kz80_c --tokens input.c
# Show AST (parser output)
./target/release/kz80_c --ast input.c
# Show hex dump of generated code
./target/release/kz80_c -S input.c
# Run all tests (unit + integration)
cargo test
# Run unit tests only (lexer, parser, preprocessor)
cargo test --bin kz80_c
# Run integration tests only (compile & run C programs)
cargo test --test integration_tests
# Run shell-based test suite (tests both Rust and self-hosted compilers)
./run_tests.sh
# Run with emulator
../emulator/retroshield output.bin
See the examples/ directory:
hello_world.c - Basic serial outputfibonacci.c - Recursive Fibonacci sequenceprint_num.c - Integer printingbcdf_test.c - BCD float operationsThe self/cc.c file contains a fully functional C compiler written in the C subset itself. It runs on the Z80 and can compile C programs from stdin to binary output on stdout.
# Compile the self-hosting compiler with the Rust compiler
./target/release/kz80_c self/cc.c -o self/cc.bin
# Use the self-hosted compiler to compile a program
# (reads C source from stdin, outputs binary to stdout)
printf 'int main() { puts("Hello from self-hosted compiler!"); return 0; }\x00' | \
../emulator/retroshield self/cc.bin > hello.bin
# Run the compiled program
../emulator/retroshield hello.bin
putchar, getchar, puts, print_numint (16-bit), char (8-bit), pointers, arrays&) and dereference (*) operators+ - * / %), bitwise (& | ^ ~), shifts (<< >>), comparison (< > <= >= == !=), logical (&& ||)if/else, while, for, return, break, continue#define and #include lines (use Rust compiler for preprocessing)# Compile and run a simple program
$ printf 'int main() { int x; x = 3 + 4; putchar(48 + x); putchar(10); return 0; }\x00' | \
../emulator/retroshield self/cc.bin > test.bin
$ ../emulator/retroshield test.bin
7
The BCD float format follows the TI-85 calculator style:
Byte 0: Sign/flags (bit 7 = negative)
Byte 1: Exponent (offset 0x80 = 10^0, so 0x84 = 10^4)
Bytes 2-5: Mantissa (4 bytes = 8 BCD digits, packed 2 per byte)
Example: 42 stored as BCD float:
0x00 (positive)0x84 (10^4)0x00 0x00 0x00 0x42The Z80's DAA (Decimal Adjust Accumulator) instruction is used for BCD arithmetic, providing exact decimal representation like TI calculators.
Source Code (.c)
│
▼
Lexer (token.rs, lexer.rs)
│
▼
Preprocessor (preprocess.rs)
│
▼
Parser (parser.rs)
│
▼
AST (ast.rs)
│
▼
Code Generator (codegen.rs)
│
▼
Z80 Binary (.bin)
#ifdef)#define lines are skipped, not processed)#define, use the Rust compiler to preprocess first&arr[0]x = a[0]; y = a[1]; z = x + y; instead of z = a[0] + a[1];)The self-hosted compiler can compile and run C programs with all language features (pointers, arrays, globals, shifts, recursion). Full self-compilation is size-constrained: the compiler source (~66KB) exceeds the 8KB input buffer. This is a fundamental Z80 memory limitation, not a compiler bug.
Verified working:
+ - * / %)& | ^ << >>)< > <= >= == !=)&& ||)*p) and write (*p = value) for both int* and char*if/else, while, for, break, continue)BSD 3-Clause License. See LICENSE for details.
Inspired by: