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)
}