Performance of compiled languages. Simplicity of Python. Memory safety inspired by Rust.
A minimalist, readable, and performant programming language for the modern age.
let fibonacci = (n int) int {
if n <= 1 { return n }
return fibonacci(n - 1) + fibonacci(n - 2)
}
for i in range(10) {
show(to_string(fibonacci(i)))
}# Build a native binary
monk build hello.monk
# Compile and run in one step
monk run hello.monk
# Validate without compiling
monk check hello.monk
# Emit C source instead of binary
monk build hello.monk -o hello.cRequires Go 1.26.1+ and a C compiler (cc/gcc/clang).
git clone https://github.com/monkfromearth/monk-lang.git
cd monk-lang
make install # builds and installs to ~/.local/bin/monklet name = "Monk" // mutable
const pi = 3.14159 // immutable (deep freeze)int (64-bit) · float (64-bit) · string (UTF-8) · boolean · none · array · record
let age = 25 // int
let temp = 36.6 // float
let greeting = "hello" // string
let active = true // boolean
let numbers = [1, 2, 3] // array (homogeneous)
let point = {x: 10, y: 20} // record (fixed shape)Functions are expressions with typed parameters and return types.
let add = (a int, b int) int {
return a + b
}
let greet = (name string) none {
show("Hello, " + name + "!")
}if x > 0 {
show("positive")
} else if x == 0 {
show("zero")
} else {
show("negative")
}
for item in collection {
show(to_string(item))
}
while condition {
// ...
}No try/catch. Monk uses guard/against/throw:
let divide = (a int, b int) int {
if b == 0 { throw "division by zero" }
return a / b
}
guard result = divide(10, 0) against error {
show("Caught: " + to_string(error))
result = 0
}Split programs across files with use and export:
// math.monk
let add = (a int, b int) int { return a + b }
export add
// main.monk
use add from "./math"
show(to_string(add(3, 4)))All four import forms:
use add from "./math" // single import
use { add, mul } from "./math" // destructured
use * from "./math" // wildcard
use add as plus from "./math" // aliasModules compile to a single .c file. Module-level code runs once. Circular imports are a compile error.
Monk has a static type checker. Annotations are optional — the checker infers from first assignment.
let x = 42 // inferred: int
let name = "monk" // inferred: string
let scores int[] = [1, 2, 3] // typed array — backed by int64_t* in C
let maybe int? = none // optional type — int or noneCaught at compile time:
- Type drift on reassignment (
x = "hi"whenxisint→ error) - Cross-type equality (
5 == "5"→ error) - Missing record fields, wrong field types
- Missing return paths in non-
nonefunctions - Arity and argument type mismatches
- Loop variable mutation (
for i in arr { i = 0 }→ error)
Typed arrays compile to raw C types. int[] uses int64_t* backing, not MonkValue*. Element reads/writes are direct pointer access. Scalar variables (int, float, bool) emit as int64_t, double, bool — no union overhead in arithmetic.
Assignment copies. Function args copy. Your data is yours.
let original = [3, 1, 2]
let sorted = bubble_sort(original)
// original is still [3, 1, 2]The examples/ directory has 24 working programs:
| File | What it shows |
|---|---|
hello.monk |
Hello world |
fibonacci.monk |
Recursion, typed functions |
fizzbuzz.monk |
Conditionals, for loops, modulo |
arrays.monk |
Array operations, iteration, append |
error_handling.monk |
guard/against/throw |
newton_sqrt.monk |
Float arithmetic, recursion, error boundaries |
todo_list.monk |
Records, value semantics, data modeling |
collatz.monk |
while loops, arrays, the Collatz conjecture |
sort.monk |
Bubble sort, value semantics proof |
types.monk |
Type annotations, inference, typed arrays, optionals |
records.monk |
Nested records with structural typing |
optionals.monk |
T? semantics, is_none, graceful reads |
guards.monk |
guard/against/throw scoping |
closures.monk |
First-class functions, capture-by-copy |
higher_order.monk |
map, filter, reduce with typed arrays |
default_params.monk |
Default parameter values |
value_semantics.monk |
Assignment copies, function args copy |
strings.monk |
String operations |
math.monk |
Math builtins |
binary_search.monk |
Binary search with typed arrays |
bitwise.monk |
Bitwise operators |
gcd_lcm.monk |
GCD/LCM, number theory |
sieve.monk |
Sieve of Eratosthenes, typed arrays |
modules/ |
Multi-file imports, exports, modules |
monk <command> [arguments]
Commands:
build <file.monk> [-o output] Compile to native binary
run <file.monk> Compile and run
check <file.monk> Parse and validate (no compilation)
version Print version
help Show help
The -o flag controls output format:
| Command | Output |
|---|---|
monk build hello.monk |
hello (native binary) |
monk build hello.monk -o app |
app (native binary) |
monk build hello.monk -o hello.c |
hello.c (C source, no compilation) |
Monk is a compiler, not an interpreter. There is no REPL.
source.monk --> [Go compiler] --> generated.c --> [cc -O3 -flto] --> native binary
|
Compiler pipeline
|
C runtime (~1,200 lines across 8 files)
|
| Category | Functions |
|---|---|
| Output | show |
| Conversion | to_string to_int to_float |
| Type check | typeof is_array is_record is_string is_int is_float |
| String | length substring split join trim to_upper_case to_lower_case starts_with ends_with contains replace index_of char_at |
| Array | append pop slice range length |
| Math | sqrt pow abs floor ceil round sin cos tan log min max |
| File I/O | file_read file_write file_exists |
Benchmarks on Apple M4 Pro (cc -O3 -flto, hyperfine, lower is better). 21 benchmarks total.
At or below C (11/21):
| Benchmark | Monk | vs C |
|---|---|---|
| fibonacci (n=35) | 17.1 ms | 0.9× |
| mandelbrot (800²×50) | 15.3 ms | 1.0× |
| leibniz (π, 50M iter) | 28.4 ms | 1.0× |
| trial_primes (<200k) | 8.7 ms | 1.0× |
| collatz | 93.6 ms | 1.0× |
| ackermann | 459 ms | 1.0× |
| bitcount | 60.6 ms | 0.9× |
| sqrt_sum | 9.2 ms | 1.0× |
| quicksort | 2.4 ms | 0.9× |
| fannkuch | 122 ms | 0.8× |
| record_access | 4.2 ms | 1.4× |
Near C — typed array overhead (3/21):
| Benchmark | Monk | vs C | Root cause |
|---|---|---|---|
| nbody (float[], 500k steps) | 17.9 ms | 1.5× | Extra pointer indirection: MonkValue → MonkFloatArray → data |
| matmul (400² int[]) | 19.8 ms | 1.6× | Same + no restrict — compiler can't prove non-aliasing |
| sieve (int[], 1M elements) | 6.3 ms | 2.0× | int64_t (8 bytes) vs C's char (1 byte): 8× memory bandwidth |
Structural gaps — semantic overhead (7/21):
| Benchmark | Monk | vs C | Root cause |
|---|---|---|---|
| functional_chain | 10.5 ms | 3.8× | map/filter/reduce allocate intermediate arrays |
| string_concat | 39.2 ms | 6.9× | Immutable strings; concat is O(n²) |
| closure_invoke | 21.5 ms | 11.9× | Heap-allocated closure struct per iteration; C uses a direct call |
| for_in_sum | 26.0 ms | 15.3× | range(10M) allocates 80 MB; C reference uses a formula (no allocation) |
| levenshtein | 68.0 ms | 26.2× | substring() allocates a new string per character comparison |
| binary_trees | 199 ms | 24.9× | Value-semantics deep copy on every tree node assignment |
| string_ops | 88.4 ms | 52.0× | to_upper_case allocates a new string per call |
The typed array benchmarks (matmul, sieve, nbody) use int[]/float[] with int64_t*/double* backing stores and bounds-check elision. The remaining typed-array gap is two pointer hops (MonkValue → TypedArray → data) and, for sieve, the 8× memory stride difference versus C's char array. Structural gaps require COW arrays, string views, or closure escape analysis — none are small changes.
See bench/ for the harness and spec/PERFORMANCE.md for methodology.
Three rules resolve every edge case:
- Explicit over implicit. No hidden coercion, no hidden errors, no hidden mutation. One exception: truthiness (
false,none,0are falsy). - Graceful on reads, strict on operations. Reading missing data =
noneor[]. Operating on invalid data = error. - Values, not references. Assignment copies. Function args copy. Closures copy. No exceptions.
0.0.1 — Buniyaad (2026-04-04). 676 tests passing (510 Go + 166 C runtime). 21 benchmarks, 24 examples.
| Phase | Status |
|---|---|
| 1. Lexer | ✅ Done |
| 2. Parser | ✅ Done |
| 3. C Runtime Library | ✅ Done |
| 4. C Code Generation | ✅ Done |
| 5. CLI | ✅ Done |
| 6. Type System + scalar unboxing codegen | ✅ Done |
| 7. Module System | ✅ Done |
| 8. C FFI | ⬅️ Next |
| 9. Linter & Formatter | Planned |
| 10. LSP + Editor | Planned |
| 11. Distribution | Planned |
src/ Go compiler (module root)
main.go CLI entry point (53 tests)
embed.go Embedded runtime (self-contained binary)
syntax/ Lexer + Parser + AST (195 tests)
types/ Static type checker (112 tests)
codegen/ AST → C code generator + unboxing (67 tests)
module/ Module resolver, dependency graph (13 tests)
unbox.go Scalar/typed-array unboxing, storage kind inference
runtime/ C runtime library (8 .c files, 163 C tests)
runtime.h Public API (MonkValue + function declarations)
internal.h Shared helpers
value.c, arith.c, string.c, container.c, math.c, builtins.c, error.c, higher_order.c
spec/ Language specification (REFERENCE.md is the source of truth)
knowledge/ Learning course — monkfromearth.github.io/monk-lang/
examples/ 24 working .monk programs (including multi-file module example)
bench/ Benchmark suite (21 benchmarks, Monk vs C)
Makefile make build/install/test/clean
New to the codebase? Start with WALKTHROUGH.md — a plain-markdown tour of the compiler pipeline, key data structures, reading order, and how to add features. No build tools required.
- Code Walkthrough — developer guide to reading and contributing to the compiler
- Language Reference — the source of truth for syntax and semantics
- Architecture Decisions — why Go, why compile-to-C
- Roadmap — phased build plan with checkboxes
- Changelog — release history
- Knowledge Site — compiler course and learning resources
- v1 Archive — original TypeScript/Bun interpreter
MIT