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.

sum.gg
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

player.gg

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.

Median runtime of fib(38). Lower is faster.
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.gg compiles and runs. --release sets LLVM optimization level 3. Bounds checks remain.
  • gg build file.gg writes a binary. gg emit-ll prints LLVM IR. gg emit-c prints the C99 backend.
  • gg bindgen header.h writes extern declarations from a C header. The extern surface is narrow, and the compiler does not check the C code.
  • std.thread is 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.bat runs them.