Examples

Learn GX by example. Each snippet is a complete, runnable program.

Basics

Hello World 01_hello_world.gx
// 01_hello_world.ec - The simplest EC program
// Build: ec examples/01_hello_world.ec -o build/01_hello_world.exe

fn main() {
    var a:i32  = 100;
    var b:i32 = 200;
    var name:str = "Afan";
    print("Hello, GXers!! {a} : {b} : {name}\n");
}
Variables 02_variables.gx
// 02_variables.ec - Variable declarations, types, and string interpolation
// Build: ec examples/02_variables.ec -o build/02_variables.exe

fn main() {
    // Type inference with var
    var a = 10;
    var b = 3.14;
    var name = "GX";

    // Explicit types
    var x:i32 = 100;
    var y:f32 = 2.718;
    var big:i64 = 9999999999;
    var precise:f64 = 3.14159265358979;

    // String interpolation
    print("a={a}\n");
    print("b={b}\n");
    print("name={name}\n");
    print("x={x} y={y}\n");
    print("big={big}\n");
    print("precise={precise}\n");

    print("i32 as string: " + x.str() + "\n");
    print("f64 as string: " + precise.str() + "\n");

    // Assignment
    x = x + 1;
    print("x after +1: {x}\n");
}
Print And Strings 03_print_and_strings.gx
// 03_print_and_strings.ec - Print, string concatenation, interpolation, UTF-8
// Build: ec examples/03_print_and_strings.ec -o build/03_print_and_strings.exe

fn main() {
    do_some()
    do_some1()
    // Basic print
    print("Hello GXers!\n");
    var s:str = "Family";
    // String concatenation with +
    var greeting = "Hello" + ", " + "GX {s}!!! \n";
    print(greeting)

    // String interpolation with {expr}
    var x:i32 = 42;


    var f:f32 = 3.14;
    print("int={x} float={f}\n");

    // .str() converts any type to string
    var v = vec3{1.0, 2.0, 3.0};
    print("vec3={v}\n");
    print("vec3 via concat: " + v.str() + "\n");

    // UTF-8 strings
    var utf = "Hello UTF-8: cafe\n";
    print(utf);

    // Escape sequences
    print("Tab:\there\n");
    print("Quote: \"hello\"\n");

    // String + number auto-converts
    print("result=" + (10 + 20).str() + "\n");
}

Data Types

Control Flow 04_control_flow.gx
// 04_control_flow.ec - If/elif/else, while loops, for-range, for-each, collect
// Build: ec examples/04_control_flow.ec -o build/04_control_flow.exe

fn main() {
    // --- If / elif / else ---
    var score:i32 = 75;
    var x:i32 = 10;

    print("Helloo\n");
    if (score >= 90) {
        print("Grade: A\n");
    } elif (score >= 80) {
        print("Grade: B\n");
    } elif (score >= 70) {
        print("Grade: C\n");
    } else {
        print("Grade: F\n");
    }

    // --- While loop ---
    var count:i32 = 0;
    while (count < 5) {
        print("count={count}\n");
        count = count + 1;
    }

    // --- For-range (inclusive) ---
    print("\nFor-range 1 to 5:\n");
    for (i = 1:5) {
        print("  i={i}\n");
    }

    // --- For-each over array ---
    print("\nFor-each:\n");
    var arr:i32[5] = {10, 20, 30, 40, 50};
    for (var x in arr) {
        print("  x={x}\n");
    }

    // --- For-each with where filter ---
    print("\nFiltered (even only):\n");
    var nums:i32[8] = {1, 2, 3, 4, 5, 6, 7, 8};
    for (var n in nums) where (n % 2 == 0) {
        print("  even={n}\n");
    }

    // --- Collect: build array from loop ---
    var evens = for (n in nums) where (n % 2 == 0) collect n;
    print("\nCollected evens ({evens.len()}):");
    for (i = 0:3) {
        print(" {evens.at(i)}");
    }

    print("\n");

    // Collect with transform
    var doubled = for (n in nums) where (n <= 4) collect n * 10;
    print("Doubled small ({doubled.len()}):");
    for (i = 0:3) {
        print(" {doubled.at(i)}");
    }
    print("\n");

    evens.free();
    doubled.free();
}
Arrays 05_arrays.gx
// 05_arrays.ec - Fixed-size arrays: declaration, init, iteration
// Build: ec examples/05_arrays.ec -o build/05_arrays.exe

fn main() {
    // Fixed-size array with initializer
    var scores:i32[5] = {95, 82, 74, 88, 91};

    var win:[]i32 = scores[0:3];

    print("Winners:\n");
    for (var s in win) {
        print(" win {s}\n");
    }

    // Array with inferred size
    var colors:f32[3] = {1.0, 0.0, 0.0};
    print("\nRGB: {colors[0]}, {colors[1]}, {colors[2]}\n");

    // Modify by index
    scores[0] = 100;
    print("\nUpdated first score: {scores[0]}\n");

    // Iterate with range
    print("\nAll scores after update:\n");
    for (i = 0:4) {
        print("  scores[{i}] = {scores[i]}\n");
    }
}
Dynamic Arrays 06_dynamic_arrays.gx
// 06_dynamic_arrays.ec - array<T> dynamic container: init, push, sort, remove, for-each
// Build: ec examples/06_dynamic_arrays.ec -o build/06_dynamic_arrays.exe

fn main() {
    // --- Static array for-each ---
    print("Static array\n");
    var week_days:str[7] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
    for (item:str in week_days) {
        print("Day: {item}\n");
    }

    // --- Dynamic array with initializer syntax ---
    print("\nDynamic array (initializer syntax)\n");
    var darr:array<str> = {"Afan", "Sanela", "Ajdin"};
    defer darr.free();

    // For-each works on dynamic arrays
    for (var item in darr) {
        print("  item: {item}\n");
    }

    // Multiple iterations work fine
    print("\nSecond iteration with index:\n");
    var cnt = 0;
    for (var item in darr) {
        print("  [{cnt}] {item}\n");
        cnt = cnt + 1;
    }

    // Index-based loop also works
    print("\nIndex-based loop:\n");
    for (j = 0:darr.len()-1) {
        print("  {darr.at(j)}\n");
    }

    // --- Dynamic array without initializer (auto-init) ---
    var a:array<i32>;
    defer a.free();

    // Push elements
    a.push(30);
    a.push(10);
    a.push(50);
    a.push(20);
    a.push(40);
    print("\nAfter push: len={a.len()} cap={a.cap()}\n");

    // Access elements
    print("a[0]={a.at(0)} a[1]={a.at(1)} a[2]={a.at(2)}\n");

    // For-each over i32 dynamic array
    print("\nFor-each over i32 array:\n");
    for (var n in a) {
        print("  {n}\n");
    }

    // Sort
    a.sort();
    print("\nAfter sort:\n");
    for (var n in a) {
        print("  {n}\n");
    }

    // Remove (swap with last - O(1))
    a.remove_swap(0);
    print("\nAfter remove_swap(0): len={a.len()}\n");
    for (var n in a) {
        print("  {n}\n");
    }

    // Clear
    a.clear();
    print("\nAfter clear: len={a.len()}\n");

    // --- Collect: create dynamic array from loop ---
    var data:i32[6] = {5, 12, 3, 18, 7, 20};
    var big = for (x in data) where (x >= 10) collect x;
    print("\nCollected (>= 10): ");
    for (var n in big) {
        print("{n} ");
    }
    print("(len={big.len()})\n");
    big.free();

    print("\nDone.\n");
}
Structs 07_structs.gx
// 07_structs.ec - Structs, field access, extension methods
// Build: ec examples/07_structs.ec -o build/07_structs.exe

struct Player {
    id:i32
    name:str
    score:f32
}

ex Player {
    fn greet() {
        print("Player {name} (id={id}) score={score}\n");
    }

    fn add_score(points:f32) {
        score = score + points;
    }
}

struct Vec2i {
    x:i32
    y:i32
}

ex Vec2i {
    fn to_str:str() {
        return "({x}, {y})";
    }
}

fn main() {
    // Create with initializer
    var p = Player{1, "Alice", 0.0};
    p.greet();

    // Modify via extension method
    p.add_score(100.5);
    p.add_score(50.0);
    p.greet();

    // Direct field access
    p.name = "Bob";
    print("Renamed to: {p.name}\n");

    // Custom struct with str conversion
    var pos = Vec2i{10, 20};
    print("Position: " + pos.to_str() + "\n");
}
Enums 08_enums.gx
// 08_enums.ec - Enums: declaration, comparison, branching
// Build: ec examples/08_enums.ec -o build/08_enums.exe

enum Direction {
    North
    South
    East
    West
}

enum Color {
    Red = 0
    Green = 1
    Blue = 2
}

fn main() {
    // Declare enum variables
    var dir:Direction = East;

    // Compare enum values
    if (dir == North) {
        print("Heading North\n");
    } elif (dir == East) {
        print("Heading East\n");
    } elif (dir == South) {
        print("Heading South\n");
    } else {
        print("Heading West\n");
    }

    // Enum to string (shows integer value)
    print("East = {dir}\n");

    // Another enum
    var c:Color = Blue;
    print("Blue = {c}\n");
}
Map 39_map.gx
// 39_map.gx - Hash map: map<K,V>
// Build: gx examples/39_map.gx -o build/39_map.exe

// Test 1: String-keyed map
fn test_str_map() {
    var scores:map<str, i32>

    var names:map<str,str>

    names.set("Afan","OLOVCIC")
    names.set("Sanela","GLUHOVIC")
    names.set("Ajdin","OLOVCIC")

    for (var k, v in names) {
        print("name: {k} {v}\n")
    }
    for (var item in names) {
        print("name: {item.key} {item.value}\n")
    }

    scores.set("alice", 95)
    scores.set("bob", 82)
    scores.set("charlie", 91)

    var a:str = "alice"
    var b:str = "bob"
    var c:str = "charlie"
    var d:str = "dave"

    print("alice: ")
    print("{scores.get(a)}\n")
    print("bob: ")
    print("{scores.get(b)}\n")
    print("has charlie: {scores.has(c)}\n")
    print("has dave: {scores.has(d)}\n")
    print("len: {scores.len()}\n")

    // Update existing key
    scores.set("bob", 88)
    print("bob updated: {scores.get(b)}\n")

    // Remove
    scores.remove(c)
    print("after remove, has charlie: {scores.has(c)}\n")
    print("len after remove: {scores.len()}\n")

    scores.free()
}

// Test 2: Integer-keyed map
fn test_int_map() {
    var names:map<i32, str>
    names.set(1, "one")
    names.set(2, "two")
    names.set(3, "three")

    print("1 -> ")
    print(names.get(1))
    print("\n")
    print("2 -> ")
    print(names.get(2))
    print("\n")
    print("3 -> ")
    print(names.get(3))
    print("\n")
    print("has 4: {names.has(4)}\n")

    names.free()
}

// Test 3: Many entries (tests grow/rehash)
fn test_grow() {
    var m:map<i32, i32>
    for (i = 0:99) {
        m.set(i, i * i)
    }
    print("len after 100 inserts: {m.len()}\n")
    print("m[50] = {m.get(50)}\n")
    print("m[99] = {m.get(99)}\n")

    // Clear and verify
    m.clear()
    print("len after clear: {m.len()}\n")

    m.free()
}

// Test 4: Map iteration — destructured (var k, v)
fn test_iter_kv() {
    var scores:map<str, i32>
    scores.set("alice", 95)
    scores.set("bob", 62)
    scores.set("charlie", 91)
    scores.set("dave", 55)

    print("All scores (k, v):\n")
    for (var name, score in scores) {
        print("  {name}: {score}\n")
    }

    print("High scores (>= 80):\n")
    for (var name, score in scores) where (score >= 80) {
        print("  {name}: {score}\n")
    }

    scores.free()
}

// Test 5: Map iteration — entry-based (var item → item.key, item.value)
fn test_iter_entry() {
    var scores:map<str, i32>
    scores.set("alice", 95)
    scores.set("bob", 62)
    scores.set("charlie", 91)
    scores.set("dave", 55)

    print("All scores (entry):\n")
    for (var entry in scores) {
        print("  {entry.key}: {entry.value}\n")
    }

    print("High scores (>= 80):\n")
    for (var entry in scores) where (entry.value >= 80) {
        print("  {entry.key}: {entry.value}\n")
    }

    // Integer-keyed iteration
    var m:map<i32, i32>
    for (i = 0:9) {
        m.set(i, i * i)
    }
    print("Squares:\n")
    for (var item in m) {
        print("  {item.key}^2 = {item.value}\n")
    }

    scores.free()
    m.free()
}

fn main() {
    print("=== String-keyed map ===\n")
    test_str_map()
    print("\n=== Integer-keyed map ===\n")
    test_int_map()
    print("\n=== Grow/rehash test ===\n")
    test_grow()
    print("\n=== Map iteration (k, v) ===\n")
    test_iter_kv()
    print("\n=== Map iteration (entry) ===\n")
    test_iter_entry()
}

Functions & Control

Vec Mat Math 09_vec_mat_math.gx
// 09_vec_mat_math.ec - Built-in vector and matrix types
// Build: ec examples/09_vec_mat_math.ec -o build/09_vec_mat_math.exe

fn main() {
    // --- Vectors ---
    var a = vec3{1.0, 2.0, 3.0};
    var b = vec3{4.0, 5.0, 6.0};

    // Vector addition
    var c = a + b;
    print("a + b = {c}\n");

    // Scalar multiply
    var d = a * 2.0;
    print("a * 2 = {d}\n");

    // Negate
    var e = -a;

    var len = 32;
    for (i=0:len) {
        print("i = {i}\n");
    }

    print("-a = {e}\n");

    // Field access
    print("a.x={a.x} a.y={a.y} a.z={a.z}\n");

    // --- vec2 and vec4 ---
    var v2 = vec2{10.0, 20.0};
    print("vec2: {v2}\n");

    var v4 = vec4{1.0, 0.0, 0.0, 1.0};
    print("vec4: {v4}\n");

    // --- Swizzle ---
    var sw1 = a.xy;
    print("a.xy = {sw1}\n");

    var sw2 = a.zyx;
    print("a.zyx = {sw2}\n");

    var sw3 = v4.wz;
    print("v4.wz = {sw3}\n");

    var sw4 = v2.yx;
    print("v2.yx = {sw4}\n");

    // Repeat components
    var sw5 = a.xx;
    print("a.xx = {sw5}\n");

    // --- Matrix ---
    var M = mat4{
        vec4{1,0,0,0},
        vec4{0,1,0,0},
        vec4{0,0,1,0},
        vec4{0,0,0,1},
    };
    print("Identity: {M}\n");
}
Functions 10_functions.gx

// 10_functions.ec - Functions, return types, recursion
// Build: ec examples/10_functions.ec -o build/10_functions.exe

fn add:i32(a:i32, b:i32) {
    return a + b;
}

fn larger:i32(a:i32, b:i32) {
    if (a > b) {
        return a;
    }
    return b;
}

fn factorial:i32(n:i32) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

fn greet(name:str) {
    print("Hello, {name}!\n");
}

fn main() {
    // Basic function calls
    var sum = add(10, 20);
    print("add(10, 20) = {sum}\n");

    var m = larger(42, 17);
    print("larger(42, 17) = {m}\n");

    // Recursion
    for (i = 1:8) {
        print("{i}! = {factorial(i)}\n");
    }

    // Void function
    greet("EC");
}
Bool 11_bool.gx
// 11_bool.ec - Boolean literals: true and false
// Build: ec examples/11_bool.ec -o build/11_bool.exe

fn main() {
    var a:bool = true;
    var b:bool = false;

    print("a = {a}\n");
    print("b = {b}\n");

    if (a) {
        print("a is true\n");
    }

    if (!b) {
        print("b is false\n");
    }

    // Boolean logic
    var c = a && b;
    var d = a || b;
    print("true && false = {c}\n");
    print("true || false = {d}\n");

    // Comparison produces bool
    var x = 5;
    var gt = x > 3;
    print("5 > 3 = {gt}\n");

    // Use in conditions directly
    var done = false;
    var count = 0;
    while (!done) {
        count = count + 1;
        if (count == 3) {
            done = true;
        }
    }
    print("counted to {count}\n");
}
Input 12_input.gx
// Example: input() built-in function
// Reads a line from stdin, with an optional prompt string.

fn main() {
    var name:str = input("What is your name? ");
    print("Hello, {name}!\n");

    var color:str = input("Favorite color? ");
    print("{name} likes {color}\n");

    // input() with no prompt
    print("Type something: ");
    var line:str = input();
    print("You said: {line}\n");
}
Bitwise 13_bitwise.gx
// 13_bitwise.ec - Bitwise operators and hex literals
// Build: ec examples/13_bitwise.ec -o build/13_bitwise.exe

fn main() {
    // --- Hex literals ---
    var a:i32 = 0xFF;
    var b:i32 = 0x0F;
    var c:i32 = 0x0A;

    print("Hex literals: a=0xFF={a}, b=0x0F={b}\n");

    // --- Bitwise AND ---
    var and_result:i32 = a & b & c;
    print("\nBitwise AND: 0xFF & 0x0F = {and_result}\n");

    // --- Bitwise OR ---
    var or_result:i32 = 0xF0 | 0x0F;
    print("Bitwise OR:  0xF0 | 0x0F = {or_result}\n");

    // --- Bitwise XOR ---
    var xor_result:i32 = 0xFF ^ 0x0F;
    print("Bitwise XOR: 0xFF ^ 0x0F = {xor_result}\n");

    // --- Bitwise NOT ---
    var not_result:i32 = ~0;
    print("Bitwise NOT: ~0 = {not_result}\n");

    // --- Left shift ---
    var lshift:i32 = 1 << 4;
    print("Left shift:  1 << 4 = {lshift}\n");

    // --- Right shift ---
    var rshift:i32 = 256 >> 3;
    print("Right shift: 256 >> 3 = {rshift}\n");

    // --- Flags pattern ---
    print("\n--- Flags pattern ---\n");
    var FLAG_READ:u32  = 1 << 0;
    var FLAG_WRITE:u32 = 1 << 1;
    var FLAG_EXEC:u32  = 1 << 2;

    var perms:u32 = FLAG_READ | FLAG_EXEC;
    print("Permissions: {perms}\n");
    print("Has READ:  {perms & FLAG_READ}\n");
    print("Has WRITE: {perms & FLAG_WRITE}\n");
    print("Has EXEC:  {perms & FLAG_EXEC}\n");

    // Add write permission
    perms = perms | FLAG_WRITE;
    print("\nAfter adding WRITE: {perms}\n");
    print("Has WRITE: {perms & FLAG_WRITE}\n");

    // Remove exec permission (AND with inverted flag)
    perms = perms & ~FLAG_EXEC;
    print("\nAfter removing EXEC: {perms}\n");
    print("Has EXEC: {perms & FLAG_EXEC}\n");

    // Toggle read with XOR
    perms = perms ^ FLAG_READ;
    print("\nAfter toggling READ: {perms}\n");
    print("Has READ: {perms & FLAG_READ}\n");

    // --- Byte extraction ---
    print("\n--- Byte extraction ---\n");
    var val:i32 = 0xABCD;
    var low:i32 = val & 0xFF;
    var high:i32 = (val >> 8) & 0xFF;
    print("0xABCD low byte:  {low}\n");
    print("0xABCD high byte: {high}\n");

    // --- Bit counting (popcount) ---
    print("\n--- Bit counting ---\n");
    var num:i32 = 0xFF;
    var count:i32 = 0;
    var tmp:i32 = num;
    for (i = 0:31) {
        if (tmp & 1) {
            count = count + 1;
        }
        tmp = tmp >> 1;
    }
    print("Bits set in 0xFF: {count}\n");

    // --- Power of two check ---
    print("\n--- Power of two check ---\n");
    var vals:i32[6] = {1, 2, 3, 4, 16, 255};
    for (var v in vals) {
        var is_pow2:i32 = 0;
        if (v & (v - 1)) {
            is_pow2 = 0;
        } else {
            is_pow2 = 1;
        }
        print("{v} is power of 2: {is_pow2}\n");
    }

    print("\nDone.\n");
}

Advanced Features

Break Continue 21_break_continue.gx
// Break and Continue example

fn main() {
    // Break: exit loop early
    print("Break test: ");
    for (i = 0:20) {
        if (i > 4) {
            break;
        }
        print("{i} ");
    }
    print("\n");

    // Continue: skip even numbers
    print("Continue test: ");
    for (i = 0:10) {
        if (i % 2 == 0) {
            continue;
        }
        print("{i} ");
    }
    print("\n");

    var a:i32 = 10;
    a++;
    a--;
    ++a;
    --a;
    print("a={a}\n");
    // Break in while loop
    print("While break: ");
    var n:i32 = 0;
    while (n < 100) {
        if (n > 5) {
            break;
        }
        print("{n} ");
        n = n + 1;
    }
    print("\n");

    // Continue in while loop
    print("While continue: ");
    var m:i32 = 0;
    while (m < 10) {
        m = m + 1;
        if (m % 3 == 0) {
            continue;
        }
        print("{m} ");
    }
    print("\n");
}
Match 21_match.gx
// 21_match.ec - Match statement: clean multi-way branching
// Build: ec examples/21_match.ec -o build/21_match.exe

// Test 1: Match on integer with single values
fn test_int() {
    var x:i32 = 2;
    match (x) {
        1: print("one\n")
        2: print("two\n")
        3: print("three\n")
        default: print("other\n")
    }
}

// Test 2: Match with comma-separated patterns
fn test_comma() {
    var code:i32 = 5;
    match (code) {
        1, 2, 3: print("low\n")
        4, 5, 6: print("mid\n")
        7, 8, 9: print("high\n")
        default: print("out of range\n")
    }
}

// Test 3: Match with range
fn test_range() {
    var score:i32 = 85;
    match (score) {
        0..59: print("F\n")
        60..69: print("D\n")
        70..79: print("C\n")
        80..89: print("B\n")
        90..100: print("A\n")
        default: print("invalid\n")
    }
}

// Test 4: Match on enum
enum Color { Red Green Blue }

fn test_enum() {
    var c:Color = Green;
    match (c) {
        Red: print("red\n")
        Green: print("green\n")
        Blue: print("blue\n")
    }
}

// Test 5: Match with block body
fn test_block() {
    var n:i32 = 3;
    match (n) {
        1: {
            print("one ");
            print("(single)\n");
        }
        2, 3: {
            print("two or three ");
            print("(group)\n");
        }
        default: print("other\n")
    }
}

fn main() {
    print("--- int match ---\n");
    test_int();
    print("--- comma match ---\n");
    test_comma();
    print("--- range match ---\n");
    test_range();
    print("--- enum match ---\n");
    test_enum();
    print("--- block match ---\n");
    test_block();
}
Func Ptr 22_func_ptr.gx
// Function pointer validation test

fn add:i32(a:i32, b:i32) {
    return a + b;
}

fn negate:i32(x:i32) {
    return 0 - x;
}

fn apply:i32(f:fn(i32):i32, val:i32) {
    return f(val);
}

fn main() {
    // Valid: assign function matching signature
    var op:fn(i32, i32):i32 = add;
    print("add(3,4) = {op(3, 4)}\n");

    // Valid: pass function pointer as argument
    var result:i32 = apply(negate, 42);
    print("negate(42) = {result}\n");

    // Call through function pointer
    var f:fn(i32):i32 = negate;
    print("f(10) = {f(10)}\n");
}
Increment 23_increment.gx
// Increment and Decrement operators

fn main() {
    // Postfix
    var a:i32 = 5;
    a++;
    print("a++ => {a}\n");

    var b:i32 = 10;
    b--;
    print("b-- => {b}\n");

    var h1:i32 = b<11?10:20;
    print("h={h1}\n");
    // Prefix
    var c:i32 = 0;
    ++c;
    ++c;
    ++c;
    c++;
    --c;
    c--;
    print("++c x3 => {c}\n");

    // Prefix vs postfix in expressions
    var e:i32 = 5;
    var f:i32 = e++;
    print("postfix: e={e}, f={f}\n");

    var g:i32 = 5;
    var h:i32 = ++g;
    print("prefix:  g={g}, h={h}\n");
}
Union 24_union.gx
// Example 24: Union Types
// Unions allow multiple fields to share the same memory — only one is valid at a time.
// Use cases: type punning, variant storage, memory-efficient tagged unions.

// --- Basic union: type punning ---
union IntFloat {
    i: i32
    f: f32
}

// --- Tagged union pattern: manual tag + union ---
enum ValueKind {
    IntVal
    FloatVal
    StrVal
}

union ValueData {
    i: i32
    f: f32
    s: str
}

union Actor {
    id:i32
    name:str
}

struct Value {
    kind: ValueKind
    data: ValueData
}

fn print_value(v: Value) {
    match (v.kind) {
        IntVal:   print("int: {v.data.i}\n")
        FloatVal: print("float: {v.data.f}\n")
        StrVal:   print("str: {v.data.s}\n")
    }
}

// --- Color as packed bytes ---
union Color {
    rgba: u32
    bytes: u8[4]
}

fn main() {
    // Type punning: reinterpret i32 bits as f32
    var pun: IntFloat;
    pun.i = 1065353216;  // IEEE 754 for 1.0f
    print("int bits: {pun.i}\n");
    print("as float: {pun.f}\n");

    // Tagged union: variant storage
    var v1: Value;
    v1.kind = IntVal;
    v1.data.i = 42;
    print_value(v1);

    var v2: Value;
    v2.kind = FloatVal;
    v2.data.f = 3.14;
    print_value(v2);

    var v3: Value;
    v3.kind = StrVal;
    v3.data.s = "hello";
    print_value(v3);

    // Packed color: write as u32, read bytes
    var c: Color;
    c.rgba = 0xFF8040FF;
    print("color rgba: {c.rgba}\n");
}
Fat Str 25_fat_str.gx
// Example 25: Fat Strings (ec_str)
// str is now { ptr, len } — O(1) length, proper comparison, safe iteration.

fn main() {
    // Basic str with .len and .cstr
    var s: str = "hello";
    print("s = {s}\n");
    print("s.len = {s.len}\n");

    // String comparison (uses ec_str_eq, not pointer comparison!)
    var a: str = "hello";
    var b: str = "hello";
    var c: str = "world";

    if (a == b) {
        print("a == b: true (correct!)\n");
    }
    if (a != c) {
        print("a != c: true (correct!)\n");
    }

    // String concatenation
    var greeting: str = "Hello, " + "EC!";
    print("greeting = {greeting}\n");
    print("greeting.len = {greeting.len}\n");

    // String interpolation
    var x: i32 = 42;
    var msg: str = "The answer is {x}";
    print("{msg}\n");

    // Empty string
    var empty: str = "";
    print("empty.len = {empty.len}\n");

    // Escape sequences — sizeof handles them correctly
    var newline: str = "a\nb";
    print("newline.len = {newline.len}\n");

    print("\nAll fat string tests passed!\n");
}
Sort 25_sort.gx
// 25_sort.ec - Sort on all container types: fixed array, slice, array<T>
// Build: ec examples/25_sort.ec -o build/25_sort.exe

fn print_arr(label: str, s: []i32) {
    print("{label}: ");
    for (var x in s) {
        print("{x} ");
    }
    print("\n");
}

fn main() {
    // === Fixed array sort ===
    var fixed:i32[6] = {42, 7, 99, 3, 55, 1};
    print_arr("before fixed", fixed);
    fixed.sort();
    print_arr("after  fixed", fixed);

    // === Slice sort ===
    var data:i32[8] = {80, 20, 60, 10, 90, 40, 70, 30};
    var sl:[]i32 = data[2:6];

    print_arr("before slice", sl);
    sl.sort();

    print_arr("after  slice", sl);
    print_arr("full   data ", data);

    // === Dynamic array sort ===
    var dyn:array<i32>;
    dyn.init();
    dyn.push(33);
    dyn.push(11);
    dyn.push(77);
    dyn.push(22);
    dyn.push(55);

    // print via slice
    var ds:[]i32 = dyn;
    print_arr("before dyn  ", ds);
    dyn.sort();

    var ds2:[]i32 = dyn;
    print_arr("after  dyn  ", ds2);
    dyn.free();
}
Sizeof 26_sizeof.gx
// sizeof expression examples

struct Point {
    x: f32
    y: f32
}

struct Color {
    r: u8
    g: u8
    b: u8
    a: u8
}

fn main() {
    // sizeof with built-in types
    print("sizeof(i8)    = {sizeof(i8)}\n");
    print("sizeof(i16)   = {sizeof(i16)}\n");
    print("sizeof(i32)   = {sizeof(i32)}\n");
    print("sizeof(i64)   = {sizeof(i64)}\n");
    print("sizeof(f32)   = {sizeof(f32)}\n");
    print("sizeof(f64)   = {sizeof(f64)}\n");
    print("sizeof(bool)  = {sizeof(bool)}\n");

    // sizeof with user-defined structs (via expression)
    var p:Point = Point{1.0, 2.0};
    print("sizeof(Point) = {sizeof(p)}\n");

    var c:Color = Color{255, 128, 0, 255};
    print("sizeof(Color) = {sizeof(c)}\n");

    // sizeof with vec types
    print("sizeof(vec2)  = {sizeof(vec2)}\n");
    print("sizeof(vec3)  = {sizeof(vec3)}\n");
    print("sizeof(vec4)  = {sizeof(vec4)}\n");

    // sizeof in expressions
    var arr_size:i64 = 10 * sizeof(i32);
    print("10 * sizeof(i32) = {arr_size}\n");
}
Slice 26_slice.gx
// Example 26: Slices and Safe Arrays
// Arrays automatically have .len and work like slices.
//
// Slice ranges are INCLUSIVE on both ends, matching the loop convention
// `for (i = a:b)`:
//   arr[1:4]   → indices 1, 2, 3, 4   (4 elements)
//   arr[0:0]   → 1 element (just index 0)
//   arr[0:arr.len-1] → the whole array

fn sum:i32(data: []i32) {
    var total: i32 = 0;
    for (var x in data) {
        total += x;
    }
    return total;
}

fn print_slice(data: []i32) {
    print("[");
    for (var x in data) {
        print("{x} ");
    }
    print("]\n");
}

fn main() {
    var arr: i32[5] = {10, 20, 30, 40, 50};

    // Arrays have .len — compile-time constant, no struct needed
    print("arr.len = {arr.len}\n");

    // Arrays implicitly convert to slices at function boundaries
    print("sum(arr) = {sum(arr)}\n");
    print("arr = ");
    print_slice(arr);

    // Subslicing works directly on arrays. Inclusive ranges:
    // arr[1:4] takes indices 1, 2, 3, 4 → 4 elements.
    var mid: []i32 = arr[1:4];
    print("arr[1:4] = ");
    print_slice(mid);
    print("mid.len = {mid.len}\n");

    // Slices work the same way
    var s: []i32 = arr;
    print("s[0] = {s[0]}\n");
    print("s.len = {s.len}\n");

    // Subslice of subslice — mid[0:2] takes indices 0, 1, 2 → 3 elements
    var inner: []i32 = mid[0:2];
    print("inner = ");
    print_slice(inner);

    print("\nAll tests passed!\n");
}
Bounds Check 27_bounds_check.gx
// Example 27: Bounds Checking
// Out-of-bounds access panics in debug mode (default).
// Use -O1 or higher to disable checks for release builds.

fn main() {
    // Fixed array bounds check
    var arr: i32[3] = {10, 20, 30};
    print("arr[0] = {arr[0]}\n");
    print("arr[2] = {arr[2]}\n");
    print("arr.len = {arr.len}\n");

    // Slice bounds check
    var s: []i32 = arr;
    print("s[0] = {s[0]}\n");
    print("s[2] = {s[2]}\n");

    // This will PANIC:
    print("About to access s[10]...\n");
    print("s[10] = {s[10]}\n");
    print("This should never print!\n");
}
Const 28_const.gx
// Example 28: Const declarations and constant folding

// Global constants (no type annotation - inferred)
const PI = 3.14159265358979;
const TAU = PI * 2.0;
const SCREEN_WIDTH = 1920;
const SCREEN_HEIGHT = 1080;
const ASPECT_RATIO = 16.0 / 9.0;

// Global constants with type annotation
const MAX_ENTITIES:i32 = 1024;
const BUFFER_SIZE:i64 = MAX_ENTITIES * 64;

// Const expressions with bitwise ops
const FLAG_READ = 1;
const FLAG_WRITE = 2;
const FLAG_EXEC = 4;
const FLAG_ALL = FLAG_READ | FLAG_WRITE | FLAG_EXEC;

// Boolean consts
const DEBUG_MODE = false;
const IS_WIDE = SCREEN_WIDTH > 1000;

// Const arrays — explicit size or inferred with []
const PRIMES:i32[5] = { 2, 3, 5, 7, 11 };
const COLORS:str[]  = { "red", "green", "blue" };  // size inferred to 3
const FIB:i64[]     = { 1, 1, 2, 3, 5, 8, 13, 21 };


fn main() {
    print("=== Const Declarations ===");
    // Using global consts
    print("PI = {PI}");
    print("TAU = {TAU}");
    print("Screen: {SCREEN_WIDTH}x{SCREEN_HEIGHT}");
    print("Aspect ratio: {ASPECT_RATIO}");
    print("Max entities: {MAX_ENTITIES}");
    print("Buffer size: {BUFFER_SIZE}");

    print("");
    print("=== Const Folding ===");

    // Const folding: these should be computed at compile time
    const AREA = SCREEN_WIDTH * SCREEN_HEIGHT;
    print("Screen area: {AREA}");

    const HALF_PI = PI / 2.0;
    print("PI/2 = {HALF_PI}");

    // Bitwise const
    print("FLAG_ALL = {FLAG_ALL}");

    // Boolean const
    print("Debug mode: {DEBUG_MODE}");
    print("Is wide: {IS_WIDE}");

    print("");
    print("=== Const in Expressions ===");

    // Const used in regular expressions
    var x = SCREEN_WIDTH / 2;
    var y = SCREEN_HEIGHT / 2;
    print("Center: ({x}, {y})");

    // Const in loop bounds
    const LOOP_COUNT = 5;
    for (i=0:LOOP_COUNT) {
        print("  i = {i}");
    }

    print("");
    print("=== Negative and Unary ===");
    const NEG_ONE = -1;
    const NOT_FLAG = ~FLAG_READ;
    print("NEG_ONE = {NEG_ONE}");
    print("~FLAG_READ = {NOT_FLAG}");

    print("");
    print("=== Const Arrays ===");
    print("First prime: {PRIMES[0]}");
    print("Last color:  {COLORS[2]}");
    print("FIB[7] = {FIB[7]}");

    print("Done!");
}

Compile-Time

Comptime If 29_comptime_if.gx
// Example 29: Compile-time conditional compilation (#if)

// Platform detection with @os
#if (@os == "windows") {
    const PLATFORM = "Windows";
}
#else {
    const PLATFORM = "Other";
}

fn main() {
    print("Platform: {PLATFORM}");

    // Statement-level #if with @os
    #if (@os == "windows") {
        print("Running on Windows");
    }
    #else {
        print("Running on non-Windows");
    }

    // #if with user const
    const ENABLE_FEATURE = true;
    #if (ENABLE_FEATURE) {
        print("Feature enabled");
    }

    // @debug check (false unless --debug flag)
    #if (@debug) {
        print("Debug mode ON");
    }
    #else {
        print("Debug mode OFF");
    }

    // @opt level check
    #if (@opt > 0) {
        print("Optimized build");
    }
    #else {
        print("Unoptimized build");
    }

    print("Done!");
}
Comptime Fn 30_comptime_fn.gx
// Example 30: Compile-time functions (#fn)

// Align a size up to the given alignment
#fn align_up:i32(size:i32, align:i32) {
    return (size + align - 1) & ~(align - 1);
}

// Create a bitmask with N bits set
#fn make_bitmask:i32(bits:i32) {
    if (bits <= 0) { return 0; }
    if (bits >= 32) { return -1; }
    return (1 << bits) - 1;
}

// Compile-time fibonacci
#fn fib:i32(n:i32) {
    if (n <= 1) { return n; }
    return fib(n - 1) + fib(n - 2);
}

// Compile-time max
#fn max:i32(a:i32, b:i32) {
    if (a > b) { return a; }
    return b;
}

// Compile-time power
#fn pow:i32(base:i32, exp:i32) {
    var result:i32 = 1;
    var i:i32 = 0;
    while (i < exp) {
        result = result * base;
        i = i + 1;
    }
    return result;
}

// All evaluated at compile time — no runtime computation!
const BUF_SIZE = align_up(100, 64);
const CHANNEL_MASK = make_bitmask(8);
const PIXEL_MASK = make_bitmask(24);
const FIB_10 = fib(10);
const MAX_DIM = max(1920, 1080);
const TWO_TO_10 = pow(2, 10);
const COMBINED = align_up(FIB_10, 16);

fn main() {
    print("=== Compile-Time Functions (#fn) ===");
    print("align_up(100, 64) = {BUF_SIZE}");
    print("make_bitmask(8)   = {CHANNEL_MASK}");
    print("make_bitmask(24)  = {PIXEL_MASK}");
    print("fib(10)           = {FIB_10}");
    print("max(1920, 1080)   = {MAX_DIM}");
    print("pow(2, 10)        = {TWO_TO_10}");
    print("align_up(fib(10), 16) = {COMBINED}");
    print("Done!");
}
Comptime For 31_comptime_for.gx
// Example 31: Compile-time loop unrolling with #for
// #for unrolls the loop at compile time, substituting the iteration
// variable with literal integer values. No runtime loop overhead.

fn main() {
    // Basic #for — unrolls to 5 print calls
    print("=== Compile-Time Loop Unrolling (#for) ===");

    #for (i=0:5) {
        print("iteration {i}");
    }

    // #for with expressions using the loop variable
    var sum:i32 = 0;
    #for (i=1:6) {
        sum += i * i;
    }
    print("sum of squares 1..5 = {sum}");

    // Nested #for — generates 3x3 = 9 iterations
    #for (row=0:3) {
        #for (col=0:3) {
            var val:i32 = row * 3 + col;
            print("  ({row},{col}) = {val}");
        }
    }

    // #for with const bounds
    const N = 4;
    #for (i=0:N) {
        print("channel {i}");
    }

    print("Done!");
}
Struct Comptime 32_struct_comptime.gx
// Example 32: Struct and Enum level comptime expansion (Phase 5)

// Struct with conditional fields based on platform.
//
// PlatformInfo has different fields depending on @os: on Windows it
// gets (handle, registry_key); elsewhere it gets (fd, path). The
// expansion happens at compile time during the comptime pre-pass,
// before the resolver runs.
//
// Note: comptime statement-level #if inside fn bodies runs in the
// post-pass (after type checking), so main() below only touches the
// unconditional fields (name, version). Reading or writing the
// platform-specific fields directly would force the type checker to
// see both branches of an #if and reject the inactive one. The struct
// expansion itself works fine — it's just the access pattern in
// fn-body code that has to stay platform-agnostic.
struct PlatformInfo {
    name: str
    #if (@os == "windows") {
        handle: i64
        registry_key: str
    } #else {
        fd: i32
        path: str
    }
    version: i32
}

// Struct with generated fields via #for
struct Pixel {
    #for (i=0:4) {
        channel_i: f32
    }
}

// Enum with conditional members
enum LogLevel {
    Error
    Warning
    Info
    #if (@debug) {
        Debug
        Trace
    }
}

fn main() {
    print("=== Struct/Enum Comptime Expansion (Phase 5) ===");

    // PlatformInfo: only access fields that are unconditional.
    var info: PlatformInfo;
    info.name = "test";
    info.version = 1;
    print("name = {info.name}");
    print("version = {info.version.str()}");

    // Pixel has channel_0 through channel_3 (generated by #for)
    var px: Pixel;
    px.channel_0 = 1.0;
    px.channel_1 = 0.5;
    px.channel_2 = 0.25;
    px.channel_3 = 1.0;
    print("pixel = ({px.channel_0.str()}, {px.channel_1.str()}, {px.channel_2.str()}, {px.channel_3.str()})");

    // LogLevel enum — Debug/Trace only exist in debug builds
    var level: LogLevel = Error;
    print("log level = {level.str()}");

    print("Done!");
}
Reflection 33_reflection.gx
// Example 33: Compile-time reflection (Phase 6)

struct Point {
    x: f32
    y: f32
    z: f32
}

struct Node {
    value: i32
    next: *Node
}

enum Color { Red  Green  Blue }

fn main() {
    print("=== Compile-Time Reflection (Phase 6) ===\n");

    // --- Struct field reflection ---
    print("Point has {@fields(Point)} fields:\n");
    #for (i=0:@fields(Point)) {
        print("  {@field(Point, i)}: {@field_type(Point, i)}\n");
    }

    // --- Field detail intrinsics ---
    print("Node has {@fields(Node)} fields:");
    #for (i=0:@fields(Node)) {
        print("  {@field(Node, i)}: ptr={@field_is_ptr(Node, i)}\n");
    }

    // --- Enum member reflection ---
    print("Color has {@members(Color)} members:\n");
    #for (i=0:@members(Color)) {
        print("  {@member(Color, i)}");
    }

    // --- Type meta ---
    print("@type_kind(Point) = {@type_kind(Point)}\n");
    print("@type_kind(Color) = {@type_kind(Color)}\n");
    print("@type_name(Point) = {@type_name(Point)}\n");

    // Note: Function reflection (@params, @param, @return_type) works
    // but requires multi-file module setup (single-file function
    // declarations have a pre-existing resolver limitation)

    print("Done!");
}
Build Directives 34_build_directives.gx
// Example 34: Build directives (Phase 7)
// @link, @cflags, @ldflags make .ec files self-contained.

// Build directives — these inject linker/compiler flags without CLI args
@link("ucrtbase")

// Platform-conditional linking via #if
#if (@os == "windows") {
    @link("kernel32")
}

fn main() {
    print("=== Build Directives (Phase 7) ===");
    print("@link and @cflags are collected from source");
    print("No CLI flags needed for linking!");
    print("Done!");


}
Templates 35_templates.gx
// Example 35: Compile-time templates (Phase 8)
// #type T declares a type parameter — monomorphized at compile time.

#type T
struct Pair {
    first: T
    second: T
}

fn main() {
    print("=== Compile-Time Templates (Phase 8) ===");

    // Pair<i32> — generates struct Pair_i32 { int32_t first; int32_t second; }
    var pi: Pair<i32>;
    pi.first = 10;
    pi.second = 20;
    print("Pair<i32>: ({pi.first.str()}, {pi.second.str()})");

    // Pair<i64> — generates struct Pair_i64 { int64_t first; int64_t second; }
    var pl: Pair<i64>;
    pl.first = 100;
    pl.second = 200;
    print("Pair<i64>: ({pl.first.str()}, {pl.second.str()})");

    var pf: Pair<f32>;
    pf.first = 100.32;
    pf.second = 200.32;
    print("Pair<f32>: ({pf.first.str()}, {pf.second.str()})");

    var ps: Pair<str>;
    ps.first = "Afan";
    ps.second = "Olovcic";
    print("Pair<f32>: ({ps.first.str()}, {ps.second.str()})");
    {
        var ps:Pair< str >;
        ps.first = "Sanela";
        ps.second = "Gluhovic";
        print("Pair<f32>: ({ps.first.str()}, {ps.second.str()})");
    }
    print("Done!");
}

Standard Library

Pointers 36_pointers.gx
// Example 36: Pointer arithmetic

fn main() {
    print("=== Pointer Arithmetic ===");

    // Basic dereference
    var x:i32 = 42;
    var p:*i32 = &x;
    print("*p = {(*p).str()}");
    *p = 99;
    print("after *p=99: x = {x.str()}#\n");

    // Pointer arithmetic: take address, add offset
    var a:i32 = 10;
    var b:i32 = 20;
    var c:i32 = 30;

    a+=10;
    a++;

    a+=0xFF;

    var pa:*i32 = &a;
    // Test pointer + int type
    var pb:*i32 = pa + 1;
    print("pa+1 deref = {(*pb).str()}");

    // Pointer difference
    var diff:i64 = pb - pa;
    print("pb - pa = {diff.str()}");

    // Pointer indexing
    print("pa[0] = {pa[0].str()}");

    print("Done!");
}
Main Args 37_main_args.gx
// Example 37: Main with string arguments
// Usage: gx 37_main_args.gx -o args.exe && args.exe hello world

fn main(args: str[]) {
    print("Got {args.len} arguments:\n");
    for (var arg in args) {
        print("  - {arg}\n");
    }
}
Par For 37_par_for.gx
// Example 37: Parallel For (par for)
// Uses OpenMP under the hood — requires -O1+ (clang/gcc) for actual parallelism
// With TCC (default), runs sequentially (graceful degradation)

fn main() {
    var data:i32[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Parallel for-range: each iteration is independent
    par for (i = 0:9) {
        data[i] = data[i] * 2;
        print("{data[i]}\n");
    }

    // Print results
    /*for (i = 0:9) {
        print("{data[i]}\n");
    }*/
}
Strings 38_strings.gx
// 38_strings.gx — String method tests

fn main() {
    var s = "  Hello, World!  "

    // ── trim ─────────────────────────────────
    var trimmed = s.trim()
    var trim_l = s.trim_left()
    var trim_r = s.trim_right()
    print("trim:       '" + trimmed + "'\n")
    print("trim_left:  '" + trim_l + "'\n")
    print("trim_right: '" + trim_r + "'\n")

    var hw = s.trim()

    // ── find / contains ──────────────────────
    var needle1 = "World"
    var needle2 = "xyz"
    var needle3 = "l"
    print("find World:     {hw.find(needle1)}\n")
    print("find xyz:       {hw.find(needle2)}\n")
    print("find_last l:    {hw.find_last(needle3)}\n")
    print("contains Hello: {hw.contains(needle1)}\n")
    print("contains xyz:   {hw.contains(needle2)}\n")

    // ── starts_with / ends_with ──────────────
    var prefix = "Hello"
    var suffix = "!"
    print("starts_with Hello: {hw.starts_with(prefix)}\n")
    print("ends_with !:       {hw.ends_with(suffix)}\n")

    // ── sub ──────────────────────────────────
    var sub1 = hw.sub(0, 5)
    var sub2 = hw.sub(7)
    print("sub(0,5): '" + sub1 + "'\n")
    print("sub(7):   '" + sub2 + "'\n")

    // ── upper / lower ────────────────────────
    var up = hw.upper()
    var lo = hw.lower()
    print("upper: '" + up + "'\n")
    print("lower: '" + lo + "'\n")

    // ── replace ──────────────────────────────
    var old_s = "World"
    var new_s = "GX"
    var rep = hw.replace(old_s, new_s)
    print("replace: '" + rep + "'\n")

    // ── count ────────────────────────────────
    var cnt = hw.count(needle3)
    print("count l: {cnt}\n")

    // ── repeat ───────────────────────────────
    var dash_str = "-"
    var dash = dash_str.repeat(20)
    print(dash + "\n")

    // ── split ────────────────────────────────
    var csv = "one,two,three,four"
    var delim = ","
    var parts = csv.split(delim)
    print("split count: {parts.len}\n")
    for (var p in parts) {
        print("  part: '" + p + "'\n")
    }

    // ── chaining ─────────────────────────────
    var input_str = "  HELLO world  "
    var hello = "hello"
    var hi = "hi"
    var result = input_str.trim().lower().replace(hello, hi)
    print("chained: '" + result + "'\n")

    // ── string interpolation to var ──────────
    var name = "GX"
    var greeting:str = "Hello {name}!"
    print(greeting + "\n")
}
Io 39_io.gx
// 39_io.gx - Test the io module

import io.io
import io.file
import io.fs
import io.path

fn main() {
    // --- Console I/O ---
    println("=== Console I/O ===")
    println("Hello from println!")
    eprintln("This goes to stderr")

    // --- Path utilities ---
    println("\n=== Path Utilities ===")

    var p = "src/frontend/parser/parser.cpp"
    print("  path:      ") println(p)
    print("  dirname:   ") println(dirname(p))
    print("  basename:  ") println(basename(p))
    print("  extension: ") println(extension(p))
    print("  stem:      ") println(stem(p))

    var win = "C:\\Users\\test\\file.txt"
    print("  win path:  ") println(win)
    print("  dirname:   ") println(dirname(win))
    print("  basename:  ") println(basename(win))

    print("  is_absolute('/usr/bin'):  ")
    if (is_absolute("/usr/bin")) { println("true") } else { println("false") }

    print("  is_absolute('C:\\foo'):    ")
    if (is_absolute("C:\\foo")) { println("true") } else { println("false") }

    print("  is_absolute('relative'):  ")
    if (is_absolute("relative")) { println("true") } else { println("false") }

    print("  join('foo','bar'):        ") println(join("foo", "bar"))
    print("  join('foo/','bar'):       ") println(join("foo/", "bar"))

    // --- Filesystem ---
    println("\n=== Filesystem ===")

    print("  exists('examples/39_io.gx'):  ")
    if (exists("examples/39_io.gx")) { println("true") } else { println("false") }

    print("  is_file('examples/39_io.gx'): ")
    if (is_file("examples/39_io.gx")) { println("true") } else { println("false") }

    print("  is_dir('examples'):           ")
    if (is_dir("examples")) { println("true") } else { println("false") }

    print("  is_file('nonexistent'):       ")
    if (is_file("nonexistent")) { println("true") } else { println("false") }

    var sz = file_size("examples/39_io.gx")
    print("  file_size: ") print("{sz}") println(" bytes")

    // --- File I/O ---
    println("\n=== File I/O ===")

    // Write a test file
    var ok = write_all("_test_io.txt", "Hello from GX io module!\nLine 2\n")
    print("  write_all: ") if (ok) { println("ok") } else { println("failed") }

    // Read it back
    var content = read_all("_test_io.txt")
    print("  read_all:  ") print(content)

    // Append to it
    ok = append_all("_test_io.txt", "Line 3 appended\n")
    print("  append:    ") if (ok) { println("ok") } else { println("failed") }

    // Read again
    content = read_all("_test_io.txt")
    print("  after append:\n") print(content)

    // Low-level file API
    var f = file_open("_test_io.txt", "r")
    if (f != 0) {
        var pos = file_tell(f)
        print("  tell at start: ") println("{pos}")
        file_close(f)
    }

    // Clean up test file
    ok = remove_file("_test_io.txt")
    print("  remove:    ") if (ok) { println("ok") } else { println("failed") }

    print("  exists after remove: ")
    if (exists("_test_io.txt")) { println("true") } else { println("false") }

    // make_dir / remove_dir
    ok = make_dir("_test_dir")
    print("\n  make_dir:  ") if (ok) { println("ok") } else { println("failed") }
    print("  is_dir:    ") if (is_dir("_test_dir")) { println("true") } else { println("false") }
    ok = remove_dir("_test_dir")
    print("  remove_dir:") if (ok) { println("ok") } else { println("failed") }

    println("\n=== Done ===")
}
Random 40_random.gx
// 40_random.gx - Test random and noise modules

import random.random
import random.noise

fn main() {
    // --- Random numbers ---
    print("=== Random (default seed) ===\n")
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")
    print("  rand_range(10,20): ") print("{rand_range(10, 20)}") print("\n")
    print("  rand_f32():        ") print("{rand_f32()}") print("\n")
    print("  rand_bool():       ")
    if (rand_bool()) { print("true\n") } else { print("false\n") }

    // --- Seeded (deterministic) ---
    print("\n=== Seeded (42) ===\n")
    seed(42)
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")

    // Same seed = same sequence
    print("\n=== Seeded (42) again ===\n")
    seed(42)
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")
    print("  rand_int(100):     ") print("{rand_int(100)}") print("\n")

    // --- Float range ---
    print("\n=== Float range ===\n")
    seed(123)
    var i:i32 = 0
    while (i < 5) {
        var v:f32 = rand_f32_range(-1.0, 1.0)
        print("  rand_f32_range(-1,1): ") print("{v}") print("\n")
        i = i + 1
    }

    // --- Simplex noise ---
    print("\n=== Simplex Noise ===\n")
    print("  simplex1(0.5):           ") print("{simplex1(0.5)}") print("\n")
    print("  simplex1(1.0):           ") print("{simplex1(1.0)}") print("\n")
    print("  simplex1(1.5):           ") print("{simplex1(1.5)}") print("\n")

    print("  simplex2(0.5, 0.5):      ") print("{simplex2(0.5, 0.5)}") print("\n")
    print("  simplex2(1.0, 2.0):      ") print("{simplex2(1.0, 2.0)}") print("\n")

    print("  simplex3(0.5, 0.5, 0.5): ") print("{simplex3(0.5, 0.5, 0.5)}") print("\n")
    print("  simplex4(1,2,3,4):       ") print("{simplex4(1.0, 2.0, 3.0, 4.0)}") print("\n")

    // --- fBm ---
    print("\n=== Fractal Brownian Motion ===\n")
    print("  fbm2(0.5, 0.5, 4, 2.0, 0.5): ") print("{fbm2(0.5, 0.5, 4, 2.0, 0.5)}") print("\n")
    print("  fbm3(1, 2, 3, 6, 2.0, 0.5):  ") print("{fbm3(1.0, 2.0, 3.0, 6, 2.0, 0.5)}") print("\n")

    // --- 2D noise map (ASCII) ---
    print("\n=== 2D Noise Map (10x5) ===\n")
    var y:i32 = 0
    while (y < 5) {
        var x:i32 = 0
        while (x < 20) {
            var n:f32 = simplex2(x * 0.2, y * 0.2)
            if (n > 0.3)       { print("#") }
            else if (n > 0.0)  { print("+") }
            else if (n > -0.3) { print(".") }
            else               { print(" ") }
            x = x + 1
        }
        print("\n")
        y = y + 1
    }

    print("\n=== Done ===\n")
}
Out Params 41_out_params.gx
// Example 41: Out Parameters & Const-by-Default Pointers
//
// GX pointer parameters are read-only (const) by default.
// Use 'out' to mark parameters that the function will modify.
// GX functions cannot return pointers — use 'out' params instead.

struct Point {
    x: f32
    y: f32
}

struct Result {
    value: f32
    ok: bool
}

// 'out' pointer param: function CAN modify what the pointer points to
fn init_point(out p: *Point, x: f32, y: f32) {
    p.x = x
    p.y = y
}

fn scale_point(out p: *Point, factor: f32) {
    p.x = p.x * factor
    p.y = p.y * factor
}

// No 'out': pointer is read-only (const Point* in C)
fn print_point(p: *Point) {
    print("({p.x.str()}, {p.y.str()})\n")
    // p.x = 99.0  ← compile error: Cannot write through read-only pointer
}

// out value param: multi-return pattern
// 'out result:f32' becomes 'float*' in C
fn divide:bool(a: f32, b: f32, out result: f32) {
    if (b == 0.0) { return false }
    *result = a / b
    return true
}

// out struct param: fill a result struct
fn compute(a: f32, b: f32, out r: *Result) {
    if (b == 0.0) {
        r.ok = false
        r.value = 0.0
    } else {
        r.ok = true
        r.value = a / b
    }
}

fn main() {
    // === Out pointer param: init and modify ===
    var pt = Point{0.0, 0.0}
    init_point(&pt, 3.0, 4.0)
    print("After init: ")
    print_point(&pt)

    scale_point(&pt, 2.0)
    print("After scale: ")
    print_point(&pt)

    // === Out value param (multi-return) ===
    var result: f32 = 0.0
    if (divide(10.0, 3.0, &result)) {
        print("10 / 3 = {result.str()}\n")
    }
    if (!divide(5.0, 0.0, &result)) {
        print("Division by zero caught!\n")
    }

    // === Out struct param ===
    var r = Result{0.0, false}
    compute(10.0, 4.0, &r)
    if (r.ok) {
        print("10 / 4 = {r.value.str()}\n")
    }
}
Typedef 42_typedef.gx
// Example 42: Type Aliases
//
// `type NewName:ExistingType` creates a type alias.
// The alias is interchangeable with the original type.
// Emits a C `typedef` — zero runtime cost.

// Alias to built-in types
type byte:u8
type size:u64
type real:f32

// Alias to pointer type
type cstring:*c_char

struct Point {
    x: real
    y: real
}

type Vec2D:Point

fn distance:real(a: *Vec2D, b: *Vec2D) {
    var dx:real = b.x - a.x
    var dy:real = b.y - a.y
    return dx * dx + dy * dy
}

fn main() {
    var b:byte = 255
    var s:size = 1024
    var r:real = 3.14

    print("byte: {b.str()}\n")
    print("size: {s.str()}\n")
    print("real: {r.str()}\n")

    var p1 = Vec2D{1.0, 2.0}
    var p2 = Vec2D{4.0, 6.0}
    var d = distance(&p1, &p2)
    print("distance^2: {d.str()}\n")
}
Microaudio 44_microaudio.gx
// Example 44: Microaudio
//
// Minimal audio playback using the microaudio module.
// Loads and plays WAV files.
//
// Run: gx examples/44_microaudio.gx -I modules -o build/audio_test.exe

import microaudio.audio

fn main() {
    audio_init()
    defer audio_shutdown()

    // Load a WAV file (place a .wav file next to the executable)
    var snd = audio_load("test.wav")
    if (snd < 0) {
        print("Could not load test.wav\n")
        print("Place a WAV file named test.wav in the working directory.\n")
        return
    }

    print("Sound loaded (id={snd.str()})\n")

    // Set volume to 80%
    audio_set_volume(snd, 0.8)

    // Play the sound
    audio_play(snd)
    print("Playing... press Enter to stop.\n")

    input()

    audio_stop(snd)
    audio_free(snd)
    print("Done.\n")
}
Json 45_json.gx
// 45_json.gx - JSON module example

// Demonstrates parsing, querying, building, and serializing JSON

import json

fn main() {
    // ---- Parsing JSON ----
    var text = "{\"name\": \"Alice\", \"age\": 30, \"scores\": [95, 87, 92], \"active\": true}"

    var doc = json.gx_json_parse(text.cstr, text.len, 0)
    if (doc == 0) {
        print("Failed to parse JSON\n")
        return
    }
    defer json.gx_json_free(doc)

    var r = json.gx_json_root(doc)

    // ---- Reading values ----
    var name = json.gx_json_get_str(json.gx_json_obj_get(r, "name"))
    var age  = json.gx_json_get_int(json.gx_json_obj_get(r, "age"))
    var active = json.gx_json_get_bool(json.gx_json_obj_get(r, "active"))
    print("Name: {name}\n")
    print("Age:  {age}\n")
    print("Active: {active}\n")

    // ---- Array access ----
    var scores = json.gx_json_obj_get(r, "scores")
    print("Scores:\n")
    // Array access by index
    var s0 = json.gx_json_get_int(json.gx_json_arr_get(scores, 0))
    var s1 = json.gx_json_get_int(json.gx_json_arr_get(scores, 1))
    var s2 = json.gx_json_get_int(json.gx_json_arr_get(scores, 2))
    print("  [0] = {s0}\n")
    print("  [1] = {s1}\n")
    print("  [2] = {s2}\n")

    // ---- Type inspection ----
    var name_type = json.gx_json_type_desc(json.gx_json_obj_get(r, "name"))
    var age_type = json.gx_json_type_desc(json.gx_json_obj_get(r, "age"))
    var scores_type = json.gx_json_type_desc(scores)
    print("Type of name: {name_type}\n")
    print("Type of age:  {age_type}\n")
    print("Type of scores: {scores_type}\n")

    // ---- Serialize back (pretty) ----
    var pretty = json.gx_json_write(doc, json.WRITE_PRETTY)
    print("\nPretty printed:\n{pretty}\n")

    // ---- Build JSON from scratch ----
    print("--- Building JSON ---\n")
    var mdoc = json.gx_json_new_doc()
    defer json.gx_json_mut_free(mdoc)

    var obj = json.gx_json_new_obj(mdoc)
    json.gx_json_set_root(mdoc, obj)

    json.gx_json_obj_add_str(mdoc, obj, "language", "GX")
    json.gx_json_obj_add_int(mdoc, obj, "version", 2)
    json.gx_json_obj_add_bool(mdoc, obj, "fast", true)
    json.gx_json_obj_add_float(mdoc, obj, "pi", 3.14159)

    // Nested array
    var arr = json.gx_json_obj_add_arr(mdoc, obj, "features")
    json.gx_json_arr_add_str(mdoc, arr, "transpiles to C")
    json.gx_json_arr_add_str(mdoc, arr, "zero-cost abstractions")
    json.gx_json_arr_add_str(mdoc, arr, "yyjson powered")

    // Nested object
    var meta = json.gx_json_obj_add_obj(mdoc, obj, "meta")
    json.gx_json_obj_add_str(mdoc, meta, "author", "GX Team")
    json.gx_json_obj_add_int(mdoc, meta, "year", 2026)

    var result = json.gx_json_mut_write(mdoc, json.WRITE_PRETTY)
    print("{result}\n")
}
Time 46_time.gx
// 46_time.gx - Time module example
// Demonstrates clock, date/time, formatting, benchmarking, and sleep

import time

fn main() {
    // ---- Current date and time ----
    var now = time.gx_time_unix()
    var y = time.gx_time_year(now)
    var mo = time.gx_time_month(now)
    var d = time.gx_time_day(now)
    var h = time.gx_time_hour(now)
    var mi = time.gx_time_minute(now)
    var s = time.gx_time_second(now)
    print("Current local time: {y}-{mo}-{d} {h}:{mi}:{s}\n")

    // UTC equivalent
    var uy = time.gx_time_utc_year(now)
    var umo = time.gx_time_utc_month(now)
    var ud = time.gx_time_utc_day(now)
    var uh = time.gx_time_utc_hour(now)
    print("Current UTC time:   {uy}-{umo}-{ud} {uh}:{mi}:{s}\n")

    // ---- Formatting ----
    var formatted = time.gx_time_format(now, "%Y-%m-%d %H:%M:%S")
    print("Formatted (local): {formatted}\n")

    var formatted_utc = time.gx_time_format_utc(now, "%Y-%m-%d %H:%M:%S")
    print("Formatted (UTC):   {formatted_utc}\n")

    // Day of week
    var wd = time.gx_time_weekday(now)
    var day_name = time.gx_time_format(now, "%A")
    print("Weekday: {wd} ({day_name})\n")

    // ---- Benchmarking ----
    print("\n--- Benchmark ---\n")
    var t0 = time.gx_time_now_ns()

    // Do some work
    var sum = 0
    for (i = 0 : 999999) {
        sum = sum + i
    }

    var t1 = time.gx_time_now_ns()
    var ms = time.gx_time_elapsed_ms(t0, t1)
    var us = time.gx_time_elapsed_us(t0, t1)
    print("Sum of 0..999999 = {sum}\n")
    print("Elapsed: {ms} ms ({us} us)\n")

    // ---- Sleep ----
    print("\n--- Sleep test ---\n")
    var s0 = time.gx_time_now_ns()
    time.gx_time_sleep_ms(100)
    var s1 = time.gx_time_now_ns()
    var slept = time.gx_time_elapsed_ms(s0, s1)
    print("Slept ~100ms, measured: {slept} ms\n")

    // ---- Build timestamp ----
    var epoch = time.gx_time_make(2026, 1, 1, 0, 0, 0)
    var new_year = time.gx_time_format(epoch, "%Y-%m-%d %H:%M:%S (%A)")
    print("\nNew Year 2026: {new_year}\n")
}
Xml 47_xml.gx
// 47_xml.gx - XML parsing, navigation, and building example
// Build: gx 47_xml.gx -I modules -o build/47_xml.exe

import xml

fn main() {
    // ---- Parse XML from string ----
    var data = "<library name='City Library'><book id='1'><title>The Rust Programming Language</title><author>Steve Klabnik</author><year>2019</year></book><book id='2'><title>The C Programming Language</title><author>Kernighan and Ritchie</author><year>1978</year></book><book id='3'><title>Structure and Interpretation</title><author>Abelson and Sussman</author><year>1996</year></book></library>"

    var doc = xml.gx_xml_parse(data.cstr, data.len)
    if (xml.gx_xml_has_error(doc)) {
        print("Parse error: {xml.gx_xml_error(doc)}\n")
        xml.gx_xml_free(doc)
        return
    }

    // Root element
    print("Root tag: {xml.gx_xml_name(doc)}\n")

    var lib_name = xml.gx_xml_attr(doc, "name")
    print("Library: {lib_name}\n")

    var num_books = xml.gx_xml_child_count(doc, "book")
    print("Book count: {num_books}\n\n")

    // Iterate children with same tag name
    var book = xml.gx_xml_child(doc, "book")
    while (book != 0) {
        var id = xml.gx_xml_attr(book, "id")
        var title = xml.gx_xml_txt(xml.gx_xml_child(book, "title"))
        var author = xml.gx_xml_txt(xml.gx_xml_child(book, "author"))
        var year = xml.gx_xml_txt(xml.gx_xml_child(book, "year"))
        print("Book {id}: {title} by {author} ({year})\n")
        book = xml.gx_xml_next(book)
    }

    // ---- Build XML from scratch ----
    print("\n--- Building XML ---\n")
    var root = xml.gx_xml_new("config")
    xml.gx_xml_set_attr(root, "version", "1.0")

    var window = xml.gx_xml_add_child(root, "window")
    xml.gx_xml_set_attr(window, "width", "1280")
    xml.gx_xml_set_attr(window, "height", "720")
    xml.gx_xml_set_txt(window, "Main Window")

    var gfx = xml.gx_xml_add_child(root, "graphics")
    xml.gx_xml_set_attr(gfx, "backend", "opengl")

    var res = xml.gx_xml_add_child(gfx, "resolution")
    xml.gx_xml_set_txt(res, "1920x1080")

    var aa = xml.gx_xml_add_child(gfx, "antialiasing")
    xml.gx_xml_set_txt(aa, "4x MSAA")

    // Serialize to string
    var xml_str = xml.gx_xml_to_str(root)
    print("{xml_str}\n")

    xml.gx_xml_free_str(xml_str)
    xml.gx_xml_free(root)
    xml.gx_xml_free(doc)
}

External Libraries

Enet 50_enet.gx
// 01_hello.gx — minimal ENet host/client smoke test in one process.
//
// Spins up a server and a client on localhost:7777, exchanges a single
// reliable packet, then tears down.
//
// Build: gx examples/enet/01_hello.gx -I modules -o build/enet_hello.exe
// Run:   ./build/enet_hello.exe
//
// FRICTION LOG (this file):
//   #3: RESOLVED — pointer cast syntax `(*void)expr` now lets us pass
//       cstr buffers to const *void parameters directly.
//   #4: RESOLVED — c_int and i32 are now mutually coercible at every site
//       (assignment, comparison, arithmetic), so binding sigs can use
//       either; the i32 declarations here remain for clarity.
//   #7: GX's `fn main()` still lowers to non-void C main; bare `return`
//       is still a clang error under -Wreturn-mismatch. Workaround
//       (single-bottom-exit, no early returns from main) preserved.
//   #8: RESOLVED — `null` literal is now a first-class language token.

import enet

fn main() {
    var ok: bool = true

    // ---- Init ----
    if (enet_initialize() != 0) {
        print("ENet init FAILED\n")
        ok = false
    }
    if (ok) { print("ENet init OK\n") }

    // Friction #8 RESOLVED: `null` literal is now in the language.
    var server: *void = null
    var client: *void = null
    var peer:   *void = null

    if (ok) {
        var server_addr: ENetAddress
        server_addr.host = ENET_HOST_ANY
        server_addr.port = 7777
        server = enet_host_create(&server_addr, 4, 2, 0, 0)
        if (server == null) {
            print("server create FAILED\n")
            ok = false
        }
    }
    if (ok) { print("server listening on port 7777\n") }

    if (ok) {
        client = enet_host_create(0, 1, 2, 0, 0)
        if (client == null) {
            print("client create FAILED\n")
            ok = false
        }
    }

    if (ok) {
        var connect_addr: ENetAddress
        enet_address_set_host(&connect_addr, "127.0.0.1")
        connect_addr.port = 7777
        peer = enet_host_connect(client, &connect_addr, 2, 0)
        if (peer == null) {
            print("client connect FAILED\n")
            ok = false
        }
    }
    if (ok) { print("client dialing 127.0.0.1:7777...\n") }

    // ---- Pump until handshake completes ----
    var ev: ENetEvent
    var connected: bool = false
    var iters: i32 = 0
    while (ok && !connected) {
        if (iters > 500) {
            print("handshake TIMEOUT\n")
            ok = false
        } else {
            var sr: i32 = enet_host_service(server, &ev, 5)
            if (sr > 0) {
                if (ev.type == ENET_EVENT_TYPE_CONNECT) {
                    print("[server] peer connected\n")
                }
            }
            var cr: i32 = enet_host_service(client, &ev, 5)
            if (cr > 0) {
                if (ev.type == ENET_EVENT_TYPE_CONNECT) {
                    print("[client] connected to server\n")
                    connected = true
                }
            }
            iters = iters + 1
        }
    }

    // ---- Send one reliable packet ----
    if (ok) {
        var msg: cstr = "hello from gx!"
        // Friction #3 RESOLVED: pointer cast syntax `(*void)expr` lets us
        // pass a cstr buffer directly. The C-glue gx_enet_packet_create_bytes
        // shim is no longer needed and will be removed in a follow-up.
        var pkt: *void = enet_packet_create((*void)msg, 14, ENET_PACKET_FLAG_RELIABLE)
        enet_peer_send(peer, 0, pkt)
        enet_host_flush(client)
        print("[client] sent packet\n")
    }

    // ---- Wait for receive ----
    var got: bool = false
    iters = 0
    while (ok && !got) {
        if (iters > 200) {
            print("receive TIMEOUT\n")
            ok = false
        } else {
            var sr: i32 = enet_host_service(server, &ev, 5)
            if (sr > 0) {
                if (ev.type == ENET_EVENT_TYPE_RECEIVE) {
                    print("[server] got packet, channel={ev.channelID}\n")
                    enet_packet_destroy(ev.packet)
                    got = true
                }
            }
            enet_host_service(client, &ev, 0)
            iters = iters + 1
        }
    }

    // ---- Disconnect cleanly ----
    if (ok && peer != null) {
        enet_peer_disconnect(peer, 0)
        iters = 0
        var done: bool = false
        while (!done && iters < 50) {
            var sr: i32 = enet_host_service(server, &ev, 5)
            if (sr > 0) {
                if (ev.type == ENET_EVENT_TYPE_DISCONNECT) {
                    print("[server] peer disconnected\n")
                    done = true
                }
            }
            enet_host_service(client, &ev, 5)
            iters = iters + 1
        }
    }

    // ---- Tear down ----
    if (client != null) { enet_host_destroy(client) }
    if (server != null) { enet_host_destroy(server) }
    enet_deinitialize()
    if (ok) {
        print("smoke test PASSED\n")
    } else {
        print("smoke test FAILED\n")
    }
}
Threading 51_threading.gx
// 01_hello.gx — threading + chan<T> smoke test.
//
// 8 worker threads each push 100,000 ints into a shared channel.
// Main drains the channel until all expected values arrive, then
// joins every worker. Verifies count == 800,000.
//
// What this demonstrates:
//   - Thread[N] typed handles instead of *void[N]
//   - chan<T> for typed cross-thread communication (no atomics,
//     no shared globals via raw memory, no manual *void casts)
//   - Bounded channel back-pressure: capacity is small relative to
//     total throughput, so producers block on full and resume as
//     main drains. That's the load-bearing path of ec_chan.
//
// Build: gx examples/threading/01_hello.gx -I modules -o build/threading_hello.exe
// Run:   ./build/threading_hello.exe

import threading

const NUM_THREADS = 8
const ITERS_PER_THREAD = 100000

var g_ch: chan<i32>(capacity = 64)

fn worker:i32(arg: *void) {
    var i: i32 = 0
    while (i < ITERS_PER_THREAD) {
        g_ch.send(i)
        i = i + 1
    }
    return 0
}

fn main() {
    // Spawn N workers — typed handle array, no *void in user code.
    var threads: Thread[8]
    var i: i32 = 0
    while (i < NUM_THREADS) {
        gx_thread_start(&threads[i], worker, 0)
        i = i + 1
    }

    // Drain in parallel with workers — required for back-pressure
    // to release. Producers block on full sends until we recv.
    var got_count: i32 = 0
    var expected: i32 = NUM_THREADS * ITERS_PER_THREAD
    var v: i32
    while (got_count < expected) {
        if (g_ch.recv(&v)) { got_count = got_count + 1 }
    }

    // All values delivered — workers are done. Join.
    i = 0
    while (i < NUM_THREADS) {
        gx_thread_join(threads[i])
        i = i + 1
    }
    g_ch.close()

    print("received = {got_count}, expected = {expected}\n")
    if (got_count == expected) {
        print("smoke test PASSED\n")
    } else {
        print("smoke test FAILED\n")
    }
}
Sdl3 52_sdl3.gx
// 01_hello.gx — Breakout-ish game in ~140 lines of GX, rendered with SDL3.
//
// Bricks on the LEFT, player paddle on the RIGHT. Ball bounces off the
// brick wall, the top/bottom edges, and the right paddle. If the ball
// gets past the paddle (off the right edge), miss count increments and
// the ball respawns. Wave clears → new wall.
//
// Controls:
//   Up / Down    — paddle up / down
//   ESC          — quit
//
// Build: gx examples/sdl3/01_hello.gx -I modules -o build/sdl3_hello.exe
// Run:   ./build/sdl3_hello.exe   (needs SDL3.dll on PATH or beside exe)

import sdl3

const SCREEN_W: f32 = 800.0
const SCREEN_H: f32 = 600.0
const PADDLE_W: f32 = 14.0
const PADDLE_H: f32 = 100.0
const PADDLE_SPEED: f32 = 6.0
const BALL_SIZE: f32 = 14.0

// Brick wall — 5 columns × 8 rows on the left third of the screen.
const BRICK_COLS: i32 = 5
const BRICK_ROWS: i32 = 8
const BRICK_W: f32 = 40.0
const BRICK_H: f32 = 50.0
const BRICK_GAP: f32 = 4.0
const BRICK_X0: f32 = 20.0
const BRICK_Y0: f32 = 80.0
const BRICK_COUNT: i32 = 40   // BRICK_COLS * BRICK_ROWS

fn clamp_paddle:f32(y: f32) {
    if (y < 0.0) { return 0.0 }
    var max_y: f32 = SCREEN_H - PADDLE_H
    if (y > max_y) { return max_y }
    return y
}

fn main() {
    var ok: bool = true
    var window: *void = null
    var renderer: *void = null

    if (!SDL_Init(SDL_INIT_VIDEO)) {
        print("SDL_Init failed: {SDL_GetError()}\n")
        ok = false
    }
    if (ok) {
        window = SDL_CreateWindow("GX Breakout", 800, 600, 0)
        if (window == null) { print("CreateWindow failed: {SDL_GetError()}\n"); ok = false }
    }
    if (ok) {
        renderer = SDL_CreateRenderer(window, null)
        if (renderer == null) { print("CreateRenderer failed: {SDL_GetError()}\n"); ok = false }
    }
    if (ok) {
        // vsync removes the 15-ms granularity jitter SDL_Delay would
        // otherwise introduce on Windows. Caps to monitor refresh.
        SDL_SetRenderVSync(renderer, 1)
    }

    // ---- Game state ----
    var paddle_y: f32 = (SCREEN_H - PADDLE_H) * 0.5
    var ball_x:   f32 = SCREEN_W * 0.5
    var ball_y:   f32 = SCREEN_H * 0.5
    var ball_vx:  f32 = 0.0 - 4.0   // start heading toward bricks
    var ball_vy:  f32 = 3.0
    var miss:     i32 = 0
    var cleared:  i32 = 0

    // Brick aliveness — flat array indexed by row*COLS + col.
    var bricks: bool[40]
    var bi: i32 = 0
    while (bi < BRICK_COUNT) { bricks[bi] = true; bi = bi + 1 }
    var bricks_left: i32 = BRICK_COUNT

    var quit: bool = !ok
    var ev: SDL_Event
    while (!quit) {
        while (SDL_PollEvent(&ev)) {
            if (ev.type == SDL_EVENT_QUIT) { quit = true }
            if (ev.type == SDL_EVENT_KEY_DOWN) {
                if (ev.key.scancode == SDL_SCANCODE_ESCAPE) { quit = true }
            }
        }

        var keys: *c_bool = SDL_GetKeyboardState(null)
        if (keys[SDL_SCANCODE_UP])   { paddle_y = paddle_y - PADDLE_SPEED }
        if (keys[SDL_SCANCODE_DOWN]) { paddle_y = paddle_y + PADDLE_SPEED }
        paddle_y = clamp_paddle(paddle_y)

        // ---- Ball physics ----
        ball_x = ball_x + ball_vx
        ball_y = ball_y + ball_vy

        // Top/bottom wall bounce
        if (ball_y < 0.0)                 { ball_y = 0.0;                  ball_vy = 0.0 - ball_vy }
        if (ball_y + BALL_SIZE > SCREEN_H) { ball_y = SCREEN_H - BALL_SIZE; ball_vy = 0.0 - ball_vy }

        // Left wall bounce (in case the ball passes all the bricks)
        if (ball_x < 0.0) { ball_x = 0.0; ball_vx = 0.0 - ball_vx }

        // Brick collision — scan the alive bricks; first AABB overlap wins.
        // To pick the right axis to reverse, we look at where the ball was
        // BEFORE this frame's step. Whichever axis it was already overlapping
        // on is the axis along which it entered → reverse the OTHER axis's
        // velocity. This correctly handles top/bottom hits (reverse vy) vs
        // side hits (reverse vx).
        var prev_x: f32 = ball_x - ball_vx
        var r: i32 = 0
        var hit: bool = false
        while (r < BRICK_ROWS && !hit) {
            var c: i32 = 0
            while (c < BRICK_COLS && !hit) {
                var idx: i32 = r * BRICK_COLS + c
                if (bricks[idx]) {
                    var bx: f32 = BRICK_X0 + c * (BRICK_W + BRICK_GAP)
                    var by: f32 = BRICK_Y0 + r * (BRICK_H + BRICK_GAP)
                    if (ball_x + BALL_SIZE > bx) {
                        if (ball_x < bx + BRICK_W) {
                            if (ball_y + BALL_SIZE > by) {
                                if (ball_y < by + BRICK_H) {
                                    bricks[idx] = false
                                    bricks_left = bricks_left - 1
                                    // Was the ball overlapping this brick's
                                    // x-range last frame? If so, it entered
                                    // vertically — reverse vy. Otherwise it
                                    // entered horizontally — reverse vx.
                                    var was_in_x: bool = (prev_x + BALL_SIZE > bx) && (prev_x < bx + BRICK_W)
                                    if (was_in_x) { ball_vy = 0.0 - ball_vy }
                                    else          { ball_vx = 0.0 - ball_vx }
                                    hit = true
                                }
                            }
                        }
                    }
                }
                c = c + 1
            }
            r = r + 1
        }

        // Right paddle collision
        if (ball_x + BALL_SIZE > SCREEN_W - PADDLE_W) {
            if (ball_y + BALL_SIZE >= paddle_y) {
                if (ball_y <= paddle_y + PADDLE_H) {
                    ball_x = SCREEN_W - PADDLE_W - BALL_SIZE
                    ball_vx = 0.0 - ball_vx
                    var hit_pos: f32 = (ball_y + BALL_SIZE * 0.5) - (paddle_y + PADDLE_H * 0.5)
                    ball_vy = ball_vy + hit_pos * 0.05
                }
            }
        }

        // Ball off the right edge → miss; respawn ball center, heading left.
        if (ball_x > SCREEN_W) {
            miss = miss + 1
            print("miss  total:{miss}  cleared waves:{cleared}\n")
            ball_x = SCREEN_W * 0.5; ball_y = SCREEN_H * 0.5
            ball_vx = 0.0 - 4.0;     ball_vy = 3.0
        }

        // All bricks down — reset wave.
        if (bricks_left <= 0) {
            cleared = cleared + 1
            print("WAVE CLEARED  total cleared:{cleared}  misses:{miss}\n")
            var i: i32 = 0
            while (i < BRICK_COUNT) { bricks[i] = true; i = i + 1 }
            bricks_left = BRICK_COUNT
            ball_x = SCREEN_W * 0.5; ball_y = SCREEN_H * 0.5
            ball_vx = 0.0 - 4.0;     ball_vy = 3.0
        }

        // ---- Render ----
        SDL_SetRenderDrawColor(renderer, 12, 14, 22, 255)
        SDL_RenderClear(renderer)

        // Bricks — alternating row tints, simple but readable
        var rr: i32 = 0
        while (rr < BRICK_ROWS) {
            var cc: i32 = 0
            while (cc < BRICK_COLS) {
                var idx2: i32 = rr * BRICK_COLS + cc
                if (bricks[idx2]) {
                    var rect: SDL_FRect
                    rect.x = BRICK_X0 + cc * (BRICK_W + BRICK_GAP)
                    rect.y = BRICK_Y0 + rr * (BRICK_H + BRICK_GAP)
                    rect.w = BRICK_W; rect.h = BRICK_H
                    if (rr % 2 == 0) { SDL_SetRenderDrawColor(renderer, 220, 80, 80, 255) }
                    else             { SDL_SetRenderDrawColor(renderer, 220, 160, 60, 255) }
                    SDL_RenderFillRect(renderer, &rect)
                }
                cc = cc + 1
            }
            rr = rr + 1
        }

        // Paddle + ball in white
        SDL_SetRenderDrawColor(renderer, 220, 220, 230, 255)
        var rp: SDL_FRect; rp.x = SCREEN_W - PADDLE_W; rp.y = paddle_y; rp.w = PADDLE_W; rp.h = PADDLE_H
        var ba: SDL_FRect; ba.x = ball_x;              ba.y = ball_y;   ba.w = BALL_SIZE; ba.h = BALL_SIZE
        SDL_RenderFillRect(renderer, &rp)
        SDL_RenderFillRect(renderer, &ba)

        SDL_RenderPresent(renderer)
        // No SDL_Delay — vsync (set above) paces the loop to monitor refresh.
    }

    if (renderer != null) { SDL_DestroyRenderer(renderer) }
    if (window != null)   { SDL_DestroyWindow(window) }
    SDL_Quit()
}
Cimgui 53_cimgui.gx
// 01_hello.gx — minimum cimgui smoke test.
//
// Validates that the @cppfile build pipeline can pull in Dear ImGui
// (5 .cpp files of C++ source) + cimgui's C wrapper, link with the
// C++ runtime, and call into the resulting library from GX.
//
// Doesn't render anything — just creates an ImGui context, asks it
// for the version string, destroys the context. That's enough to
// prove the C++ → C → GX chain is working end-to-end.
//
// Build: gx examples/cimgui/01_hello.gx -I modules -o build/cimgui_hello.exe
// Run:   ./build/cimgui_hello.exe

import cimgui

fn main() {
    var ver: cstr = igGetVersion()
    print("imgui version = {ver}\n")

    var ctx: *void = igCreateContext(null)
    if (ctx == null) {
        print("CreateContext FAILED\n")
    } else {
        print("created ImGuiContext OK\n")
        igDestroyContext(ctx)
        print("smoke test PASSED\n")
    }
}
F16 54_f16.gx
// Example 54: f16 — half-precision float (shader `half`)
//
// `f16` is a 16-bit IEEE-754 binary16 float. It mirrors the `half` type
// every shading language exposes, so it is the storage type you reach for
// when preparing data for the GPU backend: vertex attributes, weights,
// and large buffers where 2-byte elements halve bandwidth and footprint.
//
// On the C backend `f16` lowers to the C compiler's native `_Float16`
// when available (clang, gcc 12+); otherwise it falls back to `float`.
// On the LLVM/GPU backend it always lowers to true `half`.

// f16 globals — the literal is f32, narrowed into the annotated f16.
const HALF_ONE:f16  = 1.0
const HALF_PI:f16   = 3.140625   // nearest binary16 value to pi

// f16 fields pack tightly — a Vertex here is 6 bytes with native f16.
struct Vertex {
    x: f16
    y: f16
    z: f16
}

// f16 arguments and return type. Arithmetic on f16 promotes to f32
// (matching C's usual conversions), and narrows back on the typed return.
fn midpoint:f16(a: f16, b: f16) {
    var m: f16 = (a + b) * 0.5
    return m
}

fn main() {
    print("=== f16 / half precision ===\n")

    var h: f16 = 2.5
    print("h         = {h}\n")
    print("HALF_ONE  = {HALF_ONE}\n")
    print("HALF_PI   = {HALF_PI}\n")

    // f16 arithmetic — the result of a + b is f32, stored back into f16.
    var a: f16 = 1.0
    var b: f16 = 4.0
    var mid: f16 = midpoint(a, b)
    print("midpoint(1.0, 4.0) = {mid}\n")

    // f16 in a struct, ready to hand to a GPU vertex buffer.
    var v: Vertex
    v.x = 0.5
    v.y = -0.5
    v.z = 1.0
    print("Vertex = ({v.x}, {v.y}, {v.z})\n")

    // f16 fixed-size array — a tightly packed weight table.
    var weights: f16[4]
    weights[0] = 0.1
    weights[1] = 0.2
    weights[2] = 0.3
    weights[3] = 0.4
    var sum: f16 = 0.0
    for (i = 0:3) {
        sum = sum + weights[i]
    }
    print("weight sum = {sum}\n")
}
Test Math test_math.gx
// test_math.ec - Verify vec and mat math modules compile and produce correct results

import math.scalar
import math.vec
import math.mat

fn main() {
    // --- scalar tests ---
    print("=== Scalar ===\n");
    var s:f32 = sinf(PI() / 2.0);
    print("sin(PI/2) = {s}\n");

    var c:f32 = cosf(0.0);
    print("cos(0) = {c}\n");

    var sq:f32 = sqrtf(9.0);
    print("sqrt(9) = {sq}\n");
    var sw_pos:vec3 = {1,2,3};
    sw_pos.xy += 10;
    print("sw_pos after .xy += 10: {sw_pos}\n");
    var cl:f32 = clamp_f(5.0, 0.0, 1.0);
    print("clamp(5, 0, 1) = {cl}\n");

    var lr:f32 = lerp_f(0.0, 10.0, 0.25);
    print("lerp(0, 10, 0.25) = {lr}\n");

    var rad:f32 = radians(180.0);
    print("radians(180) = {rad}\n");

    // --- vec3 tests ---
    print("\n=== Vec3 ===\n");
    var a:vec3;
    a.x = 3.0;
    a.y = 0.0;
    a.z = 4.0;

    var len:f32 = length_v3(a);
    print("length(3,0,4) = {len}\n");

    var n:vec3 = normalize_v3(a);
    print("normalize(3,0,4) = {n}\n");

    var b:vec3;
    b.x = 1.0;
    b.y = 2.0;
    b.z = 3.0;

    var d:f32 = distance_v3(a, b);
    print("distance = {d}\n");

    var mid:vec3 = lerp_v3(a, b, 0.5);
    print("lerp(a, b, 0.5) = {mid}\n");

    // --- vec2 tests ---
    print("\n=== Vec2 ===\n");
    var v2:vec2;
    v2.x = 3.0;
    v2.y = 4.0;
    var len2:f32 = length_v2(v2);
    print("length(3,4) = {len2}\n");

    var n2:vec2 = normalize_v2(v2);
    print("normalize(3,4) = {n2}\n");

    // --- mat4 tests ---
    print("\n=== Mat4 ===\n");
    var id:mat4 = identity_mat4();
    print("identity = {id}\n");

    // Test translation
    var pos:vec3;
    pos.x = 10.0;
    pos.y = 20.0;
    pos.z = 30.0;
    var tm:mat4 = translation(pos);
    var tc:vec4 = tm.col[3];
    print("translation(10,20,30) col3 = {tc}\n");

    // Test mul_m4 (identity * translation = translation)
    var result:mat4 = mul_m4(id, tm);
    var rc:vec4 = result.col[3];
    print("I * T col3 = {rc}\n");

    // Test mul_m4v4
    var v:vec4;
    v.x = 1.0;
    v.y = 0.0;
    v.z = 0.0;
    v.w = 1.0;
    var tv:vec4 = mul_m4v4(tm, v);
    print("T * (1,0,0,1) = {tv}\n");

    // Test perspective
    var proj:mat4 = perspective(radians(60.0), 1.333, 0.1, 100.0);
    var p00:f32 = proj.col[0].x;
    print("perspective[0][0] = {p00}\n");

    // Test look_at
    var eye:vec3;
    eye.x = 0.0;
    eye.y = 0.0;
    eye.z = 5.0;
    var target:vec3;
    target.x = 0.0;
    target.y = 0.0;
    target.z = 0.0;
    var up:vec3;
    up.x = 0.0;
    up.y = 1.0;
    up.z = 0.0;
    var view:mat4 = look_at(eye, target, up);
    var vc:vec4 = view.col[3];
    print("look_at col3 = {vc}\n");

    print("\nAll math tests done.\n");
}

Graphics — Raylib

Ball Physics raylib/ball_physics.gx
// ball_physics.gx - Raylib ball physics sandbox
// Port of raylib [shapes] example - ball physics
// Build: gx ball_physics.gx -I modules -o ball_physics.exe
// Web:   gx ball_physics.gx -I modules --target web -o ball_physics.html
//
// Controls:
//   Left click (on ball)   — grab and throw
//   Right click            — create new random ball at cursor
//   Left Ctrl + Right hold — spawn many balls
//   Middle click           — shake all balls
//   Mouse wheel            — adjust gravity

import raylib

const MAX_BALLS = 200
const SCREEN_W = 800
const SCREEN_H = 600

struct Ball {
    position: Vector2
    speed: Vector2
    prev_position: Vector2
    radius: f32
    friction: f32
    elasticity: f32
    color: Color
    grabbed: bool
}

// Global ball pool (fixed size, no dynamic allocation)
var g_balls: Ball[200]
var g_ball_count: i32 = 0
var g_grabbed_idx: i32 = 0 - 1   // -1 = none grabbed
var g_press_offset_x: f32 = 0.0
var g_press_offset_y: f32 = 0.0

fn spawn_ball(px: f32, py: f32, vx: f32, vy: f32, radius: f32, r: u8, g: u8, b: u8) {
    if (g_ball_count >= MAX_BALLS) { return }
    var i: i32 = g_ball_count
    g_balls[i].position.x = px
    g_balls[i].position.y = py
    g_balls[i].speed.x = vx
    g_balls[i].speed.y = vy
    g_balls[i].prev_position.x = px
    g_balls[i].prev_position.y = py
    g_balls[i].radius = radius
    g_balls[i].friction = 0.985
    g_balls[i].elasticity = 0.75
    g_balls[i].color.r = r
    g_balls[i].color.g = g
    g_balls[i].color.b = b
    g_balls[i].color.a = 255
    g_balls[i].grabbed = false
    g_ball_count = g_ball_count + 1
}

fn main() {
    InitWindow(SCREEN_W, SCREEN_H, "raylib [shapes] example - ball physics")
    SetTargetFPS(60)

    // Initial ball: big blue one in the center
    spawn_ball(SCREEN_W * 0.5, SCREEN_H * 0.5, 200.0, 200.0, 40.0, 0, 121, 241)

    var gravity: f32 = 100.0
    var sw: f32 = SCREEN_W * 1.0
    var sh: f32 = SCREEN_H * 1.0

    while (!WindowShouldClose()) {
        var delta: f32 = GetFrameTime()
        var mouse: Vector2 = GetMousePosition()

        // ---- Grab a ball on left-click ----
        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
            var i: i32 = g_ball_count - 1
            while (i >= 0) {
                var dx: f32 = mouse.x - g_balls[i].position.x
                var dy: f32 = mouse.y - g_balls[i].position.y
                var dist_sq: f32 = dx * dx + dy * dy
                var r_sq: f32 = g_balls[i].radius * g_balls[i].radius
                if (dist_sq <= r_sq) {
                    g_balls[i].grabbed = true
                    g_grabbed_idx = i
                    g_press_offset_x = dx
                    g_press_offset_y = dy
                    i = 0 - 1    // break
                }
                i = i - 1
            }
        }

        // ---- Release grabbed ball ----
        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) {
            if (g_grabbed_idx >= 0) {
                g_balls[g_grabbed_idx].grabbed = false
                g_grabbed_idx = 0 - 1
            }
        }

        // ---- Spawn new random ball on right-click ----
        var spawn_new: bool = false
        if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { spawn_new = true }
        if (IsKeyDown(KEY_LEFT_CONTROL)) {
            if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { spawn_new = true }
        }
        if (spawn_new) {
            var vx: f32 = (f32)GetRandomValue(0 - 300, 300)
            var vy: f32 = (f32)GetRandomValue(0 - 300, 300)
            var rad: f32 = 20.0 + (f32)GetRandomValue(0, 30)
            var cr: c_int = GetRandomValue(0, 255)
            var cg: c_int = GetRandomValue(0, 255)
            var cb: c_int = GetRandomValue(0, 255)
            spawn_ball(mouse.x, mouse.y, vx, vy, rad, (u8)cr, (u8)cg, (u8)cb)
        }

        // ---- Shake all balls on middle-click ----
        if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {
            for (i = 0 : g_ball_count - 1) {
                if (!g_balls[i].grabbed) {
                    g_balls[i].speed.x = (f32)GetRandomValue(0 - 2000, 2000)
                    g_balls[i].speed.y = (f32)GetRandomValue(0 - 2000, 2000)
                }
            }
        }

        // ---- Adjust gravity with mouse wheel ----
        gravity = gravity + GetMouseWheelMove() * 5.0

        // ---- Update ball physics ----
        for (i = 0 : g_ball_count - 1) {
            if (!g_balls[i].grabbed) {
                g_balls[i].position.x = g_balls[i].position.x + g_balls[i].speed.x * delta
                g_balls[i].position.y = g_balls[i].position.y + g_balls[i].speed.y * delta

                // Wall collision: right
                if ((g_balls[i].position.x + g_balls[i].radius) >= sw) {
                    g_balls[i].position.x = sw - g_balls[i].radius
                    g_balls[i].speed.x = 0.0 - g_balls[i].speed.x * g_balls[i].elasticity
                }
                // Wall collision: left
                if ((g_balls[i].position.x - g_balls[i].radius) <= 0.0) {
                    g_balls[i].position.x = g_balls[i].radius
                    g_balls[i].speed.x = 0.0 - g_balls[i].speed.x * g_balls[i].elasticity
                }
                // Wall collision: bottom
                if ((g_balls[i].position.y + g_balls[i].radius) >= sh) {
                    g_balls[i].position.y = sh - g_balls[i].radius
                    g_balls[i].speed.y = 0.0 - g_balls[i].speed.y * g_balls[i].elasticity
                }
                // Wall collision: top
                if ((g_balls[i].position.y - g_balls[i].radius) <= 0.0) {
                    g_balls[i].position.y = g_balls[i].radius
                    g_balls[i].speed.y = 0.0 - g_balls[i].speed.y * g_balls[i].elasticity
                }

                // Friction + gravity
                g_balls[i].speed.x = g_balls[i].speed.x * g_balls[i].friction
                g_balls[i].speed.y = g_balls[i].speed.y * g_balls[i].friction + gravity
            } else {
                // Ball is being dragged
                var new_x: f32 = mouse.x - g_press_offset_x
                var new_y: f32 = mouse.y - g_press_offset_y
                g_balls[i].speed.x = (new_x - g_balls[i].prev_position.x) / delta
                g_balls[i].speed.y = (new_y - g_balls[i].prev_position.y) / delta
                g_balls[i].prev_position.x = new_x
                g_balls[i].prev_position.y = new_y
                g_balls[i].position.x = new_x
                g_balls[i].position.y = new_y
            }
        }

        // ---- Ball-to-ball collision (O(n^2) pairwise) ----
        // Uses equal-mass elastic collision along the collision normal.
        // Mass is approximated as radius (bigger balls push harder).
        for (a = 0 : g_ball_count - 1) {
            for (b = a + 1 : g_ball_count - 1) {
                var dx: f32 = g_balls[b].position.x - g_balls[a].position.x
                var dy: f32 = g_balls[b].position.y - g_balls[a].position.y
                var min_dist: f32 = g_balls[a].radius + g_balls[b].radius
                var dist_sq: f32 = dx * dx + dy * dy
                var min_sq: f32 = min_dist * min_dist

                if (dist_sq < min_sq) {
                    if (dist_sq > 0.0001) {
                        // Collision normal (unit vector from a to b)
                        // sqrt approximation: Newton's method would be more accurate,
                        // but for stability we use inverse sqrt via division.
                        var dist: f32 = dist_sq
                        // Fast approximate sqrt — just use raylib-provided one via math
                        // Simple approach: iterative refinement
                        var guess: f32 = min_dist
                        guess = (guess + dist / guess) * 0.5
                        guess = (guess + dist / guess) * 0.5
                        guess = (guess + dist / guess) * 0.5
                        dist = guess

                        var nx: f32 = dx / dist
                        var ny: f32 = dy / dist

                        // Separate overlapping balls (split the overlap proportionally)
                        var overlap: f32 = (min_dist - dist) * 0.5
                        if (!g_balls[a].grabbed) {
                            g_balls[a].position.x = g_balls[a].position.x - nx * overlap
                            g_balls[a].position.y = g_balls[a].position.y - ny * overlap
                        }
                        if (!g_balls[b].grabbed) {
                            g_balls[b].position.x = g_balls[b].position.x + nx * overlap
                            g_balls[b].position.y = g_balls[b].position.y + ny * overlap
                        }

                        // Relative velocity along the normal
                        var dvx: f32 = g_balls[b].speed.x - g_balls[a].speed.x
                        var dvy: f32 = g_balls[b].speed.y - g_balls[a].speed.y
                        var vel_along: f32 = dvx * nx + dvy * ny

                        // Only resolve if balls are moving toward each other
                        if (vel_along < 0.0) {
                            // Elastic collision with mass = radius
                            var ma: f32 = g_balls[a].radius
                            var mb: f32 = g_balls[b].radius
                            var e: f32 = 0.55   // elasticity (lower = more damping)
                            var j: f32 = (0.0 - (1.0 + e)) * vel_along / (1.0 / ma + 1.0 / mb)
                            var impulse_x: f32 = j * nx
                            var impulse_y: f32 = j * ny

                            if (!g_balls[a].grabbed) {
                                g_balls[a].speed.x = g_balls[a].speed.x - impulse_x / ma
                                g_balls[a].speed.y = g_balls[a].speed.y - impulse_y / ma
                            }
                            if (!g_balls[b].grabbed) {
                                g_balls[b].speed.x = g_balls[b].speed.x + impulse_x / mb
                                g_balls[b].speed.y = g_balls[b].speed.y + impulse_y / mb
                            }
                        }
                    }
                }
            }
        }

        // ---- Rest clamp: kill tiny velocities to let balls settle ----
        // Without this, balls near the floor vibrate forever due to gravity
        // fighting the elastic response.
        for (k = 0 : g_ball_count - 1) {
            if (!g_balls[k].grabbed) {
                var vx: f32 = g_balls[k].speed.x
                var vy: f32 = g_balls[k].speed.y
                var speed_sq: f32 = vx * vx + vy * vy

                // If ball is touching the floor and moving slowly, clamp to rest
                var on_floor: bool = (g_balls[k].position.y + g_balls[k].radius) >= (sh - 1.0)
                if (on_floor) {
                    if (speed_sq < 400.0) {
                        g_balls[k].speed.x = vx * 0.8    // horizontal damping
                        g_balls[k].speed.y = 0.0         // kill vertical bounce
                    }
                }
                // Also kill horizontal micro-movement to prevent sliding forever
                if (vx < 1.5) {
                    if (vx > 0.0 - 1.5) {
                        g_balls[k].speed.x = 0.0
                    }
                }
            }
        }

        // ---- Draw ----
        BeginDrawing()
        ClearBackground(RAYWHITE)

        for (i = 0 : g_ball_count - 1) {
            DrawCircleV(g_balls[i].position, g_balls[i].radius, g_balls[i].color)
            DrawCircleLinesV(g_balls[i].position, g_balls[i].radius, BLACK)
        }

        DrawText("Left-click + drag a ball to grab and throw", 10, 10, 14, DARKGRAY)
        DrawText("Right-click to spawn (hold Left Ctrl for rapid-fire)", 10, 30, 14, DARKGRAY)
        DrawText("Mouse wheel: gravity    Middle-click: shake", 10, 50, 14, DARKGRAY)

        var count = g_ball_count
        var grav_i = (i32)gravity
        var info1 = "BALL COUNT: {count}"
        var info2 = "GRAVITY: {grav_i}"
        DrawText(info1.cstr, 10, SCREEN_H - 60, 20, BLACK)
        DrawText(info2.cstr, 10, SCREEN_H - 30, 20, BLACK)

        EndDrawing()
    }

    CloseWindow()
}
Boids raylib/boids.gx
// boids.gx - Classic boids flocking simulation
// Based on Craig Reynolds' 1987 "Flocks, Herds, and Schools" paper.
// Three steering rules:
//   1. Separation  — avoid crowding local flockmates
//   2. Alignment   — steer toward average heading of neighbors
//   3. Cohesion    — move toward average position of neighbors
//
// Build: gx boids.gx -I modules -o boids.exe
// Web:   gx boids.gx -I modules --target web -o boids.html
//
// Controls:
//   Left click     — attract boids to cursor
//   Right click    — scatter boids from cursor
//   Mouse wheel    — adjust boid count (adds/removes 10)

import raylib
import math.scalar

const MAX_BOIDS = 300
const SCREEN_W = 800
const SCREEN_H = 600

struct Boid {
    position: Vector2
    velocity: Vector2
    color: Color
    sprint_timer: f32   // >0 = sprinting at 2x speed (escaping predator)
}

struct Predator {
    position: Vector2
    velocity: Vector2
    attack_timer: f32   // >0 = chasing (increased speed)
    scan_timer: f32     // countdown between prey-scans
    active: bool
}

var g_boids: Boid[300]
var g_boid_count: i32 = 0
var g_predator: Predator
var g_spawn_timer: f32 = 0.0   // counts up until next predator spawn

// Flocking parameters
const VIEW_RADIUS: f32 = 60.0
const SEPARATION_RADIUS: f32 = 25.0
const MAX_SPEED: f32 = 90.0
const MAX_FORCE: f32 = 3.5

const SEP_WEIGHT: f32 = 1.8
const ALI_WEIGHT: f32 = 1.0
const COH_WEIGHT: f32 = 0.8

// Predator parameters
const PRED_SIZE: f32 = 21.0              // 3x the boid triangle size (7)
const PRED_CRUISE_SPEED: f32 = 45.0      // slow patrol speed
const PRED_ATTACK_SPEED: f32 = 150.0     // sprint when hunting
const PRED_DETECT_RADIUS: f32 = 140.0    // prey detection range
const PRED_FLEE_RADIUS: f32 = 120.0      // how close predator is before prey flee
const PRED_SPAWN_INTERVAL: f32 = 12.0    // seconds between spawns

// --- Simple LCG PRNG ---
var g_rng: i32 = 12345

fn rand_next:i32() {
    g_rng = g_rng * 1103515245 + 12345
    var v: i32 = (g_rng / 65536) & 32767
    return v
}

fn rand_range_f:f32(lo: f32, hi: f32) {
    var v: f32 = (rand_next() % 10000) / 10000.0
    return lo + v * (hi - lo)
}

fn rand_range_i:i32(lo: i32, hi: i32) {
    var range: i32 = hi - lo + 1
    return lo + (rand_next() % range)
}

fn spawn_boid() {
    if (g_boid_count >= MAX_BOIDS) { return }
    var i: i32 = g_boid_count
    g_boids[i].position.x = rand_range_f(0.0, SCREEN_W * 1.0)
    g_boids[i].position.y = rand_range_f(0.0, SCREEN_H * 1.0)
    g_boids[i].velocity.x = rand_range_f(0.0 - MAX_SPEED, MAX_SPEED)
    g_boids[i].velocity.y = rand_range_f(0.0 - MAX_SPEED, MAX_SPEED)

    // Color gradient based on position — cool blues/purples
    var hue: f32 = rand_range_f(140.0, 255.0)
    g_boids[i].color.r = (u8)(40 + rand_range_i(0, 60))
    g_boids[i].color.g = (u8)(120 + rand_range_i(0, 80))
    g_boids[i].color.b = (u8)hue
    g_boids[i].color.a = 255

    g_boid_count = g_boid_count + 1
}

// Clamp a velocity magnitude in-place (via ball index since GX doesn't deref pointers easily)
fn limit_boid_speed(i: i32, max: f32) {
    var vx: f32 = g_boids[i].velocity.x
    var vy: f32 = g_boids[i].velocity.y
    var mag_sq: f32 = vx * vx + vy * vy
    var max_sq: f32 = max * max
    if (mag_sq > max_sq) {
        var mag: f32 = sqrtf(mag_sq)
        g_boids[i].velocity.x = (vx / mag) * max
        g_boids[i].velocity.y = (vy / mag) * max
    }
}

// Spawn a predator from a random edge, heading toward screen center
fn spawn_predator() {
    var edge: i32 = rand_next() % 4
    var cx: f32 = SCREEN_W * 0.5
    var cy: f32 = SCREEN_H * 0.5

    if (edge == 0) {
        // Top edge
        g_predator.position.x = rand_range_f(0.0, SCREEN_W * 1.0)
        g_predator.position.y = 0.0 - PRED_SIZE
    }
    if (edge == 1) {
        // Right edge
        g_predator.position.x = SCREEN_W + PRED_SIZE
        g_predator.position.y = rand_range_f(0.0, SCREEN_H * 1.0)
    }
    if (edge == 2) {
        // Bottom edge
        g_predator.position.x = rand_range_f(0.0, SCREEN_W * 1.0)
        g_predator.position.y = SCREEN_H + PRED_SIZE
    }
    if (edge == 3) {
        // Left edge
        g_predator.position.x = 0.0 - PRED_SIZE
        g_predator.position.y = rand_range_f(0.0, SCREEN_H * 1.0)
    }

    // Initial heading: toward screen center
    var dx: f32 = cx - g_predator.position.x
    var dy: f32 = cy - g_predator.position.y
    var mag: f32 = sqrtf(dx * dx + dy * dy)
    if (mag < 0.001) { mag = 0.001 }
    g_predator.velocity.x = (dx / mag) * PRED_CRUISE_SPEED
    g_predator.velocity.y = (dy / mag) * PRED_CRUISE_SPEED

    g_predator.attack_timer = 0.0
    g_predator.scan_timer = 2.0
    g_predator.active = true
}

fn main() {
    InitWindow(SCREEN_W, SCREEN_H, "GX + Raylib - Classic Boids")
    SetTargetFPS(60)

    // Spawn initial flock
    for (i = 0 : 99) {
        spawn_boid()
    }

    // Predator starts inactive; will spawn after interval
    g_predator.active = false

    var bg: Color
    bg.r = 12
    bg.g = 18
    bg.b = 32
    bg.a = 255

    while (!WindowShouldClose()) {
        var delta: f32 = GetFrameTime()
        var mouse: Vector2 = GetMousePosition()
        var mouse_attract: bool = IsMouseButtonDown(MOUSE_BUTTON_LEFT)
        var mouse_repel: bool = IsMouseButtonDown(MOUSE_BUTTON_RIGHT)

        // Adjust count with mouse wheel
        var wheel: f32 = GetMouseWheelMove()
        if (wheel > 0.0) {
            for (k = 0 : 9) { spawn_boid() }
        }
        if (wheel < 0.0) {
            if (g_boid_count >= 10) { g_boid_count = g_boid_count - 10 }
        }

        // ---- Predator spawn timer ----
        if (!g_predator.active) {
            g_spawn_timer = g_spawn_timer + delta
            if (g_spawn_timer >= PRED_SPAWN_INTERVAL) {
                g_spawn_timer = 0.0
                spawn_predator()
            }
        }

        // ---- Predator update ----
        if (g_predator.active) {
            // Periodic target scan — enter attack mode if prey is close enough
            g_predator.scan_timer = g_predator.scan_timer - delta
            if (g_predator.scan_timer <= 0.0) {
                g_predator.scan_timer = 3.0  // scan every 3 seconds

                // Find the nearest boid within detection radius
                var best_idx: i32 = 0 - 1
                var best_dist_sq: f32 = PRED_DETECT_RADIUS * PRED_DETECT_RADIUS
                for (i = 0 : g_boid_count - 1) {
                    var bdx: f32 = g_boids[i].position.x - g_predator.position.x
                    var bdy: f32 = g_boids[i].position.y - g_predator.position.y
                    var bsq: f32 = bdx * bdx + bdy * bdy
                    if (bsq < best_dist_sq) {
                        best_dist_sq = bsq
                        best_idx = i
                    }
                }
                if (best_idx >= 0) {
                    // Attack mode for 2.5 seconds — steer toward target
                    g_predator.attack_timer = 2.5
                    var tdx: f32 = g_boids[best_idx].position.x - g_predator.position.x
                    var tdy: f32 = g_boids[best_idx].position.y - g_predator.position.y
                    var tmag: f32 = sqrtf(tdx * tdx + tdy * tdy)
                    if (tmag < 0.001) { tmag = 0.001 }
                    g_predator.velocity.x = (tdx / tmag) * PRED_ATTACK_SPEED
                    g_predator.velocity.y = (tdy / tmag) * PRED_ATTACK_SPEED
                }
            }

            // Decay attack timer and slow back to cruise when it expires
            if (g_predator.attack_timer > 0.0) {
                g_predator.attack_timer = g_predator.attack_timer - delta
                if (g_predator.attack_timer <= 0.0) {
                    // Return to cruise speed
                    var cvx: f32 = g_predator.velocity.x
                    var cvy: f32 = g_predator.velocity.y
                    var cmag: f32 = sqrtf(cvx * cvx + cvy * cvy)
                    if (cmag > 0.001) {
                        g_predator.velocity.x = (cvx / cmag) * PRED_CRUISE_SPEED
                        g_predator.velocity.y = (cvy / cmag) * PRED_CRUISE_SPEED
                    }
                }
            }

            // Move predator
            g_predator.position.x = g_predator.position.x + g_predator.velocity.x * delta
            g_predator.position.y = g_predator.position.y + g_predator.velocity.y * delta

            // Kill predator when it exits the screen
            var off: f32 = PRED_SIZE * 2.0
            if (g_predator.position.x < (0.0 - off)) { g_predator.active = false }
            if (g_predator.position.x > (SCREEN_W + off)) { g_predator.active = false }
            if (g_predator.position.y < (0.0 - off)) { g_predator.active = false }
            if (g_predator.position.y > (SCREEN_H + off)) { g_predator.active = false }
        }

        // ---- Boid flocking update ----
        for (i = 0 : g_boid_count - 1) {
            // Accumulators for the three rules
            var sep_x: f32 = 0.0
            var sep_y: f32 = 0.0
            var ali_x: f32 = 0.0
            var ali_y: f32 = 0.0
            var coh_x: f32 = 0.0
            var coh_y: f32 = 0.0
            var neighbors: i32 = 0
            var sep_count: i32 = 0

            // Scan all other boids
            for (j = 0 : g_boid_count - 1) {
                if (i != j) {
                    var dx: f32 = g_boids[j].position.x - g_boids[i].position.x
                    var dy: f32 = g_boids[j].position.y - g_boids[i].position.y
                    var dist_sq: f32 = dx * dx + dy * dy

                    if (dist_sq < VIEW_RADIUS * VIEW_RADIUS) {
                        if (dist_sq > 0.01) {
                            // Alignment & cohesion: all boids in view radius
                            ali_x = ali_x + g_boids[j].velocity.x
                            ali_y = ali_y + g_boids[j].velocity.y
                            coh_x = coh_x + g_boids[j].position.x
                            coh_y = coh_y + g_boids[j].position.y
                            neighbors = neighbors + 1

                            // Separation: only very close boids
                            if (dist_sq < SEPARATION_RADIUS * SEPARATION_RADIUS) {
                                // Push away, weighted by inverse distance
                                var dist: f32 = sqrtf(dist_sq)
                                sep_x = sep_x - (dx / dist) / dist
                                sep_y = sep_y - (dy / dist) / dist
                                sep_count = sep_count + 1
                            }
                        }
                    }
                }
            }

            // Compute steering forces
            var steer_x: f32 = 0.0
            var steer_y: f32 = 0.0

            if (sep_count > 0) {
                steer_x = steer_x + sep_x * SEP_WEIGHT * 200.0
                steer_y = steer_y + sep_y * SEP_WEIGHT * 200.0
            }

            if (neighbors > 0) {
                var inv_n: f32 = 1.0 / (neighbors * 1.0)
                // Alignment: steer toward average velocity of neighbors
                var avg_vx: f32 = ali_x * inv_n
                var avg_vy: f32 = ali_y * inv_n
                steer_x = steer_x + (avg_vx - g_boids[i].velocity.x) * ALI_WEIGHT * 0.05
                steer_y = steer_y + (avg_vy - g_boids[i].velocity.y) * ALI_WEIGHT * 0.05

                // Cohesion: steer toward average position of neighbors
                var center_x: f32 = coh_x * inv_n
                var center_y: f32 = coh_y * inv_n
                steer_x = steer_x + (center_x - g_boids[i].position.x) * COH_WEIGHT * 0.02
                steer_y = steer_y + (center_y - g_boids[i].position.y) * COH_WEIGHT * 0.02
            }

            // Mouse interaction
            if (mouse_attract) {
                var mdx: f32 = mouse.x - g_boids[i].position.x
                var mdy: f32 = mouse.y - g_boids[i].position.y
                steer_x = steer_x + mdx * 0.8
                steer_y = steer_y + mdy * 0.8
            }
            if (mouse_repel) {
                var mdx: f32 = g_boids[i].position.x - mouse.x
                var mdy: f32 = g_boids[i].position.y - mouse.y
                var mdist_sq: f32 = mdx * mdx + mdy * mdy
                if (mdist_sq < 40000.0) {
                    steer_x = steer_x + mdx * 3.0
                    steer_y = steer_y + mdy * 3.0
                }
            }

            // Predator flee: if predator is active and close, steer away hard + sprint
            if (g_predator.active) {
                var pdx: f32 = g_boids[i].position.x - g_predator.position.x
                var pdy: f32 = g_boids[i].position.y - g_predator.position.y
                var pdist_sq: f32 = pdx * pdx + pdy * pdy
                var flee_sq: f32 = PRED_FLEE_RADIUS * PRED_FLEE_RADIUS
                if (pdist_sq < flee_sq) {
                    // Stronger flee the closer the predator is
                    var pdist: f32 = sqrtf(pdist_sq)
                    if (pdist < 0.001) { pdist = 0.001 }
                    var flee_force: f32 = (PRED_FLEE_RADIUS - pdist) * 4.0
                    steer_x = steer_x + (pdx / pdist) * flee_force
                    steer_y = steer_y + (pdy / pdist) * flee_force
                    // Activate sprint for 1.2 seconds
                    g_boids[i].sprint_timer = 1.2
                }
            }

            // Decay sprint timer
            if (g_boids[i].sprint_timer > 0.0) {
                g_boids[i].sprint_timer = g_boids[i].sprint_timer - delta
            }

            // Apply steering force
            g_boids[i].velocity.x = g_boids[i].velocity.x + steer_x * delta
            g_boids[i].velocity.y = g_boids[i].velocity.y + steer_y * delta

            // Limit speed (2x while sprinting to escape predator)
            var speed_cap: f32 = MAX_SPEED
            if (g_boids[i].sprint_timer > 0.0) {
                speed_cap = MAX_SPEED * 2.0
            }
            limit_boid_speed(i, speed_cap)

            // Update position
            g_boids[i].position.x = g_boids[i].position.x + g_boids[i].velocity.x * delta
            g_boids[i].position.y = g_boids[i].position.y + g_boids[i].velocity.y * delta

            // Wrap around screen edges
            if (g_boids[i].position.x < 0.0) { g_boids[i].position.x = g_boids[i].position.x + SCREEN_W }
            if (g_boids[i].position.x > SCREEN_W) { g_boids[i].position.x = g_boids[i].position.x - SCREEN_W }
            if (g_boids[i].position.y < 0.0) { g_boids[i].position.y = g_boids[i].position.y + SCREEN_H }
            if (g_boids[i].position.y > SCREEN_H) { g_boids[i].position.y = g_boids[i].position.y - SCREEN_H }
        }

        // ---- Draw ----
        BeginDrawing()
        ClearBackground(bg)

        // Draw each boid as a triangle pointing in its velocity direction
        for (i = 0 : g_boid_count - 1) {
            var vx: f32 = g_boids[i].velocity.x
            var vy: f32 = g_boids[i].velocity.y
            var vmag: f32 = sqrtf(vx * vx + vy * vy)
            if (vmag < 0.001) { vmag = 0.001 }

            // Unit direction
            var dx: f32 = vx / vmag
            var dy: f32 = vy / vmag
            // Perpendicular
            var px: f32 = 0.0 - dy
            var py: f32 = dx

            var cx: f32 = g_boids[i].position.x
            var cy: f32 = g_boids[i].position.y
            var size: f32 = 7.0

            // Triangle: tip forward, two base points behind
            // Winding MUST be counter-clockwise for raylib (otherwise back-face culled)
            var p1: Vector2
            p1.x = cx + dx * size
            p1.y = cy + dy * size
            // Back-right (CCW from tip in screen space where y goes down)
            var p2: Vector2
            p2.x = cx - dx * size - px * size * 0.6
            p2.y = cy - dy * size - py * size * 0.6
            // Back-left
            var p3: Vector2
            p3.x = cx - dx * size + px * size * 0.6
            p3.y = cy - dy * size + py * size * 0.6

            DrawTriangle(p1, p2, p3, g_boids[i].color)
        }

        // Draw predator (big red triangle)
        if (g_predator.active) {
            var pvx: f32 = g_predator.velocity.x
            var pvy: f32 = g_predator.velocity.y
            var pvmag: f32 = sqrtf(pvx * pvx + pvy * pvy)
            if (pvmag < 0.001) { pvmag = 0.001 }
            var pdx: f32 = pvx / pvmag
            var pdy: f32 = pvy / pvmag
            var ppx: f32 = 0.0 - pdy
            var ppy: f32 = pdx
            var pcx: f32 = g_predator.position.x
            var pcy: f32 = g_predator.position.y
            var psize: f32 = PRED_SIZE

            var pred_color: Color
            if (g_predator.attack_timer > 0.0) {
                // Brighter red while attacking
                pred_color.r = 255
                pred_color.g = 40
                pred_color.b = 40
            } else {
                pred_color.r = 200
                pred_color.g = 30
                pred_color.b = 30
            }
            pred_color.a = 255

            var pp1: Vector2
            pp1.x = pcx + pdx * psize
            pp1.y = pcy + pdy * psize
            var pp2: Vector2
            pp2.x = pcx - pdx * psize - ppx * psize * 0.6
            pp2.y = pcy - pdy * psize - ppy * psize * 0.6
            var pp3: Vector2
            pp3.x = pcx - pdx * psize + ppx * psize * 0.6
            pp3.y = pcy - pdy * psize + ppy * psize * 0.6
            DrawTriangle(pp1, pp2, pp3, pred_color)
        }

        DrawText("Left-click: attract    Right-click: scatter    Wheel: +/- 10 boids", 10, 10, 14, RAYWHITE)
        var count = g_boid_count
        var info = "BOIDS: {count}"
        DrawText(info.cstr, 10, SCREEN_H - 30, 20, RAYWHITE)
        DrawFPS(SCREEN_W - 90, 10)

        EndDrawing()
    }

    CloseWindow()
}
Bullet Hell raylib/bullet_hell.gx
// bullet_hell.gx - Raylib bullet hell shmup demo
// Port of raylib [shapes] example - bullet hell
// Build: gx bullet_hell.gx -I modules -o bullet_hell.exe
// Web:   gx bullet_hell.gx -I modules --target web -o bullet_hell.html
//
// Controls:
//   Right/Left or A/D  — Change number of bullet rows
//   Up/Down or W/S     — Change bullet speed
//   Z / X              — Change spawn cooldown
//   Space (hold)       — Change angle increment per frame
//   Enter              — Switch draw method (texture vs DrawCircle)
//   C                  — Clear bullets

import raylib
import math.scalar

const MAX_BULLETS = 100000

struct Bullet {
    position: Vector2
    acceleration: Vector2
    disabled: bool
    color: Color
}

// Global bullet pool — fixed size array (no dynamic allocation)
var g_bullets: Bullet[100000]

fn main() {
    var screen_width: i32 = 800
    var screen_height: i32 = 600

    InitWindow(screen_width, screen_height, "raylib [shapes] example - bullet hell")

    var bullet_count: i32 = 0
    var bullet_disabled_count: i32 = 0
    var bullet_radius: i32 = 10
    var bullet_speed: f32 = 3.0
    var bullet_rows: i32 = 6

    // Spawner state
    var base_direction: f32 = 0.0
    var angle_increment: i32 = 5
    var spawn_cooldown: f32 = 2.0
    var spawn_cooldown_timer: f32 = 2.0

    // Magic circle animation
    var magic_circle_rotation: f32 = 0.0

    // Pre-rendered bullet texture for performance
    var bullet_texture = LoadRenderTexture(24, 24)
    BeginTextureMode(bullet_texture)
        DrawCircle(12, 12, 10.0, WHITE)
        DrawCircleLines(12, 12, 10.0, BLACK)
    EndTextureMode()

    var draw_perf_mode: bool = true

    // Colors array (can't easily use const arrays in GX, use locals)
    var color_a = RED
    var color_b = BLUE

    SetTargetFPS(60)

    while (!WindowShouldClose()) {
        // ----- Update -----

        // Reset ring buffer when full
        if (bullet_count >= MAX_BULLETS) {
            bullet_count = 0
            bullet_disabled_count = 0
        }

        spawn_cooldown_timer = spawn_cooldown_timer - 1.0
        if (spawn_cooldown_timer < 0.0) {
            spawn_cooldown_timer = spawn_cooldown

            // Spawn one bullet per row, distributed around 360 degrees
            var degrees_per_row: f32 = 360.0 / (bullet_rows * 1.0)
            var row: i32 = 0
            while (row < bullet_rows) {
                if (bullet_count < MAX_BULLETS) {
                    var pos: Vector2
                    pos.x = screen_width * 0.5
                    pos.y = screen_height * 0.5
                    g_bullets[bullet_count].position = pos
                    g_bullets[bullet_count].disabled = false

                    if ((row % 2) == 0) {
                        g_bullets[bullet_count].color = color_a
                    } else {
                        g_bullets[bullet_count].color = color_b
                    }

                    var bullet_direction: f32 = base_direction + (degrees_per_row * (row * 1.0))
                    var rad: f32 = radians(bullet_direction)

                    var acc: Vector2
                    acc.x = bullet_speed * cosf(rad)
                    acc.y = bullet_speed * sinf(rad)
                    g_bullets[bullet_count].acceleration = acc

                    bullet_count = bullet_count + 1
                }
                row = row + 1
            }

            base_direction = base_direction + (angle_increment * 1.0)
        }

        // Update bullet positions
        var i: i32 = 0
        while (i < bullet_count) {
            if (!g_bullets[i].disabled) {
                g_bullets[i].position.x = g_bullets[i].position.x + g_bullets[i].acceleration.x
                g_bullets[i].position.y = g_bullets[i].position.y + g_bullets[i].acceleration.y

                var off: f32 = bullet_radius * 2.0
                if (g_bullets[i].position.x < (0.0 - off)) {
                    g_bullets[i].disabled = true
                    bullet_disabled_count = bullet_disabled_count + 1
                }
                if (g_bullets[i].position.x > (screen_width + off)) {
                    if (!g_bullets[i].disabled) {
                        g_bullets[i].disabled = true
                        bullet_disabled_count = bullet_disabled_count + 1
                    }
                }
                if (g_bullets[i].position.y < (0.0 - off)) {
                    if (!g_bullets[i].disabled) {
                        g_bullets[i].disabled = true
                        bullet_disabled_count = bullet_disabled_count + 1
                    }
                }
                if (g_bullets[i].position.y > (screen_height + off)) {
                    if (!g_bullets[i].disabled) {
                        g_bullets[i].disabled = true
                        bullet_disabled_count = bullet_disabled_count + 1
                    }
                }
            }
            i = i + 1
        }

        // ----- Input -----
        if (IsKeyPressed(KEY_RIGHT)) {
            if (bullet_rows < 359) { bullet_rows = bullet_rows + 1 }
        }
        if (IsKeyPressed(KEY_D)) {
            if (bullet_rows < 359) { bullet_rows = bullet_rows + 1 }
        }
        if (IsKeyPressed(KEY_LEFT)) {
            if (bullet_rows > 1) { bullet_rows = bullet_rows - 1 }
        }
        if (IsKeyPressed(KEY_A)) {
            if (bullet_rows > 1) { bullet_rows = bullet_rows - 1 }
        }
        if (IsKeyPressed(KEY_UP)) { bullet_speed = bullet_speed + 0.25 }
        if (IsKeyPressed(KEY_W)) { bullet_speed = bullet_speed + 0.25 }
        if (IsKeyPressed(KEY_DOWN)) {
            if (bullet_speed > 0.5) { bullet_speed = bullet_speed - 0.25 }
        }
        if (IsKeyPressed(KEY_S)) {
            if (bullet_speed > 0.5) { bullet_speed = bullet_speed - 0.25 }
        }
        if (IsKeyPressed(KEY_Z)) {
            if (spawn_cooldown > 1.0) { spawn_cooldown = spawn_cooldown - 1.0 }
        }
        if (IsKeyPressed(KEY_X)) { spawn_cooldown = spawn_cooldown + 1.0 }
        if (IsKeyPressed(KEY_ENTER)) { draw_perf_mode = !draw_perf_mode }
        if (IsKeyDown(KEY_SPACE)) {
            angle_increment = angle_increment + 1
            angle_increment = angle_increment % 360
        }
        if (IsKeyPressed(KEY_C)) {
            bullet_count = 0
            bullet_disabled_count = 0
        }

        // ----- Draw -----
        BeginDrawing()
        ClearBackground(RAYWHITE)

        // Magic circle
        magic_circle_rotation = magic_circle_rotation + 1.0
        var rect: Rectangle
        rect.x = screen_width * 0.5
        rect.y = screen_height * 0.5
        rect.width = 120.0
        rect.height = 120.0
        var origin: Vector2
        origin.x = 60.0
        origin.y = 60.0
        DrawRectanglePro(rect, origin, magic_circle_rotation, PURPLE)
        DrawRectanglePro(rect, origin, magic_circle_rotation + 45.0, PURPLE)
        DrawCircleLines(screen_width / 2, screen_height / 2, 70.0, BLACK)
        DrawCircleLines(screen_width / 2, screen_height / 2, 50.0, BLACK)
        DrawCircleLines(screen_width / 2, screen_height / 2, 30.0, BLACK)

        // Draw bullets
        if (draw_perf_mode) {
            var tw: f32 = 12.0
            var th: f32 = 12.0
            var j: i32 = 0
            while (j < bullet_count) {
                if (!g_bullets[j].disabled) {
                    var fx: f32 = g_bullets[j].position.x - tw
                    var fy: f32 = g_bullets[j].position.y - th
                    DrawTextureV(bullet_texture.texture, vec2{fx, fy}, g_bullets[j].color)
                }
                j = j + 1
            }
        } else {
            var j: i32 = 0
            while (j < bullet_count) {
                if (!g_bullets[j].disabled) {
                    DrawCircleV(g_bullets[j].position, bullet_radius * 1.0, g_bullets[j].color)
                    DrawCircleLinesV(g_bullets[j].position, bullet_radius * 1.0, BLACK)
                }
                j = j + 1
            }
        }

        // UI panel background
        var panel_bg: Color
        panel_bg.r = 0
        panel_bg.g = 0
        panel_bg.b = 0
        panel_bg.a = 200

        DrawRectangle(10, 10, 280, 150, panel_bg)
        DrawText("Controls:", 20, 20, 10, LIGHTGRAY)
        DrawText("- Right/Left or A/D: Change rows number", 40, 40, 10, LIGHTGRAY)
        DrawText("- Up/Down or W/S: Change bullet speed", 40, 60, 10, LIGHTGRAY)
        DrawText("- Z or X: Change spawn cooldown", 40, 80, 10, LIGHTGRAY)
        DrawText("- Space (Hold): Change the angle increment", 40, 100, 10, LIGHTGRAY)
        DrawText("- Enter: Switch draw method (Performance)", 40, 120, 10, LIGHTGRAY)
        DrawText("- C: Clear bullets", 40, 140, 10, LIGHTGRAY)

        DrawRectangle(610, 10, 170, 30, panel_bg)
        if (draw_perf_mode) {
            DrawText("Draw method: DrawTexture(*)", 620, 20, 10, GREEN)
        } else {
            DrawText("Draw method: DrawCircle(*)", 620, 20, 10, RED)
        }

        DrawRectangle(135, 560, 530, 30, panel_bg)
        DrawText("[ FPS stats ]", 155, 570, 10, GREEN)

        EndDrawing()
    }

    UnloadRenderTexture(bullet_texture)
    CloseWindow()
}
Hello raylib/hello.gx
// hello.gx - GX + Raylib with built-in math types
// Build: gx hello.gx -I modules -o hello.exe
//
// Vector2/3/4 are aliases to GX's vec2/vec3/vec4 — you get
// operator overloading, swizzle ops, and GX math for free.
// Colors are uppercase constants: RED, BLUE, WHITE, etc.

import raylib


fn main() {
    InitWindow(800, 450, "GX + Raylib")
    SetTargetFPS(60)

    while (!WindowShouldClose()) {
        if (IsKeyPressed(KEY_F)) {
            ToggleFullscreen()
        }

        BeginDrawing()
        ClearBackground(RAYWHITE)

        DrawText("Hello from GX + Raylib!", 240, 40, 30, DARKGRAY)

        // GX vec2 literals work directly as Vector2
        DrawRectangle(50, 120, 200, 100, RED)
        DrawCircleV(vec2{450.0, 170.0}, 60.0, BLUE)

        // vec2 math: operator overloading
        var a = vec2{600.0, 120.0}
        var b = vec2{550.0, 220.0}
        var c = vec2{650.0, 220.0}
        DrawTriangle(a, b, c, GREEN)

        // vec2 arithmetic works naturally
        var center = vec2{400.0, 350.0}
        var offset = vec2{50.0, 0.0}

        DrawCircleV(center - offset, 20.0, ORANGE)
        DrawCircleV(center, 20.0, PURPLE)
        DrawCircleV(center + offset, 20.0, SKYBLUE)

        DrawFPS(10, 10)
        DrawText("Press F for fullscreen", 10, 420, 16, GRAY)

        DrawCircleV(center, 20.0, PURPLE)
        EndDrawing()
    }

    CloseWindow()
}
import raylib.colors
Image Generation raylib/image_generation.gx
// image_generation.gx - Raylib procedural texture generation
// Port of raylib [textures] example — image generation
// Build: gx image_generation.gx -I modules -o image_generation.exe
// Web:   gx image_generation.gx -I modules --target web -o image_generation.html
//
// Demonstrates raylib's procedural image generators:
//   - Linear gradients (vertical, horizontal, diagonal)
//   - Radial / Square gradients
//   - Checkerboard
//   - White noise / Perlin noise / Cellular noise
//
// Controls: Left click or RIGHT arrow to cycle textures

import raylib

const NUM_TEXTURES = 9

fn main() {
    var screen_width: c_int = 800
    var screen_height: c_int = 600

    InitWindow(screen_width, screen_height, "raylib [textures] example - image generation")

    // Generate 9 procedural images
    var vertical_gradient   = GenImageGradientLinear(screen_width, screen_height, 0, RED, BLUE)
    var horizontal_gradient = GenImageGradientLinear(screen_width, screen_height, 90, RED, BLUE)
    var diagonal_gradient   = GenImageGradientLinear(screen_width, screen_height, 45, RED, BLUE)
    var radial_gradient     = GenImageGradientRadial(screen_width, screen_height, 0.0, WHITE, BLACK)
    var square_gradient     = GenImageGradientSquare(screen_width, screen_height, 0.0, WHITE, BLACK)
    var checked             = GenImageChecked(screen_width, screen_height, 32, 32, RED, BLUE)
    var white_noise         = GenImageWhiteNoise(screen_width, screen_height, 0.5)
    var perlin_noise        = GenImagePerlinNoise(screen_width, screen_height, 50, 50, 4.0)
    var cellular            = GenImageCellular(screen_width, screen_height, 32)

    // Upload all images to GPU as textures
    var textures: Texture[9]
    textures[0] = LoadTextureFromImage(vertical_gradient)
    textures[1] = LoadTextureFromImage(horizontal_gradient)
    textures[2] = LoadTextureFromImage(diagonal_gradient)
    textures[3] = LoadTextureFromImage(radial_gradient)
    textures[4] = LoadTextureFromImage(square_gradient)
    textures[5] = LoadTextureFromImage(checked)
    textures[6] = LoadTextureFromImage(white_noise)
    textures[7] = LoadTextureFromImage(perlin_noise)
    textures[8] = LoadTextureFromImage(cellular)

    // Free CPU image data (textures live on GPU now)
    UnloadImage(vertical_gradient)
    UnloadImage(horizontal_gradient)
    UnloadImage(diagonal_gradient)
    UnloadImage(radial_gradient)
    UnloadImage(square_gradient)
    UnloadImage(checked)
    UnloadImage(white_noise)
    UnloadImage(perlin_noise)
    UnloadImage(cellular)

    var current_texture: i32 = 0
    var auto_timer: i32 = 0

    SetTargetFPS(60)

    while (!WindowShouldClose()) {
        // Auto-cycle every 120 frames (~2 seconds at 60 FPS)
        auto_timer = auto_timer + 1
        if (auto_timer >= 120) {
            current_texture = (current_texture + 1) % NUM_TEXTURES
            auto_timer = 0
        }

        // Manual cycle on mouse click or right arrow (resets timer)
        if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
            current_texture = (current_texture + 1) % NUM_TEXTURES
            auto_timer = 0
        }
        if (IsKeyPressed(KEY_RIGHT)) {
            current_texture = (current_texture + 1) % NUM_TEXTURES
            auto_timer = 0
        }

        BeginDrawing()
        ClearBackground(RAYWHITE)

        DrawTexture(textures[current_texture], 0, 0, WHITE)

        // Info panel with faded background
        DrawRectangle(30, 550, 325, 30, Fade(SKYBLUE, 0.5))
        DrawRectangleLines(30, 550, 325, 30, Fade(WHITE, 0.5))
        DrawText("MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES", 40, 560, 10, WHITE)

        // Texture label
        if (current_texture == 0) { DrawText("VERTICAL GRADIENT", 560, 10, 20, RAYWHITE) }
        if (current_texture == 1) { DrawText("HORIZONTAL GRADIENT", 540, 10, 20, RAYWHITE) }
        if (current_texture == 2) { DrawText("DIAGONAL GRADIENT", 540, 10, 20, RAYWHITE) }
        if (current_texture == 3) { DrawText("RADIAL GRADIENT", 580, 10, 20, LIGHTGRAY) }
        if (current_texture == 4) { DrawText("SQUARE GRADIENT", 580, 10, 20, LIGHTGRAY) }
        if (current_texture == 5) { DrawText("CHECKED", 680, 10, 20, RAYWHITE) }
        if (current_texture == 6) { DrawText("WHITE NOISE", 640, 10, 20, RED) }
        if (current_texture == 7) { DrawText("PERLIN NOISE", 640, 10, 20, RED) }
        if (current_texture == 8) { DrawText("CELLULAR", 670, 10, 20, RAYWHITE) }

        EndDrawing()
    }

    // Cleanup — unload GPU textures
    for (i = 0 : 8) {
        UnloadTexture(textures[i])
    }

    CloseWindow()
}
Logo Gx Anim raylib/logo_gx_anim.gx
// logo_gx_anim.gx - Animated GX logo loaded from LogoLow.png
// Each pixel in the low-res logo becomes a quad that falls from above.
// Preserves the exact shape of the brand logo.
//
// Build: gx logo_gx_anim.gx -I modules -o logo_gx_anim.exe
// Web:   gx logo_gx_anim.gx -I modules --target web -o logo_gx_anim.html --preload-file website/resources/LogoLow.png

import raylib

const BLOCK_SIZE = 10           // pixel size of each quad on screen
const MAX_QUADS = 4000

struct Quad {
    target_x: f32
    target_y: f32
    current_y: f32
    velocity: f32
    delay: i32       // frames to wait before starting to fall
    landed: bool
    r: u8
    g: u8
    b: u8
}

// Simple LCG for randomized delays
var g_rng: i32 = 12345

fn rand_range:i32(max: i32) {
    g_rng = g_rng * 1103515245 + 12345
    var v: i32 = (g_rng / 65536) & 32767
    return v % max
}

var g_quads: Quad[4000]
var g_quad_count: i32 = 0

// Load logo image, sample every pixel, spawn a quad for each non-transparent one
fn build_logo_from_image(img: Image, center_x: i32, center_y: i32) {
    g_quad_count = 0

    var img_w: i32 = (i32)img.width
    var img_h: i32 = (i32)img.height
    var total_w: i32 = img_w * BLOCK_SIZE
    var total_h: i32 = img_h * BLOCK_SIZE
    var start_x: i32 = center_x - total_w / 2
    var start_y: i32 = center_y - total_h / 2

    // Old-school chroma key: top-left pixel is the transparent color
    var bg_color: Color = GetImageColor(img, 0, 0)

    for (y = 0 : img_h - 1) {
        for (x = 0 : img_w - 1) {
            var c: Color = GetImageColor(img, x, y)
            // Skip pixels matching the top-left (background) color
            var is_bg: bool = false
            if (c.r == bg_color.r) {
                if (c.g == bg_color.g) {
                    if (c.b == bg_color.b) {
                        is_bg = true
                    }
                }
            }
            if (!is_bg) {
                if (g_quad_count < MAX_QUADS) {
                    g_quads[g_quad_count].target_x = (start_x + x * BLOCK_SIZE) * 1.0
                    g_quads[g_quad_count].target_y = (start_y + y * BLOCK_SIZE) * 1.0
                    // Start well above the screen
                    g_quads[g_quad_count].current_y = 0.0 - 200.0 - (rand_range(300) * 1.0)
                    g_quads[g_quad_count].velocity = 0.0
                    // Random delay (0-90 frames) so quads fall in scattered order
                    g_quads[g_quad_count].delay = rand_range(90)
                    g_quads[g_quad_count].landed = false
                    g_quads[g_quad_count].r = c.r
                    g_quads[g_quad_count].g = c.g
                    g_quads[g_quad_count].b = c.b
                    g_quad_count = g_quad_count + 1
                }
            }
        }
    }
}

fn update_quads:bool() {
    var all_landed: bool = true
    for (i = 0 : g_quad_count - 1) {
        if (!g_quads[i].landed) {
            if (g_quads[i].delay > 0) {
                g_quads[i].delay = g_quads[i].delay - 1
                all_landed = false
            } else {
                g_quads[i].velocity = g_quads[i].velocity + 0.55
                g_quads[i].current_y = g_quads[i].current_y + g_quads[i].velocity

                if (g_quads[i].current_y >= g_quads[i].target_y) {
                    g_quads[i].current_y = g_quads[i].target_y
                    g_quads[i].landed = true
                } else {
                    all_landed = false
                }
            }
        }
    }
    return all_landed
}

fn draw_quads(alpha: f32) {
    for (i = 0 : g_quad_count - 1) {
        // Skip quads still waiting for their delay to expire
        if (g_quads[i].delay > 0) {
            continue
        }
        var col: Color
        col.r = g_quads[i].r
        col.g = g_quads[i].g
        col.b = g_quads[i].b
        col.a = 255
        var tint: Color = Fade(col, alpha)

        var rec: Rectangle
        rec.x = g_quads[i].target_x
        rec.y = g_quads[i].current_y
        rec.width = BLOCK_SIZE * 1.0
        rec.height = BLOCK_SIZE * 1.0
        DrawRectangleRec(rec, tint)
    }
}

fn main() {
    var screen_width: i32 = 800
    var screen_height: i32 = 600

    InitWindow(screen_width, screen_height, "GX logo animation")
    SetTargetFPS(60)

    // Load the low-res logo
    var logo_img: Image = LoadImage("website/resources/LogoLow.png")

    var bg: Color
    bg.r = 8
    bg.g = 15
    bg.b = 35
    bg.a = 255

    var state: i32 = 0
    var alpha: f32 = 1.0
    var hold_timer: i32 = 0

    build_logo_from_image(logo_img, screen_width / 2, screen_height / 2)

    while (!WindowShouldClose()) {
        if (state == 0) {
            var landed: bool = update_quads()
            if (landed) {
                state = 1
                hold_timer = 0
            }
        }
        if (state == 1) {
            hold_timer = hold_timer + 1
            if (hold_timer > 120) { state = 2 }
        }
        if (state == 2) {
            alpha = alpha - 0.015
            if (alpha <= 0.0) {
                alpha = 0.0
                state = 3
            }
        }
        if (state == 3) {
            build_logo_from_image(logo_img, screen_width / 2, screen_height / 2)
            alpha = 1.0
            hold_timer = 0
            state = 0
        }

        BeginDrawing()
        ClearBackground(bg)
        draw_quads(alpha)
        EndDrawing()
    }

    UnloadImage(logo_img)
    CloseWindow()
}

Graphics — Sokol

Input sokol/input.gx
// input.ec - Handling input events with Sokol
// Build: ec examples/sokol/input.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/input.exe

import sokol.gfx
import sokol.app
import sokol.glue

// Global state for background color
var g_r:f32 = 0.2;
var g_g:f32 = 0.2;
var g_b:f32 = 0.2;

@c_abi
fn init() {
    var gfx_desc:sg_desc;
    gfx_desc.environment = sglue_environment();
    sg_setup(&gfx_desc);
    print("Input demo initialized. Press keys or click mouse!\n");
}

@c_abi
fn frame() {
    var pass:sg_pass;
    pass.action.colors[0].load_action = 1;  // SG_LOADACTION_CLEAR
    pass.action.colors[0].clear_value.r = g_r;
    pass.action.colors[0].clear_value.g = g_g;
    pass.action.colors[0].clear_value.b = g_b;
    pass.action.colors[0].clear_value.a = 1.0;
    pass.swapchain = sglue_swapchain();
    
    sg_begin_pass(&pass);
    sg_end_pass();
    sg_commit();
}

@c_abi
fn event(ev: const *sapp_event) {
    if (ev.type == EVENTTYPE_KEY_DOWN) {
        if (ev.key_code == KEYCODE_ESCAPE) {
            sapp_quit();
        }
        print("Key pressed!\n");
    }
    elif (ev.type == EVENTTYPE_MOUSE_DOWN) {
        if (ev.mouse_button == MOUSEBUTTON_LEFT) {
            g_r = 0.8; g_g = 0.2; g_b = 0.2;
        }
        elif (ev.mouse_button == MOUSEBUTTON_RIGHT) {
            g_r = 0.2; g_g = 0.8; g_b = 0.2;
        }
        print("Mouse down!\n");
    }
    elif (ev.type == EVENTTYPE_MOUSE_MOVE) {
        // print("Mouse move: " + ev.mouse_x.str() + ", " + ev.mouse_y.str() + "\n");
    }
}

@c_abi
fn cleanup() {
    sg_shutdown();
}

fn main() {
    var desc:sapp_desc;
    desc.init_cb = init;
    desc.frame_cb = frame;
    desc.event_cb = event;
    desc.cleanup_cb = cleanup;
    desc.width = 640;
    desc.height = 480;
    desc.sample_count = 1;
    desc.swap_interval = 1;
    desc.window_title = "EC Sokol Input Demo";
    sapp_run(&desc);
}
Shapes sokol/shapes.gx
// shapes.ec - Drawing 2D shapes with Sokol GP
// Build: ec examples/sokol/shapes.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/shapes.exe

import sokol.gfx
import sokol.app
import sokol.glue
import sokol.gp
import math.scalar

// Animation state
var g_time:f32 = 0.0;
var g_frame:i32 = 0;

@c_abi
fn init() {
    var gfx_desc:sg_desc;
    gfx_desc.environment = sglue_environment();
    sg_setup(&gfx_desc);

    var gp_desc:sgp_desc;
    sgp_setup(&gp_desc);

    var err:sgp_error = sgp_get_last_error();
    var msg:cstr = sgp_get_error_message(err);
    print("SGP status: ");
    print(msg);
    print("\n");
}

@c_abi
fn frame() {
    var w:c_int = sapp_width();
    var h:c_int = sapp_height();
    var fw:f32 = 800.0;
    var fh:f32 = 600.0;

    g_time = g_time + 0.016;
    g_frame = g_frame + 1;

    sgp_begin(w, h);
    sgp_viewport(0, 0, w, h);
    sgp_project(0.0, fw, 0.0, fh);

    // Dark background
    sgp_set_color(0.08, 0.08, 0.12, 1.0);
    sgp_clear();

    // --- Grid of small dots ---
    var gx:i32 = 0;
    while (gx < 20) {
        var gy:i32 = 0;
        while (gy < 15) {
            sgp_set_color(0.15, 0.15, 0.2, 1.0);
            sgp_draw_filled_rect(gx * 40 + 19, gy * 40 + 19, 2.0, 2.0);
            gy = gy + 1;
        }
        gx = gx + 1;
    }

    // --- Animated rectangles (color wheel) ---
    var i:i32 = 0;
    while (i < 6) {
        var fi:f32 = i * 1.0;
        var angle:f32 = fi * 1.0472 + g_time;  // 60 degrees apart + rotation
        var cx:f32 = 200.0 + cosf(angle) * 80.0;
        var cy:f32 = 150.0 + sinf(angle) * 80.0;
        var r:f32 = sinf(g_time + fi) * 0.5 + 0.5;
        var g:f32 = sinf(g_time + fi + 2.094) * 0.5 + 0.5;
        var b:f32 = sinf(g_time + fi + 4.189) * 0.5 + 0.5;
        sgp_set_blend_mode(BLENDMODE_BLEND);
        sgp_set_color(r, g, b, 0.7);
        sgp_draw_filled_rect(cx - 20.0, cy - 20.0, 40.0, 40.0);
        i = i + 1;
    }

    // --- Spinning triangles ---
    sgp_set_blend_mode(BLENDMODE_BLEND);
    var t:i32 = 0;
    while (t < 4) {
        var ft:f32 = t * 1.0;
        var ta:f32 = g_time * 1.5 + ft * 1.5708;
        var tcx:f32 = 600.0;
        var tcy:f32 = 150.0;
        var tr:f32 = 60.0;
        var ax:f32 = tcx + cosf(ta) * tr;
        var ay:f32 = tcy + sinf(ta) * tr;
        var bx:f32 = tcx + cosf(ta + 2.094) * tr;
        var by:f32 = tcy + sinf(ta + 2.094) * tr;
        var ccx:f32 = tcx + cosf(ta + 4.189) * tr;
        var ccy:f32 = tcy + sinf(ta + 4.189) * tr;

        var cr:f32 = 0.2;
        var cg:f32 = 0.6;
        var cb:f32 = 0.9;
        if (t == 1) { cr = 0.9; cg = 0.3; cb = 0.2; }
        elif (t == 2) { cr = 0.2; cg = 0.9; cb = 0.3; }
        elif (t == 3) { cr = 0.9; cg = 0.9; cb = 0.2; }
        sgp_set_color(cr, cg, cb, 0.6);
        sgp_draw_filled_triangle(ax, ay, bx, by, ccx, ccy);
        t = t + 1;
    }

    // --- Pulsing center rectangle ---
    var pulse:f32 = sinf(g_time * 3.0) * 0.3 + 0.7;
    sgp_set_color(1.0, 1.0, 1.0, pulse);
    var pw:f32 = 60.0 + sinf(g_time * 2.0) * 20.0;
    var ph:f32 = 60.0 + cosf(g_time * 2.0) * 20.0;
    sgp_draw_filled_rect(400.0 - pw / 2.0, 300.0 - ph / 2.0, pw, ph);

    // --- Line star pattern ---
    sgp_set_color(0.5, 0.8, 1.0, 0.5);
    var li:i32 = 0;
    while (li < 12) {
        var la:f32 = li * 0.5236 + g_time * 0.3;  // 30 degrees apart
        var lx:f32 = 400.0 + cosf(la) * 120.0;
        var ly:f32 = 450.0 + sinf(la) * 80.0;
        sgp_draw_line(400.0, 450.0, lx, ly);
        li = li + 1;
    }

    // --- Bouncing row of rectangles ---
    var bi:i32 = 0;
    while (bi < 8) {
        var bf:f32 = bi * 1.0;
        var bx:f32 = 50.0 + bf * 90.0;
        var bounce:f32 = sinf(g_time * 4.0 + bf * 0.8);
        if (bounce < 0.0) { bounce = 0.0 - bounce; }
        var by:f32 = 520.0 - bounce * 40.0;
        var br:f32 = sinf(bf * 0.7) * 0.5 + 0.5;
        var bg:f32 = sinf(bf * 0.7 + 2.0) * 0.5 + 0.5;
        var bb:f32 = sinf(bf * 0.7 + 4.0) * 0.5 + 0.5;
        sgp_set_color(br, bg, bb, 0.9);
        sgp_draw_filled_rect(bx, by, 50.0, 20.0);
        bi = bi + 1;
    }

    // --- Concentric rectangles (top right) ---
    var ci:i32 = 0;
    while (ci < 5) {
        var cf:f32 = ci * 1.0;
        var cs:f32 = 20.0 + cf * 20.0;
        var ca:f32 = 1.0 - cf * 0.18;
        sgp_set_color(sinf(g_time + cf) * 0.5 + 0.5, 0.3, 0.8, ca);
        sgp_draw_filled_rect(400.0 - cs / 2.0, 100.0 - cs / 2.0, cs, cs);
        ci = ci + 1;
    }

    // Flush SGP into Sokol GFX pass
    var pass:sg_pass;
    pass.action.colors[0].load_action = 1;  // SG_LOADACTION_CLEAR
    pass.action.colors[0].clear_value.r = 0.0;
    pass.action.colors[0].clear_value.g = 0.0;
    pass.action.colors[0].clear_value.b = 0.0;
    pass.action.colors[0].clear_value.a = 1.0;
    pass.swapchain = sglue_swapchain();
    sg_begin_pass(&pass);
    sgp_flush();
    sgp_end();
    sg_end_pass();
    sg_commit();
}

@c_abi
fn event(ev: const *sapp_event) {
    if (ev.type == EVENTTYPE_KEY_DOWN) {
        if (ev.key_code == KEYCODE_ESCAPE) {
            sapp_quit();
        }
    }
}

@c_abi
fn cleanup() {
    sgp_shutdown();
    sg_shutdown();
}

fn main() {
    var desc:sapp_desc;
    desc.init_cb = init;
    desc.frame_cb = frame;
    desc.event_cb = event;
    desc.cleanup_cb = cleanup;
    desc.width = 800;
    desc.height = 600;
    desc.sample_count = 1;
    desc.swap_interval = 1;
    desc.window_title = "EC Sokol GP - Shapes Demo";
    sapp_run(&desc);
}
Snake sokol/snake.gx
// snake.ec - Classic Snake game in EC using Sokol
// Build: ec examples/sokol/snake.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/snake.exe

import sokol.app
import sokol.gfx
import sokol.glue

// ============================================================================
// Constants
// ============================================================================
// Grid: 20x20 cells
// Each cell in NDC: 2.0/20 = 0.1 units
// Max snake length: 400 (full grid)
// Vertex format: (x, y, r, g, b) = 5 floats per vertex
// Each cell quad: 2 triangles = 6 vertices = 30 floats
// Max cells rendered: ~410 (snake + food + border)
// Max floats: 410 * 30 = 12300

// ============================================================================
// Game State
// ============================================================================
var g_sx:i32[400];       // snake X positions (grid coords)
var g_sy:i32[400];       // snake Y positions (grid coords)
var g_slen:i32 = 3;      // current snake length
var g_dir:i32 = 1;       // direction: 0=up, 1=right, 2=down, 3=left
var g_next_dir:i32 = 1;  // buffered direction (applied on next tick)
var g_food_x:i32 = 10;
var g_food_y:i32 = 10;
var g_score:i32 = 0;
var g_alive:i32 = 1;     // 1=playing, 0=game over
var g_tick:i32 = 0;       // frame counter for movement timing
var g_speed:i32 = 8;      // frames between moves (lower = faster)
var g_rng:i32 = 42;       // simple PRNG state
var g_started:i32 = 0;    // 0=title screen, 1=playing

// ============================================================================
// Vertex buffer (dynamic, rebuilt each frame)
// ============================================================================
var g_verts:f32[12600];   // max 420 quads * 30 floats
var g_vert_count:i32 = 0; // number of floats written
var g_num_verts:i32 = 0;  // number of vertices (vert_count / 5)

// ============================================================================
// Sokol resources
// ============================================================================
var g_pip:sg_pipeline;
var g_bind:sg_bindings;
var g_vbuf:sg_buffer;

// Shaders
var g_vs:cstr = "#version 330\nin vec2 position;\nin vec3 color0;\nout vec3 color;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); color = color0; }\n";
var g_fs:cstr = "#version 330\nin vec3 color;\nout vec4 frag_color;\nvoid main() { frag_color = vec4(color, 1.0); }\n";

// ============================================================================
// PRNG (simple LCG)
// ============================================================================
fn rng_next:i32() {
    g_rng = g_rng * 1103515245 + 12345;
    var v:i32 = g_rng / 65536;
    if (v < 0) { v = 0 - v; }
    return v;
}

fn rng_range:i32(max:i32) {
    var v:i32 = rng_next();
    return v % max;
}

// ============================================================================
// Vertex helpers: push a colored quad at grid position
// ============================================================================
fn push_vert(x:f32, y:f32, r:f32, g:f32, b:f32) {
    g_verts[g_vert_count] = x;      g_vert_count = g_vert_count + 1;
    g_verts[g_vert_count] = y;      g_vert_count = g_vert_count + 1;
    g_verts[g_vert_count] = r;      g_vert_count = g_vert_count + 1;
    g_verts[g_vert_count] = g;      g_vert_count = g_vert_count + 1;
    g_verts[g_vert_count] = b;      g_vert_count = g_vert_count + 1;
}

fn push_quad(gx:i32, gy:i32, r:f32, g:f32, b:f32) {
    // Convert grid coords to NDC
    // Grid is 20x20, mapped to [-0.95, 0.95] to leave a small border
    var cell:f32 = 1.9 / 20.0;
    var gap:f32 = 0.01;
    var x0:f32 = -0.95 + gx * cell + gap;
    var y0:f32 = 0.95 - gy * cell - gap;
    var x1:f32 = x0 + cell - gap * 2.0;
    var y1:f32 = y0 - cell + gap * 2.0;

    // Triangle 1: top-left, top-right, bottom-left
    push_vert(x0, y0, r, g, b);
    push_vert(x1, y0, r, g, b);
    push_vert(x0, y1, r, g, b);
    // Triangle 2: top-right, bottom-right, bottom-left
    push_vert(x1, y0, r, g, b);
    push_vert(x1, y1, r, g, b);
    push_vert(x0, y1, r, g, b);



}

// ============================================================================
// Game Logic
// ============================================================================
fn game_reset() {
    g_slen = 3;
    g_dir = 1;
    g_next_dir = 1;
    g_score = 0;
    g_alive = 1;
    g_tick = 0;
    g_speed = 8;
    g_started = 1;

    // Start snake in center, going right
    g_sx[0] = 10; g_sy[0] = 10;  // head
    g_sx[1] = 9;  g_sy[1] = 10;
    g_sx[2] = 8;  g_sy[2] = 10;

    spawn_food();
    print("Snake game started! Score: 0\n");
}

fn spawn_food() {
    var valid:i32 = 0;
    while (valid == 0) {
        g_food_x = rng_range(20);
        g_food_y = rng_range(20);
        // Make sure food doesn't overlap snake
        valid = 1;
        var i:i32 = 0;
        while (i < g_slen) {
            if (g_sx[i] == g_food_x) {
                if (g_sy[i] == g_food_y) {
                    valid = 0;
                }
            }
            i = i + 1;
        }
    }
}

fn game_tick() {
    if (g_alive == 0) { return; }
    if (g_started == 0) { return; }

    g_tick = g_tick + 1;
    if (g_tick < g_speed) { return; }
    g_tick = 0;

    // Apply buffered direction
    g_dir = g_next_dir;

    // Calculate new head position
    var hx:i32 = g_sx[0];
    var hy:i32 = g_sy[0];
    if (g_dir == 0) { hy = hy - 1; }
    if (g_dir == 1) { hx = hx + 1; }
    if (g_dir == 2) { hy = hy + 1; }
    if (g_dir == 3) { hx = hx - 1; }

    // Wall collision
    if (hx < 0)  { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
    if (hx > 19) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
    if (hy < 0)  { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }
    if (hy > 19) { g_alive = 0; print("Game Over! Hit wall. Score: {g_score}\n"); return; }

    // Self collision
    var i:i32 = 0;
    while (i < g_slen) {
        if (g_sx[i] == hx) {
            if (g_sy[i] == hy) {
                g_alive = 0;
                print("Game Over! Hit self. Score: {g_score}\n");
                return;
            }
        }
        i = i + 1;
    }

    // Check food
    var ate:i32 = 0;
    if (hx == g_food_x) {
        if (hy == g_food_y) {
            ate = 1;
            g_score = g_score + 1;
            print("Score: {g_score}\n");
            // Speed up every 5 points
            if (g_score % 5 == 0) {
                if (g_speed > 3) {
                    g_speed = g_speed - 1;
                }
            }
        }
    }

    // Move body: shift all segments down
    if (ate == 1) {
        // Grow: don't remove tail
        var j:i32 = g_slen;
        while (j > 0) {
            g_sx[j] = g_sx[j - 1];
            g_sy[j] = g_sy[j - 1];
            j = j - 1;
        }
        g_slen = g_slen + 1;
    } else {
        // No growth: shift body, tail disappears
        var j:i32 = g_slen - 1;
        while (j > 0) {
            g_sx[j] = g_sx[j - 1];
            g_sy[j] = g_sy[j - 1];
            j = j - 1;
        }
    }

    // Place new head
    g_sx[0] = hx;
    g_sy[0] = hy;

    // Spawn new food if eaten
    if (ate == 1) {
        spawn_food();
    }
}

// ============================================================================
// Rendering
// ============================================================================
fn build_vertices() {
    g_vert_count = 0;

    if (g_started == 0) {
        // Title screen: draw "S N A K E" pattern using cells
        // Letter S (col 2-5, row 6-12)
        push_quad(2, 6, 0.2, 0.8, 0.2);  push_quad(3, 6, 0.2, 0.8, 0.2);  push_quad(4, 6, 0.2, 0.8, 0.2);
        push_quad(2, 7, 0.2, 0.8, 0.2);
        push_quad(2, 8, 0.2, 0.8, 0.2);  push_quad(3, 8, 0.2, 0.8, 0.2);  push_quad(4, 8, 0.2, 0.8, 0.2);
        push_quad(4, 9, 0.2, 0.8, 0.2);
        push_quad(2, 10, 0.2, 0.8, 0.2); push_quad(3, 10, 0.2, 0.8, 0.2); push_quad(4, 10, 0.2, 0.8, 0.2);

        // Letter N (col 6-9, row 6-10)
        push_quad(6, 6, 0.2, 0.8, 0.2);  push_quad(6, 7, 0.2, 0.8, 0.2);  push_quad(6, 8, 0.2, 0.8, 0.2);
        push_quad(6, 9, 0.2, 0.8, 0.2);  push_quad(6, 10, 0.2, 0.8, 0.2);
        push_quad(7, 7, 0.2, 0.8, 0.2);
        push_quad(8, 8, 0.2, 0.8, 0.2);
        push_quad(9, 6, 0.2, 0.8, 0.2);  push_quad(9, 7, 0.2, 0.8, 0.2);  push_quad(9, 8, 0.2, 0.8, 0.2);
        push_quad(9, 9, 0.2, 0.8, 0.2);  push_quad(9, 10, 0.2, 0.8, 0.2);

        // Letter A (col 11-14, row 6-10)
        push_quad(12, 6, 0.2, 0.8, 0.2); push_quad(13, 6, 0.2, 0.8, 0.2);
        push_quad(11, 7, 0.2, 0.8, 0.2); push_quad(14, 7, 0.2, 0.8, 0.2);
        push_quad(11, 8, 0.2, 0.8, 0.2); push_quad(12, 8, 0.2, 0.8, 0.2);
        push_quad(13, 8, 0.2, 0.8, 0.2); push_quad(14, 8, 0.2, 0.8, 0.2);
        push_quad(11, 9, 0.2, 0.8, 0.2); push_quad(14, 9, 0.2, 0.8, 0.2);
        push_quad(11, 10, 0.2, 0.8, 0.2); push_quad(14, 10, 0.2, 0.8, 0.2);

        // Letter K (col 16-19, row 6-10)
        push_quad(16, 6, 0.2, 0.8, 0.2);  push_quad(16, 7, 0.2, 0.8, 0.2);
        push_quad(16, 8, 0.2, 0.8, 0.2);  push_quad(16, 9, 0.2, 0.8, 0.2);
        push_quad(16, 10, 0.2, 0.8, 0.2);
        push_quad(18, 6, 0.2, 0.8, 0.2);  push_quad(17, 7, 0.2, 0.8, 0.2);
        push_quad(17, 8, 0.2, 0.8, 0.2);
        push_quad(17, 9, 0.2, 0.8, 0.2);  push_quad(18, 10, 0.2, 0.8, 0.2);

        // "Press SPACE" hint - small dots
        push_quad(6, 14, 0.5, 0.5, 0.5);
        push_quad(8, 14, 0.5, 0.5, 0.5);
        push_quad(10, 14, 0.5, 0.5, 0.5);
        push_quad(12, 14, 0.5, 0.5, 0.5);
        push_quad(14, 14, 0.5, 0.5, 0.5);

        g_num_verts = g_vert_count / 5;
        return;
    }

    // Draw food (pulsing red — alternate brightness)
    var food_r:f32 = 0.9;
    var food_g:f32 = 0.15;
    var food_b:f32 = 0.15;
    if (g_tick % 4 < 2) {
        food_r = 1.0;
        food_g = 0.25;
        food_b = 0.2;
    }
    push_quad(g_food_x, g_food_y, food_r, food_g, food_b);

    // Draw snake body (gradient from bright to dark green)
    var i:i32 = g_slen - 1;
    while (i >= 0) {
        var t:f32 = 0.3;
        if (g_slen > 1) {
            // Gradient: head is bright, tail is darker
            t = 0.3 + 0.6 * (1.0 - i * 1.0 / g_slen);
        }
        var sr:f32 = 0.1;
        var sg:f32 = t;
        var sb:f32 = 0.1;
        if (i == 0) {
            // Head is brightest
            sr = 0.2;
            sg = 0.95;
            sb = 0.2;
        }
        // Game over: snake turns red
        if (g_alive == 0) {
            sr = 0.7;
            sg = 0.15;
            sb = 0.1;
            if (i == 0) {
                sr = 1.0;
                sg = 0.2;
                sb = 0.15;
            }
        }
        push_quad(g_sx[i], g_sy[i], sr, sg, sb);
        i = i - 1;
    }

    // Draw border (subtle gray outline around play area)
    var bx:i32 = 0;
    while (bx < 20) {
        // Top and bottom border markers (just outside play area — we'll draw at grid edge)
        // Since we can't draw outside the grid, draw a subtle border at the edges
        if (g_alive == 1) {
            // Dim grid dots at corners only to keep it subtle
        }
        bx = bx + 1;
    }

    g_num_verts = g_vert_count / 5;
}

// ============================================================================
// Sokol Callbacks
// ============================================================================
@c_abi
fn init() {
    // Setup sokol_gfx
    var gfx_desc:sg_desc;
    gfx_desc.environment = sglue_environment();
    sg_setup(&gfx_desc);

    // Create a STREAM vertex buffer (updated every frame)
    var buf_desc:sg_buffer_desc;
    buf_desc.size = 50400;              // 12600 floats * 4 bytes = 50400 bytes
    buf_desc.usage.stream_update = 1;    // enable streaming
    g_vbuf = sg_make_buffer(&buf_desc);
    g_bind.vertex_buffers[0] = g_vbuf;

    // Create shader
    var shd_desc:sg_shader_desc;
    shd_desc.vertex_func.source = g_vs;
    shd_desc.fragment_func.source = g_fs;
    shd_desc.attrs[0].glsl_name = "position";
    shd_desc.attrs[1].glsl_name = "color0";
    var shd:sg_shader = sg_make_shader(&shd_desc);

    // Create pipeline
    var pip_desc:sg_pipeline_desc;
    pip_desc.shader = shd;
    pip_desc.layout.attrs[0].format = 2;  // SG_VERTEXFORMAT_FLOAT2
    pip_desc.layout.attrs[1].format = 3;  // SG_VERTEXFORMAT_FLOAT3
    g_pip = sg_make_pipeline(&pip_desc);

    // Seed PRNG with something
    g_rng = 7919;

    print("=== EC Snake ===\n");
    print("Arrow keys or WASD to move\n");
    print("Press SPACE to start\n");
    print("ESC to quit\n\n");
}

@c_abi
fn frame() {
    // Game logic
    game_tick();

    // Build vertices
    build_vertices();

    // Update vertex buffer
    var range:sg_range;
    range.ptr = &g_verts[0];
    range.size = g_vert_count * 4;  // 4 bytes per float
    if (g_vert_count > 0) {
        sg_update_buffer(g_vbuf, &range);
    }

    // Background color
    var bg_r:f32 = 0.08;
    var bg_g:f32 = 0.08;
    var bg_b:f32 = 0.1;
    if (g_alive == 0) {
        // Reddish tint on game over
        bg_r = 0.15;
        bg_g = 0.05;
        bg_b = 0.05;
    }

    // Render pass
    var pass:sg_pass;
    pass.action.colors[0].load_action = 1;  // SG_LOADACTION_CLEAR
    pass.action.colors[0].clear_value.r = bg_r;
    pass.action.colors[0].clear_value.g = bg_g;
    pass.action.colors[0].clear_value.b = bg_b;
    pass.action.colors[0].clear_value.a = 1.0;
    pass.swapchain = sglue_swapchain();

    sg_begin_pass(&pass);
    if (g_num_verts > 0) {
        sg_apply_pipeline(g_pip);
        sg_apply_bindings(&g_bind);
        sg_draw(0, g_num_verts, 1);
    }
    sg_end_pass();
    sg_commit();
}

@c_abi
fn event(ev: const *sapp_event) {
    if (ev.type == EVENTTYPE_KEY_DOWN) {
        // Quit
        if (ev.key_code == KEYCODE_ESCAPE) {
            sapp_quit();
        }

        // Start / restart
        if (ev.key_code == KEYCODE_SPACE) {
            if (g_started == 0) {
                game_reset();
            }
            elif (g_alive == 0) {
                game_reset();
            }
        }
        if (ev.key_code == KEYCODE_ENTER) {
            if (g_started == 0) {
                game_reset();
            }
            elif (g_alive == 0) {
                game_reset();
            }
        }

        // Direction input (prevent 180-degree turns)
        if (g_alive == 1) {
            if (ev.key_code == KEYCODE_UP) {
                if (g_dir != 2) { g_next_dir = 0; }
            }
            if (ev.key_code == KEYCODE_W) {
                if (g_dir != 2) { g_next_dir = 0; }
            }
            if (ev.key_code == KEYCODE_RIGHT) {
                if (g_dir != 3) { g_next_dir = 1; }
            }
            if (ev.key_code == KEYCODE_D) {
                if (g_dir != 3) { g_next_dir = 1; }
            }
            if (ev.key_code == KEYCODE_DOWN) {
                if (g_dir != 0) { g_next_dir = 2; }
            }
            if (ev.key_code == KEYCODE_S) {
                if (g_dir != 0) { g_next_dir = 2; }
            }
            if (ev.key_code == KEYCODE_LEFT) {
                if (g_dir != 1) { g_next_dir = 3; }
            }
            if (ev.key_code == KEYCODE_A) {
                if (g_dir != 1) { g_next_dir = 3; }
            }
        }
    }
}

@c_abi
fn cleanup() {
    sg_shutdown();
}

fn main() {
    var desc:sapp_desc;
    desc.init_cb = init;
    desc.frame_cb = frame;
    desc.event_cb = event;
    desc.cleanup_cb = cleanup;
    desc.width = 640;
    desc.height = 640;
    desc.sample_count = 1;
    desc.swap_interval = 1;
    desc.window_title = "EC Snake";
    sapp_run(&desc);
}
Triangle sokol/triangle.gx
// triangle.ec - 100% EC triangle demo using sokol wrappers
// Build: ec examples/sokol/triangle.ec -I modules --cfile modules/sokol/c/sokol_impl.c --cflags "-Imodules/sokol/c" --ldflags "-lopengl32 -lgdi32 -luser32 -lshell32" -o build/triangle.exe

import sokol.app
import sokol.gfx
import sokol.glue

// ============================================================================
// Vertex data: 3 vertices * (2 pos + 3 color) = 15 floats
// ============================================================================
var g_vertices:f32[15];

// Global state
var g_pip:sg_pipeline;
var g_bind:sg_bindings;

// ============================================================================
// Shader sources (GLSL 330)
// ============================================================================
var g_vs_source:cstr = "#version 330\nin vec2 position;\nin vec3 color0;\nout vec3 color;\nvoid main() { gl_Position = vec4(position, 0.0, 1.0); color = color0; }\n";
var g_fs_source:cstr = "#version 330\nin vec3 color;\nout vec4 frag_color;\nvoid main() { frag_color = vec4(color, 1.0); }\n";

@c_abi
fn init() {
    print("Initializing...\n");
    
    // Setup sokol_gfx
    var gfx_desc:sg_desc;
    gfx_desc.environment = sglue_environment();
    sg_setup(&gfx_desc);
    
    // Initialize vertex data: positions + colors
    // Top vertex (red)
    g_vertices[0] = 0.0;   g_vertices[1] = 0.5;
    g_vertices[2] = 1.0;   g_vertices[3] = 0.0;   g_vertices[4] = 0.0;
    // Right vertex (green)
    g_vertices[5] = 0.5;   g_vertices[6] = -0.5;
    g_vertices[7] = 0.0;   g_vertices[8] = 1.0;   g_vertices[9] = 0.0;
    // Left vertex (blue)
    g_vertices[10] = -0.5; g_vertices[11] = -0.5;
    g_vertices[12] = 0.0;  g_vertices[13] = 0.0;  g_vertices[14] = 1.0;
    
    // Create vertex buffer
    var buf_desc:sg_buffer_desc;
    buf_desc.data.ptr = &g_vertices[0];
    buf_desc.data.size = 60;  // 15 floats * 4 bytes
    var vbuf:sg_buffer = sg_make_buffer(&buf_desc);
    g_bind.vertex_buffers[0] = vbuf;
    
    // Create shader
    var shd_desc:sg_shader_desc;
    shd_desc.vertex_func.source = g_vs_source;
    shd_desc.fragment_func.source = g_fs_source;
    shd_desc.attrs[0].glsl_name = "position";
    shd_desc.attrs[1].glsl_name = "color0";
    var shd:sg_shader = sg_make_shader(&shd_desc);
    
    // Create pipeline
    var pip_desc:sg_pipeline_desc;
    pip_desc.shader = shd;
    pip_desc.layout.attrs[0].format = 2;  // SG_VERTEXFORMAT_FLOAT2
    pip_desc.layout.attrs[1].format = 3;  // SG_VERTEXFORMAT_FLOAT3
    g_pip = sg_make_pipeline(&pip_desc);
    
    print("Ready!\n");
}

@c_abi
fn frame() {
    var pass:sg_pass;
    pass.action.colors[0].load_action = 1;  // SG_LOADACTION_CLEAR
    pass.action.colors[0].clear_value.r = 0.2;
    pass.action.colors[0].clear_value.g = 0.2;
    pass.action.colors[0].clear_value.b = 0.2;
    pass.action.colors[0].clear_value.a = 1.0;
    pass.swapchain = sglue_swapchain();
    
    sg_begin_pass(&pass);
    sg_apply_pipeline(g_pip);
    sg_apply_bindings(&g_bind);
    sg_draw(0, 3, 1);
    sg_end_pass();
    sg_commit();
}

@c_abi
fn cleanup() {
    print("Cleanup...\n");
    sg_shutdown();
}

fn main() {
    print("EC Sokol Triangle (100% EC)\n");
    var desc:sapp_desc;
    desc.init_cb = init;
    desc.frame_cb = frame;
    desc.cleanup_cb = cleanup;
    desc.width = 640;
    desc.height = 480;
    desc.sample_count = 1;
    desc.swap_interval = 1;
    desc.window_title = "EC Triangle (Pure EC)";
    sapp_run(&desc);
}