A simple compiler for a custom programming language ("Mini") written in Zig. This project demonstrates the core stages of a compiler: Lexing, Parsing, Semantic Analysis, and Code Generation (LLVM IR).
- Data Types:
- Integers (
i64) - Floating Point Numbers (
f64) - Booleans (
true,false) - Strings (String literals)
- Arrays (Integer arrays)
- Structs (User-defined types)
- Integers (
- Variables:
letdeclarations and assignment. - Arithmetic:
+,-,*,/. - Logic:
&&,||,!,==,!=,<,<=,>,>=. - Control Flow:
if/elsestatements.whileloops.forloops.breakandcontinuestatements.
- Functions:
- Function declarations (
fn). - Function calls with arguments.
returnstatements.
- Function declarations (
- I/O: Built-in
print()function. - Error Reporting: Rich diagnostics with source location, underlining, and hints.
The compiler is structured into five main components:
- Lexer (
src/lexer.zig): Tokenizes the source code into a stream of tokens (keywords, identifiers, literals, operators). - Parser (
src/parser.zig): Consumes tokens to build an Abstract Syntax Tree (AST) representing the program structure. Implements operator precedence. - Semantic Analysis (
src/sema.zig): Traverses the AST to check for errors (undefined variables, type mismatches, scope resolution). - Code Generation (
src/llvm_codegen.zig): Traverses the AST to generate LLVM IR code. - Diagnostics (
src/diagnostics.zig): Handles error reporting with colored output and source context.
- Zig Compiler (tested with 0.15.0+)
- A C compiler (like
clangorgcc) for linking the runtime.
To build the compiler executable:
zig buildThe executable will be located at zig-out/bin/minic.
To compile and run a Mini program (must have .mini extension):
zig build run -- test.miniOr directly using the executable:
./zig-out/bin/minic test.miniTo run the project's unit and integration tests:
zig build testlet x: int = 10;
let pi: float = 3.14;
let s: string = "hello";
let b: bool = true;
if (x < 20) {
print(x);
} else {
print(0);
}
while (x > 0) {
x = x - 1;
if (x == 5) break;
}
for (let i = 0; i < 10; i = i + 1) {
if (i == 5) continue;
print(i);
}
let arr = [10, 20, 30];
print(arr[0]); // 10
arr[1] = 42;
print(arr[1]); // 42
struct Point {
x: int,
y: int
}
let p = Point { x: 10, y: 20 };
print(p.x);
fn add(a: int, b: int) -> int {
return a + b;
}
let result = add(5, 10);
print(result);
let y = -10;
let not_valid = !true;
The compiler provides helpful error messages with context:
error: Type mismatch in variable declaration. Expected .{ .int = void }, found .{ .string = void }
--> 1:1
|
1 | let x: int = "hello";
| ^^^
error: undefined variable 'z'
--> 3:7
|
3 | print(z);
| ^
- Memory Management: Uses Zig's
ArenaAllocatorfor efficient memory management during compilation. - Codegen: Generates a
.llLLVM IR file and aruntime.cfile (forprintf), then invokes the system C compiler (cc) to compile and link them into a final executable. - Scopes: Supports nested scopes for variables (block scoping).
MIT