An embeddable Clojure-inspired Lisp.
Written in portable C99.
Cooperative async by default, with host-granted threading when needed.
Small enough to drop in. Fast enough to be useful.
Three tiers — Floor (the minimum embedder commits to), Sandbox (canonical Clojure-core surface plus safe bundled libs), and Standalone (the Homebrew bottle). Each card shows stripped footprint with the JIT pipeline included (the default), the in-process init cost from mino_state_new through the install call, and the JIT-vs-no-JIT delta — JIT costs 34-50 KB across the tiers and effectively zero cold-start ms. Measured on Apple Silicon (arm64-darwin) against mino v0.323.0.
Reader, evaluator, GC, persistent collections, numeric ops, foundational macros. No core.clj eval. Call mino_install_minimal and you are running. JIT-less variant ships at 601 KB.
Floor plus regex, bignum, multimethods, protocols, transducers, and the safe bundled libs — every name a Clojure scripter expects. Call mino_install_sandbox or mino_install(S, env, MINO_CAP_DEFAULT). JIT-less variant ships at 909 KB.
Sandbox plus I/O, FS, STM, agents, async, bundled clojure.* stdlib, REPL, crash handler. The released Homebrew bottle. JIT-less variant is the mino-lean sibling binary at 962 KB.
Cold start (full process spawn, +JIT): Floor 4.0 ms, Sandbox 7.0 ms, Standalone 8.0 ms (median of 50 runs each). Disabling the JIT shifts none of those by more than the measurement noise floor. The JIT pays back 1.8-6.5x on compute-bound hot code — see JIT for the workload table, Performance for per-operation costs, footprint detail, and capability opt-in mechanics.
C, C++, Rust, Go, Java, .NET, Swift, Zig, and beyond. Link the library, create a runtime, evaluate scripts. No external runtime, no daemon. An in-process copy-and-patch JIT speeds hot functions on every supported host arch and can be switched off per state; the parallel mino-lean build ships the interpreter only when a smaller binary is the priority. The host owns the process and calls in when it wants the script to run.
Each runtime is a failure domain with its own heap and garbage collector. Cross-runtime data moves by deep copy. Run many tenants in one process without shared mutable state.
Fresh runtimes start with no I/O, no filesystem, and no ambient state. The embedder opts in to each capability and registers exactly the host types and methods scripts can call.
Persistent immutable data structures, lazy sequences, macros, and a REPL-driven workflow. mino is Clojure-inspired today and continues to close canon gaps over time.
Three roles, one runtime
The application developer embeds mino. The C++ engineer exposes host types. The scripter writes logic.
A fresh runtime starts with zero capabilities. No file access, no network, no ambient globals to remove. The host opts in to exactly what the script can reach. Types are registered declaratively by name, arity, and role (constructor, method, getter) rather than pushing individual functions onto a stack. Each runtime is fully isolated so multiple runtimes can run on separate threads with no shared state.
// Create a runtime and register a C++ type.
mino_state_t *S = mino_state_new();
mino_env_t *env = mino_env_new(S);
mino_install(S, env, MINO_CAP_DEFAULT | MINO_CAP_HOST);
mino_host_enable(S);
mino_host_register_ctor(S, "EventSource", 0, source_new, NULL);
mino_host_register_method(S, "EventSource", "next", 0, source_next, NULL);
mino_host_register_getter(S, "EventSource", "count", source_count, NULL);
// Evaluate a mino script, extract the result.
mino_val_t *result = mino_eval_string(S, script, env);
if (result)
mino_println(S, result);Host objects are wrapped as type-tagged handles with automatic GC cleanup. The API uses direct value pointers rather than stack indices, so there is no stack balancing and no off-by-one indexing errors. Data returned to the script is immutable. The script cannot mutate your state through the values you hand it, and you never need to defensively copy data at the boundary.
// Wrap a C++ object as a mino handle.
static mino_val_t *source_new(mino_state_t *S, mino_val_t *,
mino_val_t *, void *) {
auto *src = new EventSource;
src->events.push_back(make_event(S, "temp", "sensor-01", 21.3));
src->events.push_back(make_event(S, "temp", "sensor-02", 19.8));
// ...
return mino_handle_ex(S, src, "EventSource",
[](void *p, const char *) { delete (EventSource *)p; });
}
// Return the next event as a mino map, or nil.
static mino_val_t *source_next(mino_state_t *S, mino_val_t *target,
mino_val_t *, void *) {
auto *src = (EventSource *)mino_handle_ptr(target);
if (src->cursor >= src->events.size())
return mino_nil(S);
return src->events[src->cursor++];
}The scripter writes processing rules without knowing anything about C++. Every value flowing through the pipeline is immutable with structural sharing, so there are no aliasing bugs and no "who mutated my table" surprises. Keywords like :device double as data accessors. The set #{:temp} is used directly as a filter predicate because collections are callable. drain recurses in constant stack space via automatic tail-call optimization. Change the processing logic without recompiling.
;; Consume all events into a vector via self-recursion.
(defn drain [source acc]
(let [evt (.next source)]
(if (nil? evt)
acc
(drain source (conj acc evt)))))
;; Summarize a [device readings] group.
(defn summarize [[device readings]]
[device {:count (count readings)
:avg (/ (reduce + (map :value readings))
(count readings))}])
;; Filter, group, summarize.
(defn analyze [events type-filter]
(->> events
(filter #(type-filter (:type %)))
(group-by :device)
(map summarize)
(into (sorted-map))))
(let [events (drain (new EventSource) [])]
(analyze events #{:temp}))
;; => {"sensor-01" {:count 4, :avg 22.0}
;; "sensor-02" {:count 3, :avg 19.9}}Use cases
Sandboxed evaluation of structured config with computed values, conditionals, and host queries.
Rules enginesHost state exposed to mino predicates for declarative business logic, validation, and policy.
PluginsLoad user scripts with controlled capabilities and resource limits. No ambient access.
Interactive consolesIn-app REPLs for live inspection, debugging, and runtime configuration.
Data pipelinesCompose map, filter, reduce over persistent collections with structural sharing.
Filter, group, and aggregate streams of host data. Change the rules without recompiling.
Game scriptingEmbed a programmable console with sandboxing and step limits for player-authored code.
AutomationUser-defined workflows over host APIs with full macro and REPL support.