Embedding Cookbook

Six worked examples showing real-world embedding patterns. Each includes the full annotated C source with build instructions.

Load structured configuration from a .mino file

Demonstrates: sandboxed eval (no I/O), extracting C values from a map, using keywords as config keys.
Build: cc -std=c99 -I.. -o config config.c ../mino.c
config.c — full source
/*
 * config.c — load structured configuration from a .mino file.
 *
 * Demonstrates: sandboxed eval (no I/O), extracting C values from a map,
 * using keywords as config keys.
 *
 * Build:  cc -std=c99 -I.. -o config config.c ../mino.c
 * Run:    ./config
 */

#include "mino.h"
#include <stdio.h>

/*
 * A .mino config file is just a map literal:
 *
 *   {:port 8080
 *    :host "0.0.0.0"
 *    :debug true
 *    :workers 4
 *    :routes [{:path "/api" :handler "api_handler"}
 *             {:path "/"    :handler "static_handler"}]}
 */
static const char *config_src =
    "{:port 8080\n"
    " :host \"0.0.0.0\"\n"
    " :debug true\n"
    " :workers 4\n"
    " :routes [{:path \"/api\" :handler \"api_handler\"}\n"
    "          {:path \"/\"    :handler \"static_handler\"}]}";

int main(void)
{
    mino_env_t *env = mino_env_new();
    mino_val_t *cfg;
    mino_val_t *val;

    /* Core bindings only — no I/O. The config file cannot read files,
     * print, or access the network. */
    mino_install_core(env);

    /* Evaluate the config source. */
    cfg = mino_eval_string(config_src, env);
    if (cfg == NULL) {
        fprintf(stderr, "config error: %s\n", mino_last_error());
        return 1;
    }

    /* Bind the config map so we can query it from mino expressions. */
    mino_env_set(env, "cfg", cfg);
    val = mino_eval_string("(get cfg :port)", env);
    if (val != NULL) {
        long long port;
        if (mino_to_int(val, &port)) {
            printf("port: %lld\n", port);
        }
    }

    val = mino_eval_string("(get cfg :host)", env);
    if (val != NULL) {
        const char *host;
        size_t      len;
        if (mino_to_string(val, &host, &len)) {
            printf("host: %.*s\n", (int)len, host);
        }
    }

    val = mino_eval_string("(get cfg :debug)", env);
    if (val != NULL) {
        printf("debug: %s\n", mino_to_bool(val) ? "on" : "off");
    }

    /* Iterate over routes. */
    val = mino_eval_string("(get cfg :routes)", env);
    if (val != NULL) {
        long long count;
        mino_val_t *c = mino_eval_string("(count (get cfg :routes))", env);
        if (c != NULL && mino_to_int(c, &count)) {
            long long i;
            printf("routes (%lld):\n", count);
            for (i = 0; i < count; i++) {
                char expr[128];
                mino_val_t *route;
                mino_val_t *path;
                snprintf(expr, sizeof(expr),
                         "(nth (get cfg :routes) %lld)", i);
                route = mino_eval_string(expr, env);
                if (route == NULL) continue;
                mino_env_set(env, "__r", route);
                path = mino_eval_string("(get __r :path)", env);
                if (path != NULL) {
                    const char *s;
                    size_t      n;
                    if (mino_to_string(path, &s, &n)) {
                        printf("  %.*s\n", (int)n, s);
                    }
                }
            }
        }
    }

    mino_env_free(env);
    return 0;
}

Game scripting console

Demonstrates: mutable host state exposed through handles and host functions, execution limits to prevent runaway scripts, the REPL handle for interactive command entry.
Build: cc -std=c99 -I.. -o console console.c ../mino.c
console.c — full source
/*
 * console.c — game scripting console.
 *
 * Demonstrates: mutable host state exposed through handles and host
 * functions, execution limits to prevent runaway scripts, the REPL
 * handle for interactive command entry.
 *
 * Build:  cc -std=c99 -I.. -o console console.c ../mino.c
 * Run:    ./console
 */

#include "mino.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* --- Simulated game world ----------------------------------------------- */

typedef struct {
    double x, y;
    int    hp;
    int    max_hp;
    const char *name;
} entity_t;

static entity_t player = { 0.0, 0.0, 100, 100, "player" };

/* --- Host functions ----------------------------------------------------- */

static mino_val_t *host_pos(mino_val_t *args, mino_env_t *env)
{
    mino_val_t *items[2];
    (void)args; (void)env;
    items[0] = mino_float(player.x);
    items[1] = mino_float(player.y);
    return mino_vector(items, 2);
}

static mino_val_t *host_move(mino_val_t *args, mino_env_t *env)
{
    double dx, dy;
    (void)env;
    if (!mino_is_cons(args) || !mino_is_cons(args->as.cons.cdr)) {
        return mino_nil();
    }
    if (!mino_to_float(args->as.cons.car, &dx)) {
        long long ix;
        if (mino_to_int(args->as.cons.car, &ix)) dx = (double)ix;
        else return mino_nil();
    }
    if (!mino_to_float(args->as.cons.cdr->as.cons.car, &dy)) {
        long long iy;
        if (mino_to_int(args->as.cons.cdr->as.cons.car, &iy)) dy = (double)iy;
        else return mino_nil();
    }
    player.x += dx;
    player.y += dy;
    return host_pos(args, env);
}

static mino_val_t *host_hp(mino_val_t *args, mino_env_t *env)
{
    (void)args; (void)env;
    return mino_int(player.hp);
}

static mino_val_t *host_heal(mino_val_t *args, mino_env_t *env)
{
    long long amount;
    (void)env;
    if (mino_is_cons(args) && mino_to_int(args->as.cons.car, &amount)) {
        player.hp += (int)amount;
        if (player.hp > player.max_hp) player.hp = player.max_hp;
    }
    return mino_int(player.hp);
}

static mino_val_t *host_damage(mino_val_t *args, mino_env_t *env)
{
    long long amount;
    (void)env;
    if (mino_is_cons(args) && mino_to_int(args->as.cons.car, &amount)) {
        player.hp -= (int)amount;
        if (player.hp < 0) player.hp = 0;
    }
    return mino_int(player.hp);
}

static mino_val_t *host_status(mino_val_t *args, mino_env_t *env)
{
    mino_val_t *keys[3], *vals[3];
    (void)args; (void)env;
    keys[0] = mino_keyword("pos");
    vals[0] = host_pos(args, env);
    keys[1] = mino_keyword("hp");
    vals[1] = mino_int(player.hp);
    keys[2] = mino_keyword("name");
    vals[2] = mino_string(player.name);
    return mino_map(keys, vals, 3);
}

int main(void)
{
    mino_env_t  *env  = mino_new();

    /* Install I/O so scripts can use println. */
    mino_install_io(env);

    /* Limit scripts to 100k eval steps to prevent infinite loops. */
    mino_set_limit(MINO_LIMIT_STEPS, 100000);

    /* Register game functions. */
    mino_register_fn(env, "pos",    host_pos);
    mino_register_fn(env, "move",   host_move);
    mino_register_fn(env, "hp",     host_hp);
    mino_register_fn(env, "heal",   host_heal);
    mino_register_fn(env, "damage", host_damage);
    mino_register_fn(env, "status", host_status);

    /* Define some convenience commands in mino. */
    mino_eval_string(
        "(def teleport (fn (x y)\n"
        "  (move (- x (first (pos)))\n"
        "        (- y (nth (pos) 1)))))\n"
        "(def full-heal (fn () (heal 9999)))\n",
        env);

    /* Run a scripted demo instead of an interactive loop. */
    printf("=== game console demo ===\n\n");

    {
        static const char *commands[] = {
            "(status)",
            "(move 5 3)",
            "(damage 30)",
            "(heal 10)",
            "(teleport 100 200)",
            "(full-heal)",
            "(status)",
        };
        size_t i;
        for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) {
            mino_val_t *result;
            printf("> %s\n", commands[i]);
            result = mino_eval_string(commands[i], env);
            if (result == NULL) {
                printf("error: %s\n", mino_last_error());
            } else {
                mino_println(result);
            }
            printf("\n");
        }
    }

    mino_env_free(env);
    return 0;
}

Transform data through mino expressions

Demonstrates: pushing C data into mino as vectors/maps, running sequence operations, extracting transformed results back to C. Shows how mino can serve as a lightweight ETL / data-wrangling layer.
Build: cc -std=c99 -I.. -o pipeline pipeline.c ../mino.c
pipeline.c — full source
/*
 * pipeline.c — transform data through mino expressions.
 *
 * Demonstrates: pushing C data into mino as vectors/maps, running
 * sequence operations, extracting transformed results back to C.
 * Shows how mino can serve as a lightweight ETL / data-wrangling layer.
 *
 * Build:  cc -std=c99 -I.. -o pipeline pipeline.c ../mino.c
 * Run:    ./pipeline
 */

#include "mino.h"
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    mino_env_t *env = mino_new();
    mino_val_t *result;

    /* Build a vector of employee records from C data. */
    {
        static const char *names[]  = { "Alice", "Bob", "Carol", "Dave" };
        static const int   ages[]   = { 30, 45, 28, 52 };
        static const int salaries[] = { 75000, 92000, 68000, 110000 };
        size_t i;
        size_t n = sizeof(names) / sizeof(names[0]);
        mino_val_t **records = (mino_val_t **)malloc(n * sizeof(*records));

        for (i = 0; i < n; i++) {
            mino_val_t *keys[3], *vals[3];
            keys[0] = mino_keyword("name");
            vals[0] = mino_string(names[i]);
            keys[1] = mino_keyword("age");
            vals[1] = mino_int(ages[i]);
            keys[2] = mino_keyword("salary");
            vals[2] = mino_int(salaries[i]);
            records[i] = mino_map(keys, vals, 3);
        }
        mino_env_set(env, "employees", mino_vector(records, n));
        free(records);
    }

    /* Pipeline 1: names of employees over 35. */
    printf("Over 35:\n");
    result = mino_eval_string(
        "(->> employees\n"
        "     (filter (fn (e) (> (get e :age) 35)))\n"
        "     (map (fn (e) (get e :name))))",
        env);
    if (result != NULL) {
        printf("  ");
        mino_println(result);
    }

    /* Pipeline 2: total salary budget. */
    result = mino_eval_string(
        "(reduce + 0 (map (fn (e) (get e :salary)) employees))",
        env);
    if (result != NULL) {
        long long total;
        if (mino_to_int(result, &total)) {
            printf("Total salary: %lld\n", total);
        }
    }

    /* Pipeline 3: sorted by salary descending. */
    printf("By salary (desc):\n");
    result = mino_eval_string(
        "(->> employees\n"
        "     (sort)\n"
        "     (reverse)\n"
        "     (map (fn (e) (str (get e :name) \": $\" (get e :salary)))))",
        env);
    if (result != NULL) {
        /* Walk the result list and print each line. */
        mino_val_t *p = result;
        while (mino_is_cons(p)) {
            const char *s;
            size_t      len;
            if (mino_to_string(p->as.cons.car, &s, &len)) {
                printf("  %.*s\n", (int)len, s);
            }
            p = p->as.cons.cdr;
        }
    }

    /* Pipeline 4: group into age brackets using into + map. */
    result = mino_eval_string(
        "(let (senior   (filter (fn (e) (>= (get e :age) 40)) employees)\n"
        "      junior   (filter (fn (e) (<  (get e :age) 40)) employees))\n"
        "  {:senior (map (fn (e) (get e :name)) senior)\n"
        "   :junior (map (fn (e) (get e :name)) junior)})",
        env);
    if (result != NULL) {
        printf("Age groups: ");
        mino_println(result);
    }

    mino_env_free(env);
    return 0;
}

A plugin host that loads .mino files and calls into them

Demonstrates: module loading, calling mino functions from C with mino_call, the resolver callback, sandboxed vs full environments.
Build: cc -std=c99 -I.. -o plugin plugin.c ../mino.c
plugin.c — full source
/*
 * plugin.c — a plugin host that loads .mino files and calls into them.
 *
 * Demonstrates: module loading, calling mino functions from C with
 * mino_call, the resolver callback, sandboxed vs full environments.
 *
 * Build:  cc -std=c99 -I.. -o plugin plugin.c ../mino.c
 * Run:    ./plugin
 *
 * In this example the "plugins" are defined inline for portability,
 * but a real host would load them from disk via mino_load_file.
 */

#include "mino.h"
#include <stdio.h>
#include <string.h>

/* Inline plugin source (normally loaded from a file). */
static const char *greeter_plugin =
    "(def greet\n"
    "  (fn (name)\n"
    "    (str \"Hello, \" name \"!\")))\n"
    "\n"
    "(def farewell\n"
    "  (fn (name)\n"
    "    (str \"Goodbye, \" name \".\")))";

static const char *math_plugin =
    "(def square (fn (x) (* x x)))\n"
    "(def cube   (fn (x) (* x x x)))\n"
    "(def hypot  (fn (a b)\n"
    "              (let (a2 (square a)\n"
    "                    b2 (square b))\n"
    "                (reduce + 0 (list a2 b2)))))\n";

/* Call a named function in env with a single argument. */
static mino_val_t *call1(mino_env_t *env, const char *name, mino_val_t *arg)
{
    mino_val_t *fn   = mino_env_get(env, name);
    mino_val_t *args = mino_cons(arg, mino_nil());
    if (fn == NULL) {
        fprintf(stderr, "plugin: function '%s' not found\n", name);
        return NULL;
    }
    return mino_call(fn, args, env);
}

static mino_val_t *call2(mino_env_t *env, const char *name,
                         mino_val_t *a, mino_val_t *b)
{
    mino_val_t *fn   = mino_env_get(env, name);
    mino_val_t *args = mino_cons(a, mino_cons(b, mino_nil()));
    if (fn == NULL) {
        fprintf(stderr, "plugin: function '%s' not found\n", name);
        return NULL;
    }
    return mino_call(fn, args, env);
}

int main(void)
{
    mino_env_t *env = mino_new();
    mino_val_t *result;

    /* Load the greeter plugin. */
    if (mino_eval_string(greeter_plugin, env) == NULL) {
        fprintf(stderr, "load error: %s\n", mino_last_error());
        return 1;
    }

    /* Call plugin functions from C. */
    result = call1(env, "greet", mino_string("World"));
    if (result != NULL) {
        const char *s;
        size_t      n;
        if (mino_to_string(result, &s, &n)) {
            printf("%.*s\n", (int)n, s);
        }
    }

    result = call1(env, "farewell", mino_string("World"));
    if (result != NULL) {
        const char *s;
        size_t      n;
        if (mino_to_string(result, &s, &n)) {
            printf("%.*s\n", (int)n, s);
        }
    }

    /* Load the math plugin. */
    if (mino_eval_string(math_plugin, env) == NULL) {
        fprintf(stderr, "load error: %s\n", mino_last_error());
        return 1;
    }

    result = call1(env, "square", mino_int(7));
    if (result != NULL) {
        long long v;
        if (mino_to_int(result, &v)) {
            printf("square(7) = %lld\n", v);
        }
    }

    result = call2(env, "hypot", mino_int(3), mino_int(4));
    if (result != NULL) {
        long long v;
        if (mino_to_int(result, &v)) {
            printf("hypot(3,4) = %lld (sum of squares)\n", v);
        }
    }

    /* Demonstrate protected call — catches errors gracefully. */
    {
        mino_val_t *fn  = mino_env_get(env, "square");
        mino_val_t *bad = mino_cons(mino_string("oops"), mino_nil());
        mino_val_t *out = NULL;
        int rc = mino_pcall(fn, bad, env, &out);
        if (rc != 0) {
            printf("pcall caught: %s\n", mino_last_error());
        }
    }

    mino_env_free(env);
    return 0;
}

A repl served over a tcp socket

Demonstrates: the in-process REPL handle (mino_repl_*), I/O opt-in, hosting a REPL from an event loop without threads.
Build: cc -std=c99 -I.. -o repl_socket repl_socket.c ../mino.c
repl_socket.c — full source
/*
 * repl_socket.c — a REPL served over a TCP socket.
 *
 * Demonstrates: the in-process REPL handle (mino_repl_*), I/O opt-in,
 * hosting a REPL from an event loop without threads.
 *
 * Build:  cc -std=c99 -I.. -o repl_socket repl_socket.c ../mino.c
 * Run:    ./repl_socket        (listens on port 7100)
 *         rlwrap nc 127.0.0.1 7100
 *
 * Single-client for simplicity; a real host would use poll/epoll/kqueue.
 */

#include "mino.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 7100
#define BUFSZ 4096

static void send_str(int fd, const char *s)
{
    size_t len = strlen(s);
    (void)write(fd, s, len);
}

static void send_prompt(int fd, int continuation)
{
    send_str(fd, continuation ? "..> " : "=> ");
}

int main(void)
{
    int         srv, cli;
    struct sockaddr_in addr;
    mino_env_t *env;
    mino_repl_t *repl;
    char         buf[BUFSZ];

    /* Set up the listening socket. */
    srv = socket(AF_INET, SOCK_STREAM, 0);
    if (srv < 0) { perror("socket"); return 1; }
    {
        int opt = 1;
        setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    }
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_port        = htons(PORT);
    if (bind(srv, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind"); return 1;
    }
    listen(srv, 1);
    printf("listening on 127.0.0.1:%d\n", PORT);

    /* Accept one client. */
    cli = accept(srv, NULL, NULL);
    if (cli < 0) { perror("accept"); return 1; }
    printf("client connected\n");

    /* Create the mino runtime with full capabilities. */
    env = mino_new();
    mino_install_io(env);
    repl = mino_repl_new(env);

    send_str(cli, "mino REPL\n");
    send_prompt(cli, 0);

    /* Read lines and feed them to the REPL. */
    for (;;) {
        ssize_t n = read(cli, buf, sizeof(buf) - 1);
        if (n <= 0) break;
        buf[n] = '\0';

        /* Feed line-by-line. */
        {
            char *line = buf;
            char *nl;
            while ((nl = strchr(line, '\n')) != NULL) {
                mino_val_t *out = NULL;
                int         rc;
                *nl = '\0';
                rc = mino_repl_feed(repl, line, &out);
                switch (rc) {
                case MINO_REPL_OK:
                    if (out != NULL) {
                        /* Print the result back to the client. */
                        char repr[4096];
                        int  len;
                        /* Use a tmpfile to capture printed output. */
                        {
                            FILE *tmp = tmpfile();
                            if (tmp != NULL) {
                                mino_print_to(tmp, out);
                                len = (int)ftell(tmp);
                                if (len > 0 && len < (int)sizeof(repr)) {
                                    rewind(tmp);
                                    (void)fread(repr, 1, (size_t)len, tmp);
                                    repr[len] = '\0';
                                    send_str(cli, repr);
                                }
                                fclose(tmp);
                            }
                        }
                        send_str(cli, "\n");
                    }
                    send_prompt(cli, 0);
                    break;
                case MINO_REPL_MORE:
                    send_prompt(cli, 1);
                    break;
                case MINO_REPL_ERROR:
                    send_str(cli, "error: ");
                    send_str(cli, mino_last_error());
                    send_str(cli, "\n");
                    send_prompt(cli, 0);
                    break;
                }
                line = nl + 1;
            }
        }
    }

    printf("client disconnected\n");
    mino_repl_free(repl);
    mino_env_free(env);
    close(cli);
    close(srv);
    return 0;
}

A tiny rules engine driven by mino expressions

Demonstrates: registering host functions, evaluating user-defined predicates, using mino as a decision layer between C data and actions.
Build: cc -std=c99 -I.. -o rules rules.c ../mino.c
rules.c — full source
/*
 * rules.c — a tiny rules engine driven by mino expressions.
 *
 * Demonstrates: registering host functions, evaluating user-defined
 * predicates, using mino as a decision layer between C data and actions.
 *
 * Build:  cc -std=c99 -I.. -o rules rules.c ../mino.c
 * Run:    ./rules
 */

#include "mino.h"
#include <stdio.h>
#include <string.h>

/* --- Simulated application state ---------------------------------------- */

typedef struct {
    const char *user;
    int         age;
    int         purchases;
    double      balance;
} customer_t;

static customer_t current_customer;

/* Host functions exposed to the rules engine. */

static mino_val_t *host_age(mino_val_t *args, mino_env_t *env)
{
    (void)args; (void)env;
    return mino_int(current_customer.age);
}

static mino_val_t *host_purchases(mino_val_t *args, mino_env_t *env)
{
    (void)args; (void)env;
    return mino_int(current_customer.purchases);
}

static mino_val_t *host_balance(mino_val_t *args, mino_env_t *env)
{
    (void)args; (void)env;
    return mino_float(current_customer.balance);
}

/* --- Rules are mino expressions returning a discount tier keyword ------- */

static const char *rules_src =
    "(def discount-tier\n"
    "  (fn ()\n"
    "    (cond\n"
    "      (> (purchases) 50) :gold\n"
    "      (> (purchases) 10) :silver\n"
    "      (> (age) 65)       :senior\n"
    "      :else              :standard)))\n";

int main(void)
{
    mino_env_t *env = mino_new();
    mino_val_t *result;

    /* Register host accessors. */
    mino_register_fn(env, "age",       host_age);
    mino_register_fn(env, "purchases", host_purchases);
    mino_register_fn(env, "balance",   host_balance);

    /* Load the rules. */
    if (mino_eval_string(rules_src, env) == NULL) {
        fprintf(stderr, "rules error: %s\n", mino_last_error());
        return 1;
    }

    /* Evaluate for different customers. */
    {
        static const customer_t customers[] = {
            { "Alice",  30,  5,  120.0 },
            { "Bob",    70, 12, 3400.0 },
            { "Carol",  25, 55,  800.0 },
        };
        size_t i;
        for (i = 0; i < sizeof(customers)/sizeof(customers[0]); i++) {
            current_customer = customers[i];
            result = mino_eval_string("(discount-tier)", env);
            if (result == NULL) {
                fprintf(stderr, "eval error: %s\n", mino_last_error());
                continue;
            }
            printf("%-8s -> ", customers[i].user);
            mino_println(result);
        }
    }

    mino_env_free(env);
    return 0;
}