Greengold
An indentation-sensitive, immutable-by-default systems language.
The greengold compiler turns .gg files into LLVM IR with llvmlite, then links a native binary with the system gcc or clang.
There is no garbage collector.
A read or lend reference lasts for the call. It cannot be stored in a local, placed in a field, or returned.
Array, slice, and string bounds checks stay on in --release. --unchecked is the switch that removes them.
const MASK: u32 = 0xFF
fn sum(a: []i32) -> i32:
var total := 0
for x in a:
total += x
return total
fn main():
nums := [10, 20, 30, 40]
print("Total:", sum(nums))
print("Mask:", MASK & 0x0F)
Soon The compiler is not published yet.
Language
Five rules the compiler actually enforces.
Immutable by default
A binding cannot be assigned unless it is declared with var. Top-level var is rejected. Globals are const, and a const is limited to literals and arithmetic.
const LIMIT: i32 = 100
var total := 0
read, lend, consume
read is the default: the callee does not mutate the value. lend borrows a var for the call and writes through it. consume moves an owning value; the caller cannot use it after the call.
fn show(c: Counter):
print(c.value)
fn bump(c: lend Counter):
c.value += 1
fn finish(c: consume Counter):
print(c.value)
References stay in the call
A lend or read reference cannot be saved in a local variable, stored in a struct field, or returned. That is the memory rule: no lifetime syntax, and no reference that outlives the call. Heap owners such as Box and List are dropped at the end of the scope.
fn bump_twice(c: lend Counter):
bump(c)
bump(c)
Methods belong in impl
A struct lists fields. Methods are written in impl Type:.
impl Player:
fn take_damage(self: lend Player, amount: i32):
self.health -= amount
Casts are written out
There are no implicit numeric conversions. i64(x) and f64(n) are the casts. An out-of-range const such as u8(300) is a compile error.
x := i64(42)
f := f64(3) / 2.0
Examples
Taken from examples/. A few files are cut down so the sample fits on the page.
Structs and impl
Benchmarks
One recursive workload: fib(38), from benchmarks/benchmark_results.json.
The C++ and Rust programs read an optional argument and default to 38. The Greengold program calls fib(38) directly.
Greengold --release still checks array bounds; this function does not index an array, so that does not affect the time.
Treat the table as this one measurement.
| Compiler | Median |
|---|---|
| C++ (GCC -O3) | 89.9 ms |
| Greengold (LLVM -O3) | 114.0 ms |
| Rust (opt-level = 3) | 225.3 ms |
Tools
The compiler command is gg. python -m greengold is the same program. Neither is published for install yet.
gg run file.ggcompiles and runs.--releasesets LLVM optimization level 3. Bounds checks remain.gg build file.ggwrites a binary.gg emit-llprints LLVM IR.gg emit-cprints the C99 backend.gg bindgen header.hwritesexterndeclarations from a C header. The extern surface is narrow, and the compiler does not check the C code.std.threadis a thin wrapper over pthreads. The type system does not track which values may cross a thread.- The extension in
vscode-extension/starts a Python language server for completion, the symbol list, and formatting. - Tests live in
tests/. On Windows,.\test.batruns them.