Language Tour
Syntax Basics
Semicolons are optional. You can use them if you prefer, but GX does not require them:
var a = 10 // fine
var b:f32 = 3.14; // also fine
Variables
var a = 10 // i32
var b:f32 = 3.14
var name:str = "GX"
Constants
const PI = 3.14159265358979
const TAU = PI * 2.0
const MAX_ENTITIES:i32 = 1024
const FLAG_ALL = FLAG_READ | FLAG_WRITE | FLAG_EXEC
Constants are evaluated at compile time (constant folding). The type can be inferred or explicit. Assignment to a const is a compile error.
Slices
Views into contiguous data — no copying:
var arr: i32[5] = {10, 20, 30, 40, 50};
var s: []i32 = arr;
print("len={s.len}\n"); // 5
var mid: []i32 = s[1:3]; // {20, 30} — zero-copy
Strings
str is a fat string — carries pointer + length for O(1) .len access and value-based == comparison:
var s:str = "hello"
print("length: {s.len}\n") // 5
s.cstr // raw const char* for C interop
String Methods
Built-in methods on str — most are zero-copy (no allocation):
var s = " Hello, World! "
// Search
s.find("World") // 7 (byte index, or -1)
s.find_last("l") // 10
s.contains("Hello") // true
s.starts_with(" He") // true
s.ends_with("! ") // true
s.count("l") // 3
// Slice & trim (zero-copy)
s.sub(2, 5) // "Hello"
s.sub(9) // "World! " (to end)
s.trim() // "Hello, World!"
s.trim_left() // "Hello, World! "
s.trim_right() // " Hello, World!"
// Transform (uses scratch buffer)
s.upper() // " HELLO, WORLD! "
s.lower() // " hello, world! "
s.replace("World", "GX") // " Hello, GX! "
"-".repeat(20) // "--------------------"
// Split
var parts = "a,b,c".split(",") // ["a", "b", "c"]
parts.len // 3
for (var p in parts) { print(p) }
String Interpolation
Use {expr} inside string literals to embed expressions:
var x:i32 = 42;
var name:str = "GX";
print("Hello {name}, value={x}\n");
print("2+3={2 + 3}\n");
Expressions are automatically converted to strings via .str().
Input
var name:str = input("Your name? ");
print("Hello, {name}!\n");
var line:str = input(); // no prompt
input() reads a line from stdin (without the trailing newline). The optional string argument is a prompt.
Functions
fn add:i32(x:i32, y:i32) {
return x + y;
}
Structs
struct Point {
x:f32
y:f32
}
Extensions
ex Point {
fn length:f32() {
return self.x * self.x + self.y * self.y;
}
}
Enums
enum Color {
Red
Green
Blue
}
Members are separated by newlines (commas optional). Stored as i32.
Loops
for (i = 1:5) { } // Inclusive range
for (var x in arr) { } // For-each
for (var x in arr) where (x > 3) { } // Filtered for-each
par for (i = 0:9) { } // Parallel (OpenMP)
Collect (List Comprehensions)
Build a dynamic array from a filtered/transformed loop:
var nums:i32[6] = {1, 2, 3, 4, 5, 6};
var evens = for (n in nums) where (n % 2 == 0) collect n;
var doubled = for (n in nums) where (n <= 3) collect n * 10;
evens.free();
doubled.free();
The result is an array<T> (dynamic array) that must be freed when done.
Defer
fn process() {
var nums:array<i32>;
nums.init();
defer nums.free(); // Runs at block exit, LIFO order
nums.push(10);
nums.push(20);
return; // defer fires before return
}
Vectors & Matrices
GX has built-in f32-based vector and matrix types — no imports needed:
var v:vec2 = vec2{1.0, 2.0};
var a:vec3 = vec3{1.0, 2.0, 3.0};
var b:vec4 = vec4{1.0, 2.0, 3.0, 4.0};
var m:mat4 = identity_mat4();
// Component access
var px:f32 = a.x;
var col:vec4 = m.col[0];
// Built-in math
var d:f32 = dot(a, a);
var c:vec3 = cross(a, a);
// String interpolation works
print("a = {a}\n"); // a = vec3(1, 2, 3)
Types: vec2, vec3, vec4, mat2, mat3, mat4. Matrices are column-major.
Swizzle
var v = vec3{1.0, 2.0, 3.0};
var xy = v.xy; // vec2(1, 2)
var rev = v.zyx; // vec3(3, 2, 1)
var dup = v.xx; // vec2(1, 1)
Components: x, y, z, w. Result type depends on swizzle length.
Modules
module util;
import util;
pub fn hello() {}
Module Resolution
Dotted module names map directly to directory structures.
Example:
import sokol.app
Resolves to:
./sokol/app/
All .gx files inside that directory are loaded as part of the module.
The compiler searches for modules starting from the current working directory
and any paths specified with -I.
Import Loading
Imports are loaded using BFS (breadth-first) ordering:
- The entry file is parsed first.
- Its
importstatements are queued. - Each imported module’s
.gxfiles are parsed and their imports queued. - This continues until all transitive dependencies are loaded.
All collected AST nodes are then resolved and type-checked together, so cross-module references (e.g., enum members defined in an imported module) are visible everywhere.
Module Package Layout
A typical module package (e.g., Sokol bindings) looks like:
modules/
sokol/
gx/
app.gx // module sokol.app (extern declarations)
gfx.gx // module sokol.gfx (extern declarations)
c/
sokol_app.h // C headers
sokol_gfx.h
sokol_impl.c // C implementation
Build with:
gx examples/triangle/input.gx -I modules --cfile modules/sokol/c/sokol_impl.c