Embedding Quadrate

This guide shows how to embed Quadrate as a scripting language in your C/C++ applications. Quadrate is well-suited for embedding because it:

  • Compiles to native code (fast execution)
  • Has a simple stack-based model (easy to interface with)
  • Provides a clean C API
  • Supports registering native functions

Quick Start

Here's the minimal code to embed Quadrate:

#include <qd/qd.h>

int main(void) {
    // Create execution context with 1024-element stack
    qd_context* ctx = qd_create_context(1024);

    // Create a module and add code
    qd_module* mod = qd_get_module(ctx, "game");
    qd_add_script(mod, "fn hello() { \"Hello from Quadrate!\" print nl }");
    qd_build(mod);

    // Execute
    qd_execute(ctx, "game::hello");

    // Cleanup
    qd_free_context(ctx);
    return 0;
}

Compile with:

gcc -o myapp myapp.c -lqd -lqdrt -L/path/to/quadrate/dist/lib -I/path/to/quadrate/dist/include

Core Concepts

Contexts

A qd_context holds the runtime state: the stack, error state, and loaded modules.

qd_context* ctx = qd_create_context(1024);  // Stack capacity
// ... use context ...
qd_free_context(ctx);

Modules

Modules are containers for functions. Create or retrieve a module with qd_get_module:

qd_module* math = qd_get_module(ctx, "math");
qd_module* game = qd_get_module(ctx, "game");

Adding Scripts

Add Quadrate source code to a module:

qd_add_script(mod, "fn square(x:i64 -- result:i64) { dup * }");
qd_add_script(mod, "fn cube(x:i64 -- result:i64) { dup dup * * }");

You can call qd_add_script multiple times before building.

Building

Compile all added scripts:

qd_build(mod);

// Check if compilation succeeded
if (!qd_is_compiled(mod)) {
    fprintf(stderr, "Compilation failed!\n");
}

Executing Code

Execute Quadrate expressions:

qd_execute(ctx, "5 math::square print nl");  // Prints: 25
qd_execute(ctx, "3 math::cube print nl");    // Prints: 27

Registering Native Functions

The real power of embedding comes from exposing your application's functions to scripts.

Basic Native Function

Native functions take a qd_context* and return qd_exec_result:

#include <qd/qd.h>
#include <qdrt/runtime.h>

// Native function: pushes current time onto stack
qd_exec_result native_get_time(qd_context* ctx) {
    time_t now = time(NULL);
    return qd_push_i(ctx, (int64_t)now);
}

int main(void) {
    qd_context* ctx = qd_create_context(1024);
    qd_module* utils = qd_get_module(ctx, "utils");

    // Register the native function
    qd_register_function(utils, "get_time", (void(*)(void))native_get_time);

    qd_build(utils);

    // Call from Quadrate
    qd_execute(ctx, "utils::get_time print nl");

    qd_free_context(ctx);
    return 0;
}

Reading Arguments from the Stack

Use qd_stack_pop to read arguments:

#include <qdrt/stack.h>

// Native function: (a b -- sum)
qd_exec_result native_add(qd_context* ctx) {
    qd_stack_element_t b, a;

    // Pop in reverse order (b is on top)
    qd_stack_pop(ctx->st, &b);
    qd_stack_pop(ctx->st, &a);

    int64_t sum = a.value.i + b.value.i;
    return qd_push_i(ctx, sum);
}

Stack Element Types

The qd_stack_element_t structure:

typedef struct {
    qd_stack_type type;  // QD_STACK_TYPE_INT, _FLOAT, _STR, _PTR
    union {
        int64_t i;       // Integer value
        double f;        // Float value
        qd_string_t* s;  // String value (reference counted)
        void* p;         // Pointer value
    } value;
} qd_stack_element_t;

Push Functions

qd_push_i(ctx, 42);           // Push integer
qd_push_f(ctx, 3.14159);      // Push float
qd_push_s(ctx, "hello");      // Push string (copied)
qd_push_p(ctx, my_pointer);   // Push pointer

Tutorial: Embedding in a Game Engine

This tutorial shows how to use Quadrate as a scripting language for a simple game engine. We'll build a system where:

  1. Game entities have scriptable behaviors
  2. Scripts can access game state (positions, health, etc.)
  3. Scripts can call engine functions (spawn, damage, move)

Project Structure

my_game/
├── src/
│   ├── main.c
│   ├── engine.c
│   ├── engine.h
│   └── scripting.c
├── scripts/
│   ├── player.qd
│   └── enemy.qd
└── Makefile

Step 1: Define Game State

// engine.h
#ifndef ENGINE_H
#define ENGINE_H

#include <stdint.h>
#include <stdbool.h>

#define MAX_ENTITIES 100

typedef struct {
    int64_t id;
    double x, y;
    int64_t health;
    int64_t max_health;
    bool active;
    const char* script_module;  // Which Quadrate module controls this entity
} Entity;

typedef struct {
    Entity entities[MAX_ENTITIES];
    int entity_count;
    int64_t current_entity;  // Entity being updated (for script access)
    double delta_time;
} GameState;

extern GameState g_game;

Entity* engine_spawn(double x, double y, int64_t health, const char* script);
Entity* engine_get_entity(int64_t id);
void engine_damage(int64_t id, int64_t amount);
void engine_move(int64_t id, double dx, double dy);

#endif

Step 2: Implement Engine Functions

// engine.c
#include "engine.h"
#include <stdio.h>
#include <string.h>

GameState g_game = {0};

Entity* engine_spawn(double x, double y, int64_t health, const char* script) {
    if (g_game.entity_count >= MAX_ENTITIES) return NULL;

    Entity* e = &g_game.entities[g_game.entity_count];
    e->id = g_game.entity_count;
    e->x = x;
    e->y = y;
    e->health = health;
    e->max_health = health;
    e->active = true;
    e->script_module = script;

    g_game.entity_count++;
    printf("[Engine] Spawned entity %lld at (%.1f, %.1f)\n", e->id, x, y);
    return e;
}

Entity* engine_get_entity(int64_t id) {
    if (id < 0 || id >= g_game.entity_count) return NULL;
    return &g_game.entities[id];
}

void engine_damage(int64_t id, int64_t amount) {
    Entity* e = engine_get_entity(id);
    if (!e || !e->active) return;

    e->health -= amount;
    printf("[Engine] Entity %lld took %lld damage (health: %lld)\n",
           id, amount, e->health);

    if (e->health <= 0) {
        e->active = false;
        printf("[Engine] Entity %lld destroyed!\n", id);
    }
}

void engine_move(int64_t id, double dx, double dy) {
    Entity* e = engine_get_entity(id);
    if (!e || !e->active) return;

    e->x += dx;
    e->y += dy;
}

Step 3: Create Script Bindings

// scripting.c
#include <qd/qd.h>
#include <qdrt/runtime.h>
#include <qdrt/stack.h>
#include "engine.h"
#include <stdio.h>
#include <math.h>

static qd_context* script_ctx = NULL;

// === Native functions exposed to scripts ===

// Get current entity's ID ( -- id)
qd_exec_result script_self(qd_context* ctx) {
    return qd_push_i(ctx, g_game.current_entity);
}

// Get entity position (id -- x y)
qd_exec_result script_get_pos(qd_context* ctx) {
    qd_stack_element_t id_elem;
    qd_stack_pop(ctx->st, &id_elem);

    Entity* e = engine_get_entity(id_elem.value.i);
    if (!e) {
        qd_push_f(ctx, 0.0);
        qd_push_f(ctx, 0.0);
        return (qd_exec_result){0};
    }

    qd_push_f(ctx, e->x);
    return qd_push_f(ctx, e->y);
}

// Get entity health (id -- health)
qd_exec_result script_get_health(qd_context* ctx) {
    qd_stack_element_t id_elem;
    qd_stack_pop(ctx->st, &id_elem);

    Entity* e = engine_get_entity(id_elem.value.i);
    return qd_push_i(ctx, e ? e->health : 0);
}

// Move entity (id dx dy --)
qd_exec_result script_move(qd_context* ctx) {
    qd_stack_element_t dy_elem, dx_elem, id_elem;
    qd_stack_pop(ctx->st, &dy_elem);
    qd_stack_pop(ctx->st, &dx_elem);
    qd_stack_pop(ctx->st, &id_elem);

    engine_move(id_elem.value.i, dx_elem.value.f, dy_elem.value.f);
    return (qd_exec_result){0};
}

// Damage entity (id amount --)
qd_exec_result script_damage(qd_context* ctx) {
    qd_stack_element_t amount_elem, id_elem;
    qd_stack_pop(ctx->st, &amount_elem);
    qd_stack_pop(ctx->st, &id_elem);

    engine_damage(id_elem.value.i, amount_elem.value.i);
    return (qd_exec_result){0};
}

// Spawn new entity (x y health -- id)
qd_exec_result script_spawn(qd_context* ctx) {
    qd_stack_element_t health_elem, y_elem, x_elem;
    qd_stack_pop(ctx->st, &health_elem);
    qd_stack_pop(ctx->st, &y_elem);
    qd_stack_pop(ctx->st, &x_elem);

    Entity* e = engine_spawn(x_elem.value.f, y_elem.value.f,
                             health_elem.value.i, NULL);
    return qd_push_i(ctx, e ? e->id : -1);
}

// Get delta time ( -- dt)
qd_exec_result script_delta_time(qd_context* ctx) {
    return qd_push_f(ctx, g_game.delta_time);
}

// Calculate distance between two points (x1 y1 x2 y2 -- dist)
qd_exec_result script_distance(qd_context* ctx) {
    qd_stack_element_t y2, x2, y1, x1;
    qd_stack_pop(ctx->st, &y2);
    qd_stack_pop(ctx->st, &x2);
    qd_stack_pop(ctx->st, &y1);
    qd_stack_pop(ctx->st, &x1);

    double dx = x2.value.f - x1.value.f;
    double dy = y2.value.f - y1.value.f;
    return qd_push_f(ctx, sqrt(dx*dx + dy*dy));
}

// Log a message (msg --)
qd_exec_result script_log(qd_context* ctx) {
    qd_stack_element_t msg;
    qd_stack_pop(ctx->st, &msg);

    if (msg.type == QD_STACK_TYPE_STR) {
        printf("[Script] %s\n", qd_string_data(msg.value.s));
        qd_string_release(msg.value.s);  // Release reference
    }
    return (qd_exec_result){0};
}

// === Script system initialization ===

void scripting_init(void) {
    script_ctx = qd_create_context(4096);

    // Create the 'engine' module with native functions
    qd_module* engine = qd_get_module(script_ctx, "engine");

    qd_register_function(engine, "self", (void(*)(void))script_self);
    qd_register_function(engine, "get_pos", (void(*)(void))script_get_pos);
    qd_register_function(engine, "get_health", (void(*)(void))script_get_health);
    qd_register_function(engine, "move", (void(*)(void))script_move);
    qd_register_function(engine, "damage", (void(*)(void))script_damage);
    qd_register_function(engine, "spawn", (void(*)(void))script_spawn);
    qd_register_function(engine, "delta_time", (void(*)(void))script_delta_time);
    qd_register_function(engine, "distance", (void(*)(void))script_distance);
    qd_register_function(engine, "log", (void(*)(void))script_log);

    qd_build(engine);
}

void scripting_load_module(const char* name, const char* source) {
    qd_module* mod = qd_get_module(script_ctx, name);
    qd_add_script(mod, source);
    qd_build(mod);

    if (!qd_is_compiled(mod)) {
        fprintf(stderr, "Failed to compile module: %s\n", name);
    }
}

void scripting_call_update(const char* module_name) {
    char call[256];
    snprintf(call, sizeof(call), "%s::update", module_name);
    qd_execute(script_ctx, call);
}

void scripting_shutdown(void) {
    qd_free_context(script_ctx);
    script_ctx = NULL;
}

Step 4: Write Game Scripts

// scripts/player.qd
// Player behavior script

// Called every frame
fn update() {
    engine::self -> id

    // Get current position
    id engine::get_pos -> y -> x

    // Simple movement: move right over time
    engine::delta_time 100.0 * -> speed
    id speed 0.0 engine::move

    // Log position every update
    "Player position: " print x print ", " print y print nl
}

fn take_damage(amount:i64 -- ) {
    engine::self -> id
    id amount engine::damage

    id engine::get_health -> hp
    hp 0 > if {
        "Ouch! Health remaining: " print hp print nl
    } else {
        "Player defeated!" engine::log
    }
}
// scripts/enemy.qd
// Enemy AI script

fn update() {
    engine::self -> id

    // Get own position
    id engine::get_pos -> my_y -> my_x

    // Get player position (assume player is entity 0)
    0 engine::get_pos -> player_y -> player_x

    // Calculate distance to player
    my_x my_y player_x player_y engine::distance -> dist

    // If close enough, attack!
    dist 50.0 < if {
        "Enemy attacks player!" engine::log
        0 10 engine::damage
    } else {
        // Move toward player
        player_x my_x - -> dx
        player_y my_y - -> dy

        // Normalize and scale by speed
        dist 0.001 + -> len  // Avoid divide by zero
        dx len / engine::delta_time * 50.0 * -> move_x
        dy len / engine::delta_time * 50.0 * -> move_y

        id move_x move_y engine::move
    }
}

Step 5: Main Game Loop

// main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "engine.h"

// Forward declarations from scripting.c
void scripting_init(void);
void scripting_load_module(const char* name, const char* source);
void scripting_call_update(const char* module_name);
void scripting_shutdown(void);

// Helper to load script file
char* load_file(const char* path) {
    FILE* f = fopen(path, "r");
    if (!f) return NULL;

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* buf = malloc(size + 1);
    fread(buf, 1, size, f);
    buf[size] = '\0';
    fclose(f);
    return buf;
}

int main(void) {
    printf("=== Game Engine with Quadrate Scripting ===\n\n");

    // Initialize scripting system
    scripting_init();

    // Load scripts
    char* player_script = load_file("scripts/player.qd");
    char* enemy_script = load_file("scripts/enemy.qd");

    if (player_script) {
        scripting_load_module("player", player_script);
        free(player_script);
    }
    if (enemy_script) {
        scripting_load_module("enemy", enemy_script);
        free(enemy_script);
    }

    // Spawn entities
    Entity* player = engine_spawn(0.0, 0.0, 100, "player");
    Entity* enemy = engine_spawn(100.0, 100.0, 50, "enemy");

    // Game loop (simplified - just 10 frames)
    printf("\n--- Starting game loop ---\n\n");

    for (int frame = 0; frame < 10; frame++) {
        printf("Frame %d:\n", frame);
        g_game.delta_time = 0.016;  // ~60 FPS

        // Update all entities
        for (int i = 0; i < g_game.entity_count; i++) {
            Entity* e = &g_game.entities[i];
            if (!e->active || !e->script_module) continue;

            g_game.current_entity = e->id;
            scripting_call_update(e->script_module);
        }

        printf("\n");
        usleep(100000);  // 100ms delay for demo
    }

    // Cleanup
    scripting_shutdown();
    printf("=== Game ended ===\n");

    return 0;
}

Step 6: Build and Run

# Makefile
QUADRATE_DIR = /path/to/quadrate/dist
CFLAGS = -I$(QUADRATE_DIR)/include -Wall
LDFLAGS = -L$(QUADRATE_DIR)/lib -lqd -lqdrt -lm -Wl,-rpath,$(QUADRATE_DIR)/lib

game: src/main.c src/engine.c src/scripting.c
    gcc $(CFLAGS) -o game $^ $(LDFLAGS)

clean:
    rm -f game

API Reference

Context Management

Function Description
qd_create_context(size) Create context with stack capacity
qd_free_context(ctx) Free context and all resources
qd_clone_context(ctx) Deep copy a context

Module Management

Function Description
qd_get_module(ctx, name) Get or create module
qd_add_script(mod, source) Add Quadrate source code
qd_register_function(mod, name, fn) Register native function
qd_build(mod) Compile the module
qd_is_compiled(mod) Check if compilation succeeded

Execution

Function Description
qd_execute(ctx, expr) Execute Quadrate expression

Stack Operations

Function Description
qd_push_i(ctx, val) Push integer
qd_push_f(ctx, val) Push float
qd_push_s(ctx, str) Push string (copied)
qd_push_p(ctx, ptr) Push pointer
qd_stack_pop(ctx->st, &elem) Pop element
qd_stack_peek(ctx->st, &elem) Peek top element

Stack Element Access

qd_stack_element_t elem;
qd_stack_pop(ctx->st, &elem);

switch (elem.type) {
    case QD_STACK_TYPE_INT:
        printf("Integer: %lld\n", elem.value.i);
        break;
    case QD_STACK_TYPE_FLOAT:
        printf("Float: %f\n", elem.value.f);
        break;
    case QD_STACK_TYPE_STR:
        printf("String: %s\n", qd_string_data(elem.value.s));
        qd_string_release(elem.value.s);  // Don't forget!
        break;
    case QD_STACK_TYPE_PTR:
        printf("Pointer: %p\n", elem.value.p);
        break;
}

Best Practices

  1. Error Handling: Always check qd_is_compiled() after building modules.

  2. String Memory: When popping strings, call qd_string_release() when done.

  3. Stack Balance: Native functions must leave the stack balanced according to their signature.

  4. Thread Safety: Each thread should have its own qd_context.

  5. Hot Reloading: To reload scripts, create a new module with the same name and rebuild.

Common Patterns

Returning Multiple Values

// Native: ( -- x y z)
qd_exec_result get_vector(qd_context* ctx) {
    qd_push_f(ctx, 1.0);  // x
    qd_push_f(ctx, 2.0);  // y
    return qd_push_f(ctx, 3.0);  // z
}

Passing Callbacks

// Quadrate side
fn my_callback(x:i64 -- result:i64) { x 2 * }
fn process() {
    10 fn(x:i64 -- r:i64) { x 2 * } engine::with_callback
}

Error Propagation

qd_exec_result native_might_fail(qd_context* ctx) {
    if (error_condition) {
        // Set error state
        ctx->error_code = 1;
        return (qd_exec_result){.error = 1};
    }
    return qd_push_i(ctx, result);
}