G 0.10.0 is here. G has been rebuilt from scratch, and it is now a real, native compiler. What changed →
New in 0.10.0
G has been rebuilt.
It is now a full compiler.

The old G translated your code into C and handed that to gcc. That era is over. G 0.10.0 is a brand-new compiler written from scratch: it compiles G straight to native code through LLVM, with real debug info, a real module system, a standard library, a build tool, and full editor and debugger support.

Before · 0.8 transpiler
.g → generated C → gcc → binary
Now · 0.10.0 compiler
.g → G compiler → LLVM → native binary
G-Lang logo
Get G 0.10.0 Documentation View Source
v0.10.0 — Native compiler — MIT

G-Lang
C evolved.

C, improved. G keeps everything C programs and C libraries already do, and adds modules, safer defaults and modern conveniences, compiled to native code. Pasted C keeps its meaning or fails to compile, never silently changes.

What's in G 0.10.0 — hover a feature to compare with C
Native Compiler
Modules
C Libraries, Directly
G Libraries
Zero by Default
^ Pointers
Struct Superpowers
defer
Overloading & Defaults
Tagged Unions & match
Error Handling
switch & match
Standard Library
guild + Editor
Error Traces
fntype
Unqualified Imports
Caller Location
C · .c — the old way

          
G 0.10.0 · .g — the new way

        
A complete G program
#import <stdio.h>
#import Units                  // Units.g: no header file

enum Kind { SOLDIER, TANK };

// '!' means this can fail with an Error
Units.Unit! spawn(Kind kind, i32 supply)
{
    if (supply <= 0)
        return error(1, "no supply");
    match (kind) {             // must cover every Kind
    case SOLDIER: return (Units.Unit){.soldier = {}};
    case TANK:    return (Units.Unit){.tank = {}};
    }
    return error(3, "unknown kind");
}

int main(void)
{
    i32 kills;                 // zero by default
    Units.Soldier s;           // s.hp starts at 100
    Units.hurt(&s);            // overloaded, amount = 10
    defer printf("bye\n");     // runs as main ends

    Units.Unit u = spawn(TANK, 0) else {
        printf("can't: %s\n", err.msg);
        return 1;
    };
    return 0;
}

Installation

G 0.10.0 runs on Linux x86-64 and is tested on Arch and CachyOS, Ubuntu 24.04 and Debian 13. You need make and clang with LLVM 18 or newer and libclang. On Windows, use WSL2 with Ubuntu 24.04 (not tested separately).

# Arch and Arch-based distributions
sudo pacman -S --needed base-devel clang llvm

# Ubuntu 24.04 and Debian 13
sudo apt install build-essential clang libclang-dev llvm llvm-dev

Then build and install:

git clone https://gitlab.com/galapagos-ware/g-lang.git
cd g-lang
make -C compiler
sudo make -C compiler install  # into /usr/local; PREFIX=... to change it
g --version  # g 0.10.0

This installs g and guild into /usr/local/bin and the standard library into /usr/local/lib/g/std. G finds its standard library relative to its own location, so any prefix works. To try G without installing, add compiler/build to your PATH instead.

Your first program

guild new hello
cd hello
guild run

guild new creates guild.toml, src/main.g, a test, and VSCodium debug settings. guild build -v prints every g command it runs, so any build can be repeated by hand. g also works on its own, like a C compiler:

g -O2 -Wall -o hello src/main.g
./hello

Modules

G replaces #include and header files with #import. A file is a module named after the file, and its names are used through the module name. A module can span several files and live in its own folder.

#import Units                 // finds Units.g
#import player/Player         // a module in a folder
#import Physics as ph         // an alias

Units.hurt(&s);
ph.step(dt);

Everything in a module is visible to importers, except static names (module only) and static static names (this file only). Each module is compiled once, and modules may import each other.

C libraries

C reaches G through its header. Without an alias, a header's names are used exactly as in C; with one, they're qualified. Static and shared C libraries both work: link them as in C, or list them in guild.toml.

#import <zlib.h>              // compress(), uncompress()
#import <zlib.h> as z         // z.crc32(), z.Bytef

unsigned long crc = z.crc32(0, (const z.Bytef^)"hello", 5);
# guild.toml
name   = "zdemo"
c-libs = ["z"]

G libraries

A G module compiles to a static (.a) or shared (.so) library plus a .gi interface file. Importers use it exactly like source. With guild:

# engine/guild.toml
name    = "Engine"
kind    = "lib"
library = "shared"  # "static" (default), "shared" or "both"

# game/guild.toml
name = "game"
[deps]
Engine = "../engine"

guild builds dependencies first, and a program with shared dependencies runs in place. By hand, it's the C workflow: g -c -o out/MathLib.o MathLib/ then ar rcs, or g -shared -fPIC -o libMathLib.so MathLib/.

G features

i32 kills;                     // zero by default
f32 buf[4096] = undefined;     // opt out where speed matters

i32^ p = &kills;               // ^ declares a pointer; *p dereferences

struct Health { i32 hp = 100; i32 max_hp = 100; };
struct Soldier {               // a type name, no typedef
    using Health health;       // s.hp reaches s.health.hp
    f32 speed = 2.5f;          // field default
};

void hurt(Soldier^ s, i32 amount = 10);   // default parameter
void hurt(Tank^ t, i32 amount = 10);      // overload on type

{
    defer printf("cleanup\n"); // runs when the block ends
}

tagged union Unit { Soldier soldier; Tank tank; };
match (u) {                    // cases end on their own
case soldier as s: ...
case tank as t:    ...
}

switch (n) {
case 3:
    printf("three, ");
    fallthrough;               // falling through must be said out loud
case 2:
    printf("streak\n");
    break;
}

fntype Callback = int(int hp, float armor);   // function pointer type

#import Global unqualified     // clamp(x) as well as Global.clamp(x)

void log(const char^ msg,      // the caller's file and line, no macro
         const char^ file = __CALLER_FILE__, int line = __CALLER_LINE__);

Also: all of C23 and the GNU extensions C headers rely on, and the fixed-width types i8–i64, u8–u64, f32, f64.

Error handling

i64! load_score(const char^ path)          // '!' = can fail
{
    GString text = try GFile.read_all(path);  // pass errors up
    defer GString.free(&text);
    return try GString.to_int(text);
}

i64 score = load_score("save.txt") else {     // or handle them here
    printf("no save: %s\n", err.msg);
    return 1;
};

An error is a plain value, {int code; const char^ msg;}, made with return error(code, msg). There are no exceptions and no hidden control flow. else {}; deliberately ignores an error.

In debug builds, every error also carries a return trace: where it was raised and each try that passed it up. GTrace.print(err) prints it, and main may return ! to print it when the program fails:

error 2: not enough supply
  src/main.g:13: in spawn (raised)
  src/main.g:23: in main (passed up)

guild

CommandWhat it does
guild new name [--lib]Create a project: guild.toml, src/, tests/, assets/, VSCodium settings
guild build [--release]Build into guild/debug/ or guild/release/
guild runBuild, then run
guild testBuild and run every tests/*.g as its own program
guild cleanRemove guild/
-vPrint every g command, so any build can be repeated by hand
# guild.toml
name    = "hegemony"
kind    = "app"  # or "lib"
include = ["vendor"]
c-libs  = ["SDL3", "m"]
flags   = ["-Wall"]

[deps]
Engine = "../engine"

Standard library

ModuleWhat it gives you
GStringGrowable strings, or fixed ones on your own buffer. Always '\0'-terminated, so s.data goes straight to C. append, appendf, find, sub, to_int, to_float…
GArrayGrowable arrays of any item size: push, get, pop, insert, remove, remove_swap, sort
GMapHash maps with text keys: set, get, has, remove, next
GFileRead or write a whole file in one call, with errors you can handle
GTracePrints an error's return trace: where it was raised and each try that passed it up
GMathVec2/3/4, Mat4 and quaternions, column-major in the glm conventions, ready for OpenGL, Vulkan and SDL

Editor & debugger

The VSCodium extension (on the Download page) uses the compiler's own language server, g --lsp: highlighting, errors as you type, hover, completion with docs, and go-to-definition, including into compiled G libraries and the standard library. Debugging goes through gdb, with breakpoints and stepping on your .g lines and G names in the variables view. Tagged unions show only the member they hold. The Breakpoints panel has two error breakpoints: G errors: when raised (off by default) stops on every return error(...), and G errors: when main fails (on) stops before a failing program exits, with the error shown and the call stack below it.

codium --install-extension g-lang-0.10.0.vsix

Full specification

The complete language specification, with every rule, error message and design decision, lives in the repository: docs/spec/modules.md ↗ and docs/spec/guild.md ↗.

G in practice.

This is examples/tour from the repository: one small program that uses the language's features. The test suite builds and runs it on every change, so it always matches the compiler.

src/Units.gG
// Units.g: the types for the tour. This is a module: main.g reaches it as Units.<name>.

struct Pos
{
    f32 x;
    f32 y;
};

struct Health
{
    i32 hp = 100;                               // field defaults (milestone 3)
    i32 max_hp = 100;
};

struct Soldier                                  // no typedef needed: Soldier is a type name
{
    using Health health;                        // s.hp instead of s.health.hp
    Pos at;
    f32 speed = 2.5f;
};

struct Tank
{
    using Health health = {.hp = 400, .max_hp = 400};   // a using field with its own default
    Pos at;
    i32 shells = 20;
};

// One value that is either a Soldier or a Tank, and knows which (milestone 6).
tagged union Unit
{
    Soldier soldier;
    Tank tank;
};

// Overloading (milestone 4): the argument's type picks the version.
// Default parameters: leave out 'amount' and it's 10.
void hurt(Soldier^ s, i32 amount = 10)
{
    s->hp -= amount;
}

void hurt(Tank^ t, i32 amount = 10)
{
    t->hp -= amount / 2;                        // armor halves the damage
}

void hurt(Unit^ u, i32 amount = 10)
{
    match (*u) {                                // cases end on their own (milestone 7)
    case soldier as s: hurt(s, amount);         // s is a Soldier^ into *u
    case tank as t:    hurt(t, amount);
    }
}
src/main.gG
// main.g: a tour of G, milestones 2 to 7. Build and run it with: guild run

#import <stdio.h>
#import Units

enum Kind { SOLDIER, TANK };

// Error handling (milestone 6): '!' means this can fail with an Error {code, msg}.
Units.Unit! spawn(Kind kind, i32 supply)
{
    if (supply <= 0)
        return error(1, "no supply at all");
    if (supply > 3)
        return error(2, "not enough supply");
    match (kind) {                              // every enum constant must be handled
    case SOLDIER: return (Units.Unit){.soldier = {}};
    case TANK:    return (Units.Unit){.tank = {}};
    }
    return error(3, "unknown kind");
}

// 'try' passes an error straight up to whoever called build_army.
static i32! build_army(Units.Unit^ army, i32 supply)
{
    army[0] = try spawn(SOLDIER, supply);
    army[1] = try spawn(TANK, supply);
    return 2;
}

static void describe(Units.Unit u)
{
    match (u) {
    case soldier as s: printf("  soldier hp %d/%d speed %.1f\n", s->hp, s->max_hp, s->speed);
    case tank as t:    printf("  tank    hp %d/%d shells %d\n", t->hp, t->max_hp, t->shells);
    }
}

int main(void)
{
    // Zero by default (milestone 2): no garbage values.
    i32 kills;
    printf("kills start at %d\n", kills);

    // Struct defaults and using (milestone 3).
    Units.Soldier s;
    printf("soldier hp %d (through using), speed %.1f\n", s.hp, s.speed);

    // Overloading and default parameters (milestone 4).
    Units.Tank t;
    Units.hurt(&s);                             // amount defaults to 10
    Units.hurt(&t, 40);                         // the Tank version: armor halves it
    printf("after hits: soldier %d, tank %d\n", s.hp, t.hp);

    // defer (milestone 4): runs when the block ends, last one first.
    {
        defer printf("  ...cleanup 1\n");
        defer printf("  ...cleanup 2\n");
        printf("  inside a block\n");
    }

    // Error handling (milestone 6).
    Units.Unit army[2];
    i32 n = build_army(army, 5) else {
        printf("can't build: %s (code %d)\n", err.msg, err.code);
    };                                          // the block didn't leave, so n is 0
    n = build_army(army, 2) else {
        printf("unexpected: %s\n", err.msg);
        return 1;
    };
    printf("built %d units\n", n);

    // Tagged unions with match (milestones 6 and 7).
    for (i32 i = 0; i < n; i++) {
        Units.hurt(&army[i], 30);               // the Unit^ version picks the right one
        describe(army[i]);
    }

    // ^ pointers (milestone 2); * still dereferences.
    i32^ p = &kills;
    *p += 3;
    printf("kills now %d\n", kills);

    // switch (milestone 7): falling into the next case must be said out loud.
    switch (kills) {
    case 3:
        printf("three kills, ");
        fallthrough;
    case 2:
        printf("a streak!\n");
        break;
    default:
        printf("keep going\n");
    }
    return 0;
}
guild runOUTPUT
kills start at 0
soldier hp 100 (through using), speed 2.5
after hits: soldier 90, tank 380
  inside a block
  ...cleanup 2
  ...cleanup 1
can't build: not enough supply (code 2)
built 2 units
  soldier hp 70/100 speed 2.5
  tank    hp 385/400 shells 20
kills now 3
three kills, a streak!

Build it two ways

with guildbuild tool
$ cd examples/tour
$ guild run

# optimised build in guild/release/
$ guild build --release

# show the exact g commands
$ guild build -v
with gcompiler
$ cd examples/stdlib-tour
$ g -o stdlib-tour src/main.g
$ ./stdlib-tour

# the usual C compiler flags work
$ g -O2 -Wall -g -o app src/main.g -lSDL3

Every example in the repository

examples/Shows
helloThe smallest program
tourThe language features in one program (above)
stdlib-tourGString, GArray, GMap, GFile and GMath
modules-sourceModules in folders, aliases, one module across two files
g-static-libA G library as a static library (.a + .gi), used through guild [deps]
g-shared-libA G library as a shared library (.so + .gi), imported with an alias
c-static-libA C library built as .a, imported with and without an alias
c-shared-libThe same C library as .so
c-system-libzlib from the system, with and without an alias
raylib-windowA raylib window

Browse the examples on GitLab ↗

v0.10.0 — Latest Release — Native compiler

Download G

G 0.10.0 adds error return traces, error breakpoints, fntype, unqualified imports and caller location defaults, and runs on Arch, Ubuntu 24.04 and Debian 13. It builds from source on Linux x86-64; all it needs is clang and LLVM.

VSCodium Extension

Editor support for .g files, built on g --lsp

  • Highlighting and errors as you type
  • Hover, completion with docs
  • Go-to-definition, even into libraries
  • Debugging through gdb, with error breakpoints
  • Needs G 0.10.0 installed
Download Extension 0.10.0

codium --install-extension g-lang-0.10.0.vsix

— downloads of native G

Or clone it and build:

$ git clone https://gitlab.com/galapagos-ware/g-lang.git
$ cd g-lang && make -C compiler
$ sudo make -C compiler install

What you need

Required

Linux x86-64

Tested on Arch and CachyOS, Ubuntu 24.04 and Debian 13. Windows: WSL2 with Ubuntu. Windows and macOS ports are welcome as contributions.

Required

clang + LLVM

G turns your code into LLVM IR and uses clang to make machine code, and reads C headers through libclang. LLVM 18 or newer.

Optional

gdb + VSCodium

For source-level debugging: breakpoints and stepping on .g lines, G names in the variables view, and breakpoints on errors.

Optional

Any C library

SDL, raylib, OpenGL, Vulkan, zlib… #import its header and link it. No bindings needed.

The old transpilers

Before the native compiler, G was a transpiler that translated G into C. Both versions are kept here for reference only: they are deprecated and get no fixes, and their old repositories now just point here. This page is the only place to download them.

DEPRECATED

C transpiler · 0.8

The last transpiler: guild written in C, translating .g to C for gcc or clang. Includes its VSCode extension and tests.

g-lang-0.8-c-transpiler.tar.gz
DEPRECATED

Python transpiler

The original G: gompile.py and guild.py, with the first standard library and a debug adapter.

g-lang-python-transpiler.tar.gz

— transpiler-era downloads & clones

Why G exists.

C is the language games are built in. From id Software's Quake to modern indie engines, C's performance, predictability and direct hardware access make it the pragmatic choice. But C has papercuts: small, daily frustrations that pile up over a project.

G fixes the papercuts. It adds what C programmers reach for constantly (modules instead of headers, zero-initialised variables, defer, overloading, default parameters, tagged unions, error values and a small standard library) and keeps everything else exactly as C does it. G isn't only for games: it's improved C for any programmer, and game programming is the benchmark it is measured against.

From transpiler to compiler

G started as a transpiler: first a Python script, then a rewrite in C that reached 0.8. It worked, but you wrote G and debugged C, and every feature was a text rewrite with sharp edges.

G 0.10.0 is a complete rebuild. It's a new compiler front end written in C, on top of LLVM: it reads G, writes LLVM IR with real debug info, and hands it to clang for machine code. C headers are read through libclang, so any C library works straight from its header. The module system is real (each module is compiled once, and no text is ever pasted), G libraries ship as .a/.so plus a .gi interface, and the editor and debugger understand G itself.

The old transpilers are still available as deprecated downloads.

Philosophy

Status

G is at 0.10.0, for Linux x86-64. Releases stay 0.x until a real project, the Hegemony game, builds from a dozen or so G files with graphics, without errors. 1.0 then freezes the .gi library format and the binary layouts.

How it's built

The language design is Galapagos Ware's. The specification and test suite are written by hand with help from Claude (Anthropic's AI), and the compiler itself is written by a Claude Code agent working unattended in a locked-down container, graded by a test suite it can't change. The commit history shows who wrote what.

Author

Built by Garrett Gaston (Galapagos Ware). Source on GitLab. MIT License.

Get in touch.

Found a bug? Have a feature idea? Building something with G? There are two ways to reach us, depending on what you need.

Report a Bug

Found something broken? Open a GitLab issue. It gets tracked, prioritised and fixed. GitLab account required.

Open an Issue ↗

Browse Issues

See what's already been reported, vote on features, or pick something to contribute to.

View All Issues ↗

General feedback

Questions, ideas, or just want to say you're using G? Use the form below.

Send a message

Message sent — thanks for the feedback!