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.cconfig.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.cconsole.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.cpipeline.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.cplugin.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.crepl_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.crules.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;
}