Interactive Console

In-app REPL for live inspection, scripted commands, and step limits.

← Back to use cases

Full source

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

/*
 * console.cpp - in-app scripting console.
 *
 * An application embeds a command console where users type mino
 * expressions to inspect and control the running system. The host
 * registers query and action functions. Step limits prevent runaway
 * scripts from blocking the application.
 *
 * Build:
 *   make
 *   c++ -std=c++17 -Isrc -o examples/use-cases/console \
 *       examples/use-cases/console.cpp src/[a-z]*.o -lm
 */

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

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

/* Simulated application state: a key-value store. The console
 * exposes read/write access through registered functions. */

struct AppState {
    struct Entry { char key[64]; mino_val_t *val; };
    Entry entries[64];
    int   count = 0;
};

static AppState app;

static mino_val_t *host_get(mino_state_t *S, mino_val_t *args,
                            mino_env_t *)
{
    const char *key;
    size_t len;
    if (!mino_is_cons(args) ||
        !mino_to_string(mino_car(args), &key, &len))
        return mino_nil(S);
    for (int i = 0; i < app.count; i++) {
        if (strncmp(app.entries[i].key, key, len) == 0 &&
            app.entries[i].key[len] == '\0')
            return app.entries[i].val;
    }
    return mino_nil(S);
}

static mino_val_t *host_put(mino_state_t *S, mino_val_t *args,
                            mino_env_t *)
{
    const char *key;
    size_t len;
    if (!mino_is_cons(args) ||
        !mino_to_string(mino_car(args), &key, &len) ||
        !mino_is_cons(mino_cdr(args)))
        return mino_nil(S);
    mino_val_t *val = mino_car(mino_cdr(args));

    /* Update existing or append. */
    for (int i = 0; i < app.count; i++) {
        if (strncmp(app.entries[i].key, key, len) == 0 &&
            app.entries[i].key[len] == '\0') {
            app.entries[i].val = val;
            return val;
        }
    }
    if (app.count < 64) {
        snprintf(app.entries[app.count].key, 64, "%.*s", (int)len, key);
        app.entries[app.count].val = val;
        app.count++;
    }
    return val;
}

static mino_val_t *host_keys(mino_state_t *S, mino_val_t *,
                             mino_env_t *)
{
    mino_val_t *items[64];
    for (int i = 0; i < app.count; i++)
        items[i] = mino_string(S, app.entries[i].key);
    return mino_vector(S, items, (size_t)app.count);
}

static mino_val_t *host_dump(mino_state_t *S, mino_val_t *,
                             mino_env_t *)
{
    mino_val_t *ks[64], *vs[64];
    for (int i = 0; i < app.count; i++) {
        ks[i] = mino_string(S, app.entries[i].key);
        vs[i] = app.entries[i].val;
    }
    return mino_map(S, ks, vs, (size_t)app.count);
}

/* ── Script ────────────────────────────────────────────────────────── */

/* Convenience commands defined in mino, building on the host
 * primitives. These are loaded once at startup. */

static const char *prelude =
    "(defn batch-put [pairs]\n"
    "  (->> pairs\n"
    "       (map (fn [[k v]] (put k v)))\n"
    "       (count)))\n"
    "\n"
    "(defn search [pattern]\n"
    "  (->> (store-keys)\n"
    "       (filter (fn [k] (includes? k pattern)))\n"
    "       (map (fn [k] [k (get-val k)]))))\n";

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

int main()
{
    mino_state_t *S   = mino_state_new();
    mino_env_t   *env = mino_env_new_default(S);

    /* Step limit: prevent runaway scripts. */
    mino_set_limit(S, MINO_LIMIT_STEPS, 100000);

    /* Register host functions. */
    mino_register_fn(S, env, "get-val",    host_get);
    mino_register_fn(S, env, "put",        host_put);
    mino_register_fn(S, env, "store-keys", host_keys);
    mino_register_fn(S, env, "dump",       host_dump);

    /* Load the prelude. */
    if (!mino_eval_string(S, prelude, env)) {
        fprintf(stderr, "prelude error: %s\n", mino_last_error(S));
        return 1;
    }

    /* Simulate a console session. */
    printf("=== console session ===\n\n");

    static const char *commands[] = {
        "(put \"version\" \"2.7.1\")",
        "(put \"max-connections\" 256)",
        "(put \"log-level\" :info)",
        "(dump)",
        "(batch-put [[\"db-host\" \"10.0.1.5\"] [\"db-port\" 5432]])",
        "(search \"db\")",
        "(get-val \"max-connections\")",
    };

    for (auto *cmd : commands) {
        printf("> %s\n", cmd);
        mino_val_t *result = mino_eval_string(S, cmd, env);
        if (result) {
            mino_println(S, result);
        } else {
            printf("error: %s\n", mino_last_error(S));
        }
        printf("\n");
    }

    /* Demonstrate step limit enforcement. */
    printf("> (loop [] (recur))   ; infinite loop\n");
    mino_val_t *r = mino_eval_string(S, "(loop [] (recur))", env);
    if (!r)
        printf("error: %s\n", mino_last_error(S));

    mino_env_free(S, env);
    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/console \
  use-cases/console.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/console

The mino script

Convenience commands defined in mino, building on the host primitives. These are loaded once at startup.

(defn batch-put [pairs]
  (->> pairs
       (map (fn [[k v]] (put k v)))
       (count)))

(defn search [pattern]
  (->> (store-keys)
       (filter (fn [k] (includes? k pattern)))
       (map (fn [k] [k (get-val k)]))))

← All use cases