MiniLang is a tiny, dynamically typed, C-style language that drives a full pipeline:
lexer -> parser -> HIR -> MIR -> execution hosts. The solution contains an interpreter,
a register-VM backend, a custom VM runtime with GC,
shared host/tooling infrastructure in Compiler.Tooling, and an isolated
Experimental/Typing area for incomplete type-system work. Program files use the
extension .minl.
- Solution:
Compiler.sln - Projects:
Compiler.Frontend,Compiler.Frontend.Translation,Compiler.Backend.JIT.Abstractions,Compiler.Backend.VM,Compiler.Runtime.VM,Compiler.Interpreter,Compiler.Tooling,Compiler.Benchmarks,Compiler.Tests - Shared solution settings:
Directory.Build.props,Directory.Packages.props
The repository currently targets .NET 10 (net10.0).
- Build:
dotnet build Compiler.sln -c Debug - Test:
dotnet test Compiler.sln - Format:
dotnet format
The executable hosts use System.CommandLine and Microsoft.Extensions.Hosting.
- Interpreter:
dotnet run --project Compiler.Interpreter -- run --file Compiler.Tests/Tasks/factorial_calculation.minl - VM backend:
dotnet run --project Compiler.Backend.VM -- run --file Compiler.Tests/Tasks/factorial_calculation.minl
-h|--helpshow help-f|--file <path>path to.minlsource file-v|--verboseverbose logs (parse, MIR dump, return, timing)--quietsuppress program stdout (builtins likeprint)--timeprint total execution time (ms)
--gc-threshold=Ninitial VM heap collection threshold (objects)--gc-growth=Xthreshold growth factor (e.g., 1.5)--gc-auto=on|offenable/disable opportunistic collections--gc-statsprint VM GC statistics after execution
- The default backend compiles MIR -> register bytecode and executes it on the VM.
- The interpreter is a separate execution host used alongside the VM in regression and parity tests.
- Run benchmark harness:
dotnet run --project Compiler.Benchmarks -c Release - Covered workloads: factorial, array sorting, prime number generation
Example GC stats output
[gc] mode=vm auto=on threshold=64 growth=1.5
[gc] allocations=128 collections=3 live=10 peak_live=70
| Token | Example(s) | Notes |
|---|---|---|
| Identifiers | foo, bar42 |
ASCII letters / digits / _, must not start with a digit |
| Integer lit. | 0, -42, 123456 |
64-bit signed (long) |
| Char lit. | 'a', '\n', '\\' |
Simple C-style escapes |
| String lit. | "hi", "\"x\"" |
Back-slash escapes \" and \\ |
| Keywords | fn, var, if, else, while, for, break, continue, return, true, false |
|
| Operators | `+ - * / % < <= > >= == != && | |
| Punctuation | , ; ( ) { } [ ] |
|
| Comments | // to end of line |
Discarded by the lexer |
| Tag | Literal(s) | Description |
|---|---|---|
long |
42 |
64-bit signed integer |
bool |
true |
Logical value |
char |
'x' |
16-bit Unicode scalar |
string |
"foo" |
Immutable UTF-16 sequence |
array |
— | Mutable, zero-indexed VM array, elements default to null |
null |
— | Absence of a value |
postfixExpr calls & indexing (left)
unary + - ! (right)
multiplication * / % (left)
addition + - (left)
comparison < <= > >= (left)
equality == != (left)
logicalAnd && (left, short-circuit)
logicalOr || (left, short-circuit)
assignment = (right) value of expr is RHS
Truthiness (used by if, while, …)
bool→ itselflong→ non‑zerostring→ non‑emptyarray→ non‑emptynull→ false
var i = 0; // variableDecl
if (cond) stmt // ifStmt (+ optional else)
while (cond) stmt // whileStmt
for (init; cond; iter) stmt
break; continue; // loop control
return expr?; // returnStmt
expr; // exprStmt (may be empty)
{ ... } // block
All variables are function-local; there are no globals.
fn gcd(a, b) {
while (b != 0) {
var t = b;
b = a % b;
a = t;
}
return a;
}
- No overloading; a single global namespace of functions.
- If execution “falls off” the end, the function yields
null. - Exactly one entry function
main()with zero parameters is required.
var a = array(5); // 5 nulls
a[0] = 123;
print(a[0]); // 123
Indices are bounds‑checked; out‑of‑range access raises a runtime error.
| Name | Arity | Behaviour |
|---|---|---|
array(n) |
1 | Fresh array of length n (all elements null). |
array(n, init) |
2 | Fills array with init. |
print(x, …) |
varargs | Writes space‑separated values and a newline (to stdout) |
len(x) |
1 | Length of string or array (returns long) |
ord(c) |
1 | Code point of char or 1‑length string (returns long) |
chr(i) |
1 | char for integer code point (range‑checked) |
assert(cond, msg?) |
1–2 | Throws on false; optional message |
clock_ms() |
0 | Monotonic timestamp in milliseconds as long |
fn fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
}
fn main() {
print(fact(10)); // 3628800
}
fn qsort(arr, lo, hi) {
if (lo >= hi) return;
var p = arr[(lo + hi) / 2];
var i = lo;
var j = hi;
while (i <= j) {
while (arr[i] < p) i = i + 1;
while (arr[j] > p) j = j - 1;
if (i <= j) {
var t = arr[i]; arr[i] = arr[j]; arr[j] = t;
i = i + 1; j = j - 1;
}
}
qsort(arr, lo, j);
qsort(arr, i, hi);
}
fn main() {
var a = array(10);
var k = 0;
while (k < 10) { a[k] = 9 - k; k = k + 1; }
qsort(a, 0, 9);
k = 0; while (k < 10) { print(a[k]); k = k + 1; }
}
fn sieve(limit) {
var isPrime = array(limit + 1);
var i = 2;
while (i <= limit) { isPrime[i] = true; i = i + 1; }
var p = 2;
while (p * p <= limit) {
if (isPrime[p]) {
var j = p * p;
while (j <= limit) { isPrime[j] = false; j = j + p; }
}
p = p + 1;
}
i = 2; while (i <= limit) { if (isPrime[i]) print(i); i = i + 1; }
}
fn main() { sieve(100); }