A tiny, statically-typed language that compiles to native code through LLVM — written from scratch, by hand, to learn how real compilers work.
IQ-- is a complete (if small) compiler: a hand-written lexer and recursive-descent / Pratt parser, a name resolver, a bidirectional type checker, and a backend that emits textual LLVM IR — which clang turns into a real native executable. The compiler core has zero third-party dependencies (pure C++23 standard library); the only dependency anywhere is Catch2 for the test suite.
It is an educational project — every stage is built to be read and understood, using the same techniques production compilers (Clang, rustc) use: 32-bit source offsets, table-driven character classification, string interning, LLVM-style RTTI on the AST, panic-mode error recovery with caret diagnostics, an arena allocator, canonical interned types, and bidirectional inference.
fn sum_sq(n: i32) -> i64 {
let acc: i64 = 0;
for i in 0..n { acc += (i as i64) * (i as i64); }
ret acc;
}
fn main() {
let xs = [1, 2, 3, 4];
let [first, ..rest, last] = xs; // destructuring with a bindable rest
let pair = (first, last); // tuples
print(pair.0 + pair.1); // 5
print(xs[^1]); // 4 (index from the end)
print(sum_sq(10)); // 285
}$ IQ--.exe --run program.iq
5
4
285 .iq source
│
▼
┌────────┐ ┌────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Lexer │──▶│ Parser │──▶│ Resolver │──▶│ Type checker │──▶│ CodeGen │
│ tokens │ │ AST │ │ scopes │ │ types + sema │ │ LLVM IR │
└────────┘ └────────┘ └──────────┘ └──────────────┘ └────┬─────┘
│ .ll
▼
clang ─▶ native .exe
Requires Visual Studio 2026 (MSVC v145, C++23).
clangships with Visual Studio, so no separate LLVM install is needed.
# 1. Build (Visual Studio: open IQ--.slnx and Build, or from a dev shell)
msbuild driver\IQ--.vcxproj /p:Configuration=Debug /p:Platform=x64
# 2. Compile & run an IQ-- program in one step
driver\x64\Debug\IQ--.exe --run program.iqEvery compiler stage is also inspectable on its own:
| Mode | What it does |
|---|---|
--dump-tokens |
print the token stream from the lexer |
--dump-ast |
print the AST as an indented tree |
--dump-ast=dot |
emit the AST as Graphviz DOT (… | dot -Tpng -o ast.png) |
--check |
parse + resolve + type-check, report errors only |
--emit-llvm [-o f.ll] |
emit LLVM IR (to stdout or a file) |
--run |
compile via clang and execute |
Types — i32 i64 u32 u64 f32 f64, bool, string, void, fixed arrays [T; N], and n-tuples (A, B, C, …).
Declarations & flow — fn (with a void default return), top-level const, let/const locals, if/else, while, for x in lo..hi (..= inclusive), ret, break, continue.
Expressions — arithmetic, comparisons, short-circuit && ||, unary - !, assignment and compound += -= *= /= %= (to names, array elements a[i] = x, and tuple fields t.0 = x), calls, array indexing a[i] and from-end a[^k], tuple field access t.0, as casts, array/tuple literals, and print.
Functions & aggregates — arrays and tuples can be passed to and returned from functions, so multiple return values are simply a returned tuple (fn divmod(a, b) -> (i32, i32)). Parallel assignment swaps without a temp: (a, b) = (b, a).
Compile-time guarantees — name resolution (undeclared / redefined / use-before-declaration), bidirectional type inference (untyped literals are pinned by context; no implicit numeric widening — use as), suffix-vs-annotation conflict detection, all-paths-return analysis (it knows a non-breaking while true diverges), const-expression array lengths, and mutability checks.
Every snippet below compiles and runs on IQ-- as it stands today.
| Recursion — Fibonacci | Euclid's GCD |
|---|---|
fn fib(n: i32) -> i64 {
if n < 2 { ret n as i64; }
ret fib(n - 1) + fib(n - 2);
}
// fib(10) -> 55 |
fn gcd(a: i32, b: i32) -> i32 {
let x = a; let y = b;
while y != 0 {
let t = y;
y = x % y;
x = t;
}
ret x;
}
// gcd(48, 36) -> 12 |
| Primality test | Binary search (read-only array) |
fn is_prime(n: i32) -> bool {
if n < 2 { ret false; }
let i = 2;
while i * i <= n {
if n % i == 0 { ret false; }
i += 1;
}
ret true;
}
// is_prime(17) -> true |
let xs = [1, 3, 5, 7, 9, 11];
let lo = 0; let hi = 5;
let found = -1;
while lo <= hi {
let mid = (lo + hi) / 2;
if xs[mid] == 7 { found = mid; break; }
if xs[mid] < 7 { lo = mid + 1; }
else { hi = mid - 1; }
}
// found -> 3 |
In-place bubble sort — fixed array, element assignment, swaps:
let xs = [5, 2, 8, 1, 9, 3];
let n = 6;
let i = 0;
while i < n {
let j = 0;
while j < n - 1 - i {
if xs[j] > xs[j + 1] {
(xs[j], xs[j + 1]) = (xs[j + 1], xs[j]); // swap, no temp
}
j += 1;
}
i += 1;
}
for k in 0..n { print(xs[k]); } // 1 2 3 5 8 9How far the language reaches today, problem by problem. The mainstream languages can of course do all of these — the point is to show IQ--'s current frontier.
| Problem / data structure | IQ-- | Rust | C++ | C# | Java | Python |
|---|---|---|---|---|---|---|
| Recursion (Fibonacci, factorial) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Euclid's GCD, fast exponentiation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Primality / sieve over a fixed array | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Sum / min / max / count over an array | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Linear & binary search (read-only) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Fixed-size tuple records (e.g. a point) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| In-place sorting (bubble / insertion) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Dynamic array / stack / queue | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Hash map / set | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Linked list / tree / graph | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| String processing (length, slicing) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
✅ supported · ❌ needs language features not built yet (heap, struct, generics).
| Feature | IQ-- | Rust | C++ | C# | Java | Python |
|---|---|---|---|---|---|---|
| Static type checking | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Local type inference | ✅ | ✅ | ✅ auto |
✅ var |
var |
n/a |
| Bidirectional literal inference | ✅ | ✅ | ❌ | ❌ | ❌ | n/a |
Sized integer types (i32/u64…) |
✅ | ✅ | ✅ | ✅ | ❌ bignum | |
| No implicit numeric widening | ✅ | ✅ | ❌ | ❌ | ❌ | n/a |
| Tuples (n-ary) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Multiple return values (via tuple) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
Pattern destructuring (let [a,..b,c]) |
✅ | ✅ | ✅ | |||
| Fixed-size arrays | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Dynamic collections / generics | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Compiles to native code | ✅ LLVM | ✅ | ✅ | ❌ |
Euclid's GCD — IQ-- sits comfortably in the C / Rust family:
| IQ-- | Rust | Python |
fn gcd(a: i32, b: i32)
-> i32 {
let (x, y) = (a, b);
while y != 0 {
let t = y;
y = x % y;
x = t;
}
ret x;
} |
fn gcd(a: i32, b: i32)
-> i32 {
let (mut x, mut y) =
(a, b);
while y != 0 {
let t = y;
y = x % y;
x = t;
}
x
} |
def gcd(a, b):
x, y = a, b
while y != 0:
x, y = y, x % y
return x |
Being honest about the frontier:
- No heap — no
Vec,Stringoperations, maps, or linked structures. - No
struct,match, generics, or modules — the pattern grammar and the tuple/field machinery are already in place to makestructandmatchthe next natural additions.
IQ--/
├── frontend/ IQFrontend.vcxproj — lexer, parser, AST, resolver, type checker
├── backend/ IQBackend.vcxproj — LLVM IR code generation
├── driver/ IQ--.vcxproj — the command-line compiler (IQ--.exe)
├── test/ IQTests.vcxproj — Catch2 suite (64 cases, 137 assertions)
└── iqrun.cs — optional .NET 10 helper (ProcessX / Zx)
Headers live under include/iq/…, implementations under src/…, mirroring Clang's layout.
msbuild test\IQTests.vcxproj /p:Configuration=Debug /p:Platform=x64
test\x64\Debug\IQTests.exe