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.
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.
#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;
}
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).
Then build and install:
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.
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 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 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);
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:
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/.
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.
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:
| Command | What 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 run | Build, then run |
| guild test | Build and run every tests/*.g as its own program |
| guild clean | Remove guild/ |
| -v | Print every g command, so any build can be repeated by hand |
| Module | What it gives you |
|---|---|
| GString | Growable 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… |
| GArray | Growable arrays of any item size: push, get, pop, insert, remove, remove_swap, sort |
| GMap | Hash maps with text keys: set, get, has, remove, next |
| GFile | Read or write a whole file in one call, with errors you can handle |
| GTrace | Prints an error's return trace: where it was raised and each try that passed it up |
| GMath | Vec2/3/4, Mat4 and quaternions, column-major in the glm conventions, ready for OpenGL, Vulkan and SDL |
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.
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 ↗.
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.
// 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);
}
}
// 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;
}
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!
$ cd examples/tour $ guild run # optimised build in guild/release/ $ guild build --release # show the exact g commands $ guild build -v
$ 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
| examples/ | Shows |
|---|---|
| hello | The smallest program |
| tour | The language features in one program (above) |
| stdlib-tour | GString, GArray, GMap, GFile and GMath |
| modules-source | Modules in folders, aliases, one module across two files |
| g-static-lib | A G library as a static library (.a + .gi), used through guild [deps] |
| g-shared-lib | A G library as a shared library (.so + .gi), imported with an alias |
| c-static-lib | A C library built as .a, imported with and without an alias |
| c-shared-lib | The same C library as .so |
| c-system-lib | zlib from the system, with and without an alias |
| raylib-window | A raylib window |
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.
The compiler, guild, the standard library and the examples
Linux x86-64 · MIT License
Editor support for .g files, built on g --lsp
codium --install-extension g-lang-0.10.0.vsix
— downloads of native G
Or clone it and build:
Tested on Arch and CachyOS, Ubuntu 24.04 and Debian 13. Windows: WSL2 with Ubuntu. Windows and macOS ports are welcome as contributions.
G turns your code into LLVM IR and uses clang to make machine code, and reads C headers through libclang. LLVM 18 or newer.
For source-level debugging: breakpoints and stepping on .g lines, G names in the variables view, and breakpoints on errors.
SDL, raylib, OpenGL, Vulkan, zlib… #import its header and link it. No bindings needed.
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.
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.gzThe 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
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.
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.
using, and variety from tagged unions.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.
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.
Built by Garrett Gaston (Galapagos Ware). Source on GitLab. MIT License.
Found a bug? Have a feature idea? Building something with G? There are two ways to reach us, depending on what you need.
Found something broken? Open a GitLab issue. It gets tracked, prioritised and fixed. GitLab account required.
Open an Issue ↗See what's already been reported, vote on features, or pick something to contribute to.
View All Issues ↗Questions, ideas, or just want to say you're using G? Use the form below.