Plugins

Load user scripts with controlled capabilities and error isolation.

← Back to use cases

Full source

This is a self-contained C++ program. Copy it, compile against the mino library, and run it.

/*
 * plugins.cpp - load user scripts with controlled capabilities.
 *
 * A document processing system where C++ provides the document model
 * and plugins define transformation rules. Each plugin runs in its
 * own sandboxed environment with only the capabilities it needs.
 * The host calls plugin functions directly via mino_call.
 *
 * Build:
 *   make
 *   c++ -std=c++17 -Isrc -o examples/use-cases/plugins \
 *       examples/use-cases/plugins.cpp src/[a-z]*.o -lm
 */

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

/* ── Expose ────────────────────────────────────────────────────────── */

/* Documents are mino maps with `:title`, `:body`, `:tags` keys.
 * Plugins receive immutable documents and return transformed copies.
 * The plugin cannot modify the original through the value it receives. */

static mino_val_t *make_document(mino_state_t *S,
                                 const char *title,
                                 const char *body,
                                 const std::vector<const char *> &tags)
{
    std::vector<mino_val_t *> tag_vals;
    for (auto *t : tags)
        tag_vals.push_back(mino_keyword(S, t));
    mino_val_t *tag_vec = mino_vector(S, tag_vals.data(), tag_vals.size());

    mino_val_t *ks[3], *vs[3];
    ks[0] = mino_keyword(S, "title"); vs[0] = mino_string(S, title);
    ks[1] = mino_keyword(S, "body");  vs[1] = mino_string(S, body);
    ks[2] = mino_keyword(S, "tags");  vs[2] = tag_vec;
    return mino_map(S, ks, vs, 3);
}

/* ── Script (plugin 1: metadata enrichment) ────────────────────────── */

static const char *metadata_plugin =
    ";; Add word count and reading time to a document.\n"
    "(defn enrich [doc]\n"
    "  (let [words (count (split (:body doc) \" \"))\n"
    "        minutes (max 1 (quot words 200))]\n"
    "    (-> doc\n"
    "        (assoc :word-count words)\n"
    "        (assoc :reading-time minutes))))\n";

/* ── Script (plugin 2: tag-based filtering) ────────────────────────── */

static const char *filter_plugin =
    ";; Keep only documents matching a tag set.\n"
    "(defn match? [doc required-tags]\n"
    "  (some required-tags (:tags doc)))\n"
    "\n"
    "(defn filter-docs [docs required-tags]\n"
    "  (->> docs\n"
    "       (filter (fn [d] (match? d required-tags)))\n"
    "       (mapv :title)))\n";

/* ── Embed ─────────────────────────────────────────────────────────── */

/* Helper: call a mino function by name. */
static mino_val_t *call1(mino_state_t *S, 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(S, arg, mino_nil(S));
    return fn ? mino_call(S, fn, args, env) : nullptr;
}

static mino_val_t *call2(mino_state_t *S, 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(S, a, mino_cons(S, b, mino_nil(S)));
    return fn ? mino_call(S, fn, args, env) : nullptr;
}

int main()
{
    mino_state_t *S = mino_state_new();

    /* Build documents from the C++ side. Root each one so the GC
     * cannot collect them while subsequent allocations happen. */
    mino_ref_t *r1 = mino_ref(S, make_document(S, "Getting Started",
        "This guide walks through the initial setup process for new users",
        {"guide", "beginner"}));
    mino_ref_t *r2 = mino_ref(S, make_document(S, "API Reference",
        "Complete reference for all public functions and types in the system",
        {"reference", "api"}));
    mino_ref_t *r3 = mino_ref(S, make_document(S, "Performance Tuning",
        "Advanced techniques for optimizing throughput and reducing latency in production deployments",
        {"guide", "advanced"}));

    mino_val_t *doc_items[] = {mino_deref(r1), mino_deref(r2), mino_deref(r3)};
    mino_ref_t *docs_ref = mino_ref(S, mino_vector(S, doc_items, 3));
    mino_unref(S, r1);
    mino_unref(S, r2);
    mino_unref(S, r3);

    /* Plugin 1: metadata enrichment (sandboxed, no I/O). */
    {
        mino_env_t *env = mino_env_new(S);
        mino_install(S, env, MINO_CAP_DEFAULT);

        if (!mino_eval_string(S, metadata_plugin, env)) {
            fprintf(stderr, "plugin load error: %s\n", mino_last_error(S));
            return 1;
        }

        printf("=== metadata plugin ===\n");
        mino_env_set(S, env, "docs", mino_deref(docs_ref));
        mino_val_t *enriched = mino_eval_string(S,
            "(mapv enrich docs)", env);
        if (enriched)
            mino_println(S, enriched);

        mino_env_free(S, env);
    }

    /* Plugin 2: tag filter (separate sandbox). */
    {
        mino_env_t *env = mino_env_new(S);
        mino_install(S, env, MINO_CAP_DEFAULT);

        if (!mino_eval_string(S, filter_plugin, env)) {
            fprintf(stderr, "plugin load error: %s\n", mino_last_error(S));
            return 1;
        }

        printf("\n=== filter plugin ===\n");
        mino_val_t *tag_items[] = {mino_keyword(S, "guide")};
        mino_val_t *tags = mino_set(S, tag_items, 1);
        mino_val_t *result = call2(S, env, "filter-docs",
                                   mino_deref(docs_ref), tags);
        if (result) {
            printf("guides: ");
            mino_println(S, result);
        }

        mino_env_free(S, env);
    }

    /* Protected call: demonstrate error isolation. */
    {
        mino_env_t *env = mino_env_new(S);
        mino_install(S, env, MINO_CAP_DEFAULT);
        mino_eval_string(S, metadata_plugin, env);

        printf("\n=== error isolation ===\n");
        mino_val_t *bad = mino_string(S, "not a document");
        mino_val_t *out = nullptr;
        int rc = mino_pcall(S, mino_env_get(env, "enrich"),
                            mino_cons(S, bad, mino_nil(S)), env, &out,
                            nullptr);
        if (rc != 0)
            printf("caught: %s\n", mino_last_error(S));

        mino_env_free(S, env);
    }

    mino_unref(S, docs_ref);
    mino_state_free(S);
}
Build and run:
c++ -std=c++17 -O2 \
  -Imino/src -Imino/src/public -Imino/src/runtime -Imino/src/gc -Imino/src/eval \
  -Imino/src/collections -Imino/src/prim -Imino/src/async -Imino/src/interop \
  -Imino/src/diag -Imino/src/vendor/imath \
  -o use-cases/plugins \
  use-cases/plugins.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
./use-cases/plugins

← All use cases