Language Bindings

mino exposes a plain C ABI with simple types: pointers, integers, doubles, and null-terminated strings. Any language with C FFI support can embed it directly, with no wrapper library or code generation step.

Each example creates a runtime, evaluates mino code, and extracts the result. The same pattern works from every host language with C FFI.

C

The baseline reference. Direct API calls, no translation layer.

embed.c
/*
 * embed.c - minimal embedding example for mino.
 *
 * Demonstrates: creating a runtime, registering a host function,
 * evaluating mino code, and extracting C values from the result.
 *
 * Build:
 *   cc -std=c99 -I.. -o embed embed.c ../mino.c
 * Run:
 *   ./embed
 */

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

/* A host function exposed to mino as (add-tax amount). */
static mino_val *host_add_tax(mino_state *S, mino_val *args, mino_env *env)
{
    long long amount;
    (void)env;
    if (!mino_is_cons(args) || !mino_to_int(mino_car(args), &amount)) {
        return mino_nil(S);
    }
    return mino_float(S, (double)amount * 1.08);
}

int main(void)
{
    mino_state *S   = mino_state_new();
    mino_env   *env = mino_env_new_default(S);       /* env + core in one call */

    /* Register a host-defined function. */
    mino_register_fn(S, env, "add-tax", host_add_tax);

    /* Evaluate mino source that calls the host function. */
    mino_val *result = mino_eval_string(S,
        "(def prices [100 200 300])\n"
        "(loop [i 0 total 0.0]\n"
        "  (if (< i (count prices))\n"
        "      (recur (+ i 1) (+ total (add-tax (nth prices i))))\n"
        "      total))\n",
        env);

    /* Extract and use the result from C. */
    if (result == NULL) {
        fprintf(stderr, "error: %s\n", mino_last_error(S));
    } else {
        double total;
        if (mino_to_float(result, &total)) {
            printf("total with tax: %.2f\n", total);
        }
    }

    /* Demonstrate the in-process REPL handle: feed lines one at a time,
     * collecting results as complete forms become available. */
    {
        mino_repl *repl = mino_repl_new(S, env);
        mino_val  *out  = NULL;
        int          rc;

        /* Single-line form. */
        rc = mino_repl_feed(repl, "(+ 1 2)\n", &out);
        if (rc == MINO_REPL_OK && out != NULL) {
            printf("repl: ");
            mino_println(S, out);
        }

        /* Multi-line form: first line is incomplete. */
        rc = mino_repl_feed(repl, "(* 3\n", &out);
        if (rc == MINO_REPL_MORE) {
            printf("repl: awaiting more input...\n");
        }
        rc = mino_repl_feed(repl, "   4)\n", &out);
        if (rc == MINO_REPL_OK && out != NULL) {
            printf("repl: ");
            mino_println(S, out);
        }

        mino_repl_free(repl);
    }

    mino_env_free(S, env);
    mino_state_free(S);
    return 0;
}

C++

Same API calls with C++17 patterns: auto, range-for, lambdas. The extern "C" guards in mino.h mean no wrapper is needed.

embed.cpp
/*
 * embed.cpp -- C++ host interop with mino.
 *
 * Build (run from the mino-examples root, with the mino source
 * tree available as a sibling at ../mino):
 *
 *   c++ -std=c++17 -O2 \
 *     -I../mino/src -I../mino/src/public -I../mino/src/runtime \
 *     -I../mino/src/gc -I../mino/src/eval -I../mino/src/collections \
 *     -I../mino/src/prim -I../mino/src/async -I../mino/src/interop \
 *     -I../mino/src/diag -I../mino/src/vendor/imath \
 *     -o embed src/embed.cpp \
 *     ../mino/src/public/*.c ../mino/src/runtime/*.c \
 *     ../mino/src/gc/*.c ../mino/src/eval/*.c \
 *     ../mino/src/collections/*.c ../mino/src/prim/*.c \
 *     ../mino/src/async/*.c ../mino/src/interop/*.c \
 *     ../mino/src/regex/*.c ../mino/src/diag/*.c \
 *     ../mino/src/vendor/imath/*.c -lm
 */

#include "mino.h"
#include <cstdio>
#include <vector>

// --- Host type: a simple accumulator ---

struct Accumulator {
    std::vector<double> values;
    double total = 0.0;
};

static mino_val *acc_new(mino_state *S, mino_val *,
                           mino_val *, void *) {
    return mino_handle_ex(S, new Accumulator, "Accumulator",
        [](void *p, const char *) { delete static_cast<Accumulator *>(p); });
}

static mino_val *acc_add(mino_state *S, mino_val *target,
                           mino_val *args, void *) {
    auto *a = static_cast<Accumulator *>(mino_handle_ptr(target));
    double v;
    mino_to_float(mino_car(args), &v);
    a->values.push_back(v);
    a->total += v;
    return target;
}

static mino_val *acc_total(mino_state *S, mino_val *target,
                             mino_val *, void *) {
    return mino_float(S,
        static_cast<Accumulator *>(mino_handle_ptr(target))->total);
}

static mino_val *acc_count(mino_state *S, mino_val *target,
                             mino_val *, void *) {
    return mino_int(S, static_cast<long long>(
        static_cast<Accumulator *>(mino_handle_ptr(target))->values.size()));
}

int main() {
    mino_state *S   = mino_state_new();
    mino_env   *env = mino_env_new_default(S);

    // Register the Accumulator type with mino.
    mino_host_enable(S);
    mino_host_register_ctor(S, "Accumulator", 0, acc_new, nullptr);
    mino_host_register_method(S, "Accumulator", "add", 1, acc_add, nullptr);
    mino_host_register_getter(S, "Accumulator", "total", acc_total, nullptr);
    mino_host_register_getter(S, "Accumulator", "count", acc_count, nullptr);

    // mino code uses dot-syntax and tail-call recursion.
    mino_val *result = mino_eval_string(S,
        "(defn add-all [acc items]       \n"
        "  (if (empty? items)            \n"
        "    acc                         \n"
        "    (do (.add acc (first items))\n"
        "        (add-all acc (rest items)))))\n"
        "                               \n"
        "(let [acc (new Accumulator)]    \n"
        "  (add-all acc [10 20 30 40 50])\n"
        "  (/ (.-total acc)             \n"
        "     (.-count acc)))           \n",
        env);

    double avg;
    if (result && mino_to_float(result, &avg))
        printf("average: %.1f\n", avg);   // => average: 30.0

    mino_env_free(S, env);
    mino_state_free(S);
}

Java (JNI)

A thin JNI bridge maps native methods to the mino C API. State and environment handles are passed as Java longs.

MinoEmbed.java
/*
 * MinoEmbed.java — embedding mino from Java via JNI.
 *
 * A thin JNI bridge exposes the core mino operations: create a
 * runtime, evaluate code, and extract results. The same event
 * processing scenario runs through the mino script.
 *
 * Build:
 *   make
 *   # Compile Java
 *   javac -d examples/bindings examples/bindings/MinoEmbed.java
 *   # Build the JNI shared library
 *   cc -std=c99 -shared -fPIC -Isrc \
 *      -I"$(java -XshowSettings:properties 2>&1 | grep java.home | awk '{print $3}')/include" \
 *      -I"$(java -XshowSettings:properties 2>&1 | grep java.home | awk '{print $3}')/include/darwin" \
 *      -o examples/bindings/libminojni.dylib \
 *      examples/bindings/mino_jni.c src/[a-z]*.o -lm
 *   # Run
 *   java -Djava.library.path=examples/bindings -cp examples/bindings MinoEmbed
 */

public class MinoEmbed {

    /* Native methods bridging to the mino C API. */
    private static native long   stateNew();
    private static native long   envNew(long state);
    private static native void   envFree(long state, long env);
    private static native void   stateFree(long state);
    private static native String evalString(long state, String src, long env);
    private static native String lastError(long state);

    static {
        System.loadLibrary("minojni");
    }

    /* Processing script: same as the C and C++ examples. */
    private static final String SCRIPT =
        "(defn avg [xs]\n" +
        "  (/ (reduce + xs) (count xs)))\n" +
        "\n" +
        "(defn summarize [[device readings]]\n" +
        "  [device {:count (count readings)\n" +
        "           :avg   (avg (map :value readings))}])\n" +
        "\n" +
        "(->> events\n" +
        "     (filter #(= (:type %) :temp))\n" +
        "     (group-by :device)\n" +
        "     (map summarize)\n" +
        "     (into (sorted-map)))\n";

    /* Event data built as a mino vector literal. */
    private static final String EVENTS =
        "(def events\n" +
        "  [{:type :temp     :device \"sensor-01\" :value 21.3 :ts 1000}\n" +
        "   {:type :humidity :device \"sensor-01\" :value 45.0 :ts 1001}\n" +
        "   {:type :temp     :device \"sensor-02\" :value 19.8 :ts 1002}\n" +
        "   {:type :temp     :device \"sensor-01\" :value 22.1 :ts 1003}\n" +
        "   {:type :temp     :device \"sensor-02\" :value 20.4 :ts 1004}\n" +
        "   {:type :temp     :device \"sensor-01\" :value 22.9 :ts 1005}])\n";

    public static void main(String[] args) {
        long state = stateNew();
        long env   = envNew(state);

        /* Define event data from a mino literal. */
        String r = evalString(state, EVENTS, env);
        if (r == null) {
            System.err.println("error: " + lastError(state));
            return;
        }

        /* Run the processing script. */
        String result = evalString(state, SCRIPT, env);
        if (result != null) {
            System.out.println("result: " + result);
        } else {
            System.err.println("error: " + lastError(state));
        }

        envFree(state, env);
        stateFree(state);
    }
}
mino_jni.c (JNI bridge)
/*
 * mino_jni.c — JNI bridge for the mino C API.
 *
 * Maps Java native methods in MinoEmbed to their mino equivalents.
 * State and env handles are passed as Java longs (opaque pointers).
 * eval_string returns the printed representation of the result.
 */

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

/* Capture printed representation into a C string. */
static char print_buf[4096];

static void val_to_string(mino_state *S, const mino_val *v)
{
    FILE *f = tmpfile();
    if (!f) { print_buf[0] = '\0'; return; }
    mino_print_to(S, f, v);
    long len = ftell(f);
    if (len < 0) len = 0;
    if ((size_t)len >= sizeof(print_buf)) len = sizeof(print_buf) - 1;
    rewind(f);
    fread(print_buf, 1, (size_t)len, f);
    print_buf[len] = '\0';
    fclose(f);
}

/* Java_MinoEmbed_stateNew */
JNIEXPORT jlong JNICALL
Java_MinoEmbed_stateNew(JNIEnv *jenv, jclass cls)
{
    (void)jenv; (void)cls;
    return (jlong)(uintptr_t)mino_state_new();
}

/* Java_MinoEmbed_envNew */
JNIEXPORT jlong JNICALL
Java_MinoEmbed_envNew(JNIEnv *jenv, jclass cls, jlong state)
{
    (void)jenv; (void)cls;
    return (jlong)(uintptr_t)mino_env_new_default(
        (mino_state *)(uintptr_t)state);
}

/* Java_MinoEmbed_envFree */
JNIEXPORT void JNICALL
Java_MinoEmbed_envFree(JNIEnv *jenv, jclass cls, jlong state, jlong env)
{
    (void)jenv; (void)cls;
    mino_env_free((mino_state *)(uintptr_t)state,
                  (mino_env *)(uintptr_t)env);
}

/* Java_MinoEmbed_stateFree */
JNIEXPORT void JNICALL
Java_MinoEmbed_stateFree(JNIEnv *jenv, jclass cls, jlong state)
{
    (void)jenv; (void)cls;
    mino_state_free((mino_state *)(uintptr_t)state);
}

/* Java_MinoEmbed_evalString */
JNIEXPORT jstring JNICALL
Java_MinoEmbed_evalString(JNIEnv *jenv, jclass cls,
                          jlong state, jstring src, jlong env)
{
    (void)cls;
    mino_state *S = (mino_state *)(uintptr_t)state;
    mino_env   *E = (mino_env *)(uintptr_t)env;
    const char *csrc = (*jenv)->GetStringUTFChars(jenv, src, NULL);

    mino_val *result = mino_eval_string(S, csrc, E);
    (*jenv)->ReleaseStringUTFChars(jenv, src, csrc);

    if (result == NULL)
        return NULL;

    /* Print result to buffer. */
    val_to_string(S, result);

    return (*jenv)->NewStringUTF(jenv, print_buf);
}

/* Java_MinoEmbed_lastError */
JNIEXPORT jstring JNICALL
Java_MinoEmbed_lastError(JNIEnv *jenv, jclass cls, jlong state)
{
    (void)cls;
    const char *err = mino_last_error((mino_state *)(uintptr_t)state);
    return err ? (*jenv)->NewStringUTF(jenv, err) : NULL;
}

Other languages

Since mino is a plain C library, most systems languages can call it directly through their standard FFI mechanisms. Below are short sketches showing how the call looks in each language.

Zig

@cImport reads mino.h at compile time.

const mino = @cImport({
    @cInclude("mino.h");
});

pub fn main() !void {
    const S = mino.mino_state_new();
    defer mino.mino_state_free(S);

    const env = mino.mino_env_new_default(S);
    defer mino.mino_env_free(S, env);

    const result = mino.mino_eval_string(S,
        "(+ 1 2)", env);
    if (result) |r| mino.mino_println(S, r);
}

Rust

extern "C" block with unsafe wrappers. A safe Rust API could be layered on top.

extern "C" {
    fn mino_state_new() -> *mut MinoState;
    fn mino_state_free(s: *mut MinoState);
    fn mino_env_new_default(s: *mut MinoState) -> *mut MinoEnv;
    fn mino_env_free(s: *mut MinoState, e: *mut MinoEnv);
    fn mino_eval_string(
        s: *mut MinoState, src: *const c_char,
        e: *mut MinoEnv,
    ) -> *mut MinoVal;
    fn mino_println(s: *mut MinoState, v: *const MinoVal);
}

fn main() {
    unsafe {
        let s = mino_state_new();
        let e = mino_env_new_default(s);
        let src = CString::new("(+ 1 2)").unwrap();
        let r = mino_eval_string(s, src.as_ptr(), e);
        if !r.is_null() { mino_println(s, r); }
        mino_env_free(s, e);
        mino_state_free(s);
    }
}

C# / .NET

P/Invoke with [DllImport] attributes. Works on .NET Framework, .NET Core, and Mono.

using System.Runtime.InteropServices;

class Mino {
    [DllImport("mino")] static extern IntPtr mino_state_new();
    [DllImport("mino")] static extern void mino_state_free(IntPtr s);
    [DllImport("mino")] static extern IntPtr mino_env_new_default(IntPtr s);
    [DllImport("mino")] static extern void mino_env_free(
        IntPtr s, IntPtr e);
    [DllImport("mino")] static extern IntPtr mino_eval_string(
        IntPtr s, string src, IntPtr e);
    [DllImport("mino")] static extern void mino_println(
        IntPtr s, IntPtr v);

    static void Main() {
        var s = mino_state_new();
        var e = mino_env_new_default(s);
        var r = mino_eval_string(s, "(+ 1 2)", e);
        if (r != IntPtr.Zero) mino_println(s, r);
        mino_env_free(s, e);
        mino_state_free(s);
    }
}

Go

cgo with #include "mino.h" in a magic comment. The Go runtime handles the C-to-Go boundary.

// #cgo LDFLAGS: -lmino -lm
// #include "mino.h"
import "C"

func main() {
    s := C.mino_state_new()
    defer C.mino_state_free(s)

    e := C.mino_env_new_default(s)
    defer C.mino_env_free(s, e)

    src := C.CString("(+ 1 2)")
    defer C.free(unsafe.Pointer(src))

    r := C.mino_eval_string(s, src, e)
    if r != nil { C.mino_println(s, r) }
}

Swift

A C bridging header exposes the mino API to Swift. Optional return types map naturally to nullable pointers.

// Bridging header: #include "mino.h"

let s = mino_state_new()!
defer { mino_state_free(s) }

let e = mino_env_new_default(s)!
defer { mino_env_free(s, e) }

if let r = mino_eval_string(s, "(+ 1 2)", e) {
    mino_println(s, r)
}