# calog

**C: Any Language, One Gateway.**

calog embeds scripting into a C or C++ program. You register your native C functions
**once**, and they become callable from **any** embedded engine — Lua, JavaScript, Scheme,
Squirrel, Wren, Berry, Ruby, or MY-BASIC — with values passed through a single canonical type. Add a new
scripting language to your app by linking one more archive; your native functions don't
change.

```c
#include "calog.h"

CalogT *calog = calogCreate();
calogRegister(calog, "hostLog", hostLog, NULL);       // your C function, once

CalogContextT *ctx = calogContextOpen(calog, &calogJsEngine);
calogContextEval(ctx, "hostLog('hello from JavaScript, ' + 6 * 7)");
```

Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
`&calogS7Engine`, `&calogWrenEngine`, `&calogBerryEngine`, `&calogMrubyEngine`, or `&calogMyBasicEngine` — nothing else changes.

---

## Why calog

- **One API, many languages.** Register a native once; call it from every engine.
  `calog.h` is the entire public surface.
- **One value type.** Everything crosses the boundary as a `CalogValueT` — nil, bool,
  int, real, binary-safe string, a hybrid list/map aggregate, or a script function
  value. No per-engine glue in your code.
- **Natives run on your thread, serialized.** A script calling a native is marshalled to
  the host thread and run there during `calogPump()`, one at a time — so your C code is
  never called concurrently and needs **no locking**. (An opt-in escape hatch runs a
  native inline on the script's thread when you want it.)
- **Fire-and-forget scripts.** Scripts run on their own context threads; the host stays
  responsive and drives everything from its own loop.
- **Callbacks both ways.** A script can hand you a function value to keep and call later
  (routed back to the engine that owns it); and you can hand a native to a script as a
  callable value with `calogFnFromNative`.
- **Many runtimes.** Independent `CalogT` runtimes coexist in one process; one host
  thread can drive several.
- **Load by filename.** `calogContextLoad(calog, "config")` finds `config.lua` /
  `config.js` / `config.nut` / `config.bas` and runs it on the matching engine.
- **Reproducible & rigorous.** Every engine is vendored and built from source (no system
  packages). The core is warning-clean on gcc **and** clang (`-Wall -Wextra -Werror
  -Wconversion -Wsign-conversion`) and runs clean under AddressSanitizer, UBSan, and
  ThreadSanitizer.

## Engines

| Engine        | Vtable                 | Extension | Notes                                   |
|---------------|------------------------|-----------|-----------------------------------------|
| Lua 5.4       | `calogLuaEngine`       | `.lua`    |                                         |
| JavaScript    | `calogJsEngine`        | `.js`     | QuickJS-ng (ES2023+, BigInt → int64)    |
| Squirrel 3.2  | `calogSquirrelEngine`  | `.nut`    | C++ VM                                  |
| MY-BASIC      | `calogMyBasicEngine`   | `.bas`    | interpreters serialize at load          |
| Scheme        | `calogS7Engine`        | `.scm`    | s7; 64-bit ints                         |
| Wren          | `calogWrenEngine`      | `.wren`   | doubles only; call via `Calog.call(…)` |
| Berry         | `calogBerryEngine`     | `.be`     | scalars + callbacks                     |
| Ruby          | `calogMrubyEngine`     | `.rb`     | mruby; 64-bit ints; built by mruby's Rake |

A native can return a keyed `CalogValueT` record and **every** engine reads its fields with
native syntax (`user.name`, `user["name"]`, `(user "name")`), and a script can hand a
list/map *back* to C on **every** engine. (Wren's C API can't enumerate a map's keys, so
calog carries a small documented patch to the vendored Wren that adds one.) A host function
value also crosses *into* a script on **every** engine, where the script gets a callable it
can invoke.

You can also bring your own: `CalogEngineT` is a public four-function vtable.

---

## Build

Requirements: a POSIX system (pthreads), `gcc` or `clang`, and GNU `make`. Developed on
Linux; macOS should work, Windows via WSL.

```sh
make            # builds lib/libcalog.a, the per-engine archives, tests, and the example
make test       # runs the full suite under AddressSanitizer + UBSan
```

The engines are vendored under `vendor/` and compiled from source, so the build is
self-contained.

### Linking your program

Link against `lib/libcalog.a` plus the archive for **each engine you actually use** —
unused engines (and their vendored runtimes) stay out of your binary.

```sh
# a JavaScript-only host:
cc -Isrc myhost.c lib/libcalog.a lib/libquickjs.a -pthread -lm -o myhost
```

Per-engine link needs:

| Engine   | Archive             | Extra libs (Linux) |
|----------|---------------------|--------------------|
| Lua      | `lib/liblua.a`      | `-ldl -lm`         |
| JS       | `lib/libquickjs.a`  | `-lm`              |
| Squirrel | `lib/libsquirrel.a` | `-lstdc++ -lm`     |
| MY-BASIC | `lib/libmybasic.a`  | `-lm`              |
| Scheme   | `lib/libs7.a`       | `-ldl -lm`         |
| Wren     | `lib/libwren.a`     | `-lm`              |
| Berry    | `lib/libberry.a`    | `-lm`              |

Everything links with `-pthread`.

---

## Running scripts with `calog`

`make` also builds **`bin/calog`**, a ready-to-run interpreter that links every engine and
library. Point it at one or more script files:

```sh
bin/calog hello.lua                 # run on the engine its extension selects
bin/calog config                    # no extension -> search config.lua / .js / .nut / .bas / .be / .scm / .wren
bin/calog producer.js consumer.lua  # several files share one runtime (kv, pubsub, exports)
```

Scripts print with `calogPrint(...)` and end by calling `calogExit([code])` (calog is
event-driven, so a script asks to exit; `Ctrl-C` also works). [`API.md`](API.md) documents
every native a script can call; [`examples/scripts/`](examples/scripts/) has runnable
examples across every engine and library, plus polyglot and multi-file demos.

---

## A complete example

```c
#include "calog.h"

#include <stdio.h>
#include <time.h>

// A native the host provides. It runs on the host thread during calogPump, serialized
// with every other native, so it can touch host state without locking.
static int32_t hostLog(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    (void)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogStringE) {
        return calogFail(result, calogErrArgE, "hostLog expects one string");
    }
    printf("[host] %s\n", args[0].as.s.bytes);
    return calogOkE;
}

int main(void) {
    CalogT *calog = calogCreate();
    calogRegister(calog, "hostLog", hostLog, NULL);

    CalogContextT *ctx = calogContextOpen(calog, &calogJsEngine);
    calogContextEval(ctx, "hostLog('hello from JavaScript, ' + (6 * 7))");

    // The script runs on its own thread and calls hostLog; the host services those
    // calls by pumping in its own loop. (A real app pumps inside its main loop.)
    struct timespec tick = { 0, 1000000 };  // 1 ms
    for (int i = 0; i < 50; i++) {
        calogPump(calog);
        nanosleep(&tick, NULL);
    }

    calogContextClose(ctx);
    calogDestroy(calog);
    return 0;
}
```

See [`examples/embed.c`](examples/embed.c) for this, ready to build (`make` produces
`bin/embed`).

---

## How it works

### The canonical value

Every value crossing the C/script boundary is a `CalogValueT`:

```c
typedef enum { calogNilE, calogBoolE, calogIntE, calogRealE,
               calogStringE, calogAggE, calogFnE } CalogTypeE;

struct CalogValueT {
    CalogTypeE type;
    union { bool b; int64_t i; double r;
            CalogStringT s; CalogAggT *agg; CalogFnT *fn; } as;
};
```

Build them with the constructors, and free owned values you're done with:

```c
CalogValueT v;
calogValueInt(&v, 42);
calogValueString(&v, "binary\0safe", 11);   // length-prefixed, NUL-safe
// ... calogValueBool / calogValueReal / calogValueAgg / calogValueFn ...
calogValueFree(&v);
```

`CalogAggT` is a hybrid **list + map** (`calogAggPush`, `calogAggSet`, `calogAggGet`),
so it round-trips onto engines that model sequences and dictionaries differently.
Strings are binary-safe (length-prefixed, always NUL-terminated for convenience).

### Registering natives

```c
// Runs on the HOST thread (serialized). This is what you want almost always.
calogRegister(calog, "add", add, NULL);

// Escape hatch: runs INLINE on the calling script's thread. Faster (no host hop),
// but YOU guarantee it is thread-safe.
calogRegisterInline(calog, "clockNow", clockNow, NULL);
```

A native has one signature and reports failure through `result`:

```c
static int32_t add(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    (void)userData;
    if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
        return calogFail(result, calogErrArgE, "add expects two integers");
    }
    calogValueInt(result, args[0].as.i + args[1].as.i);
    return calogOkE;
}
```

Every registered native is automatically visible in every context you open.

### Contexts, scripts, and the pump

A **context** is one engine instance on its own thread. `calogContextEval` is
**fire-and-forget**: it queues the script and returns immediately.

```c
CalogContextT *ctx = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(ctx, "hostLog('running')");   // returns now; script runs on ctx's thread
```

Because natives are serviced on the host thread, the host must **pump**:

```c
for (;;) {
    calogPump(calog);   // runs any pending native calls here, then returns
    do_other_host_work();
}
```

Script results come back by **calling natives** — `calogContextEval` doesn't return a
value. Script errors are delivered to an optional handler on the host thread during
`calogPump`:

```c
static void onError(uint64_t contextId, const char *message, void *userData) {
    fprintf(stderr, "script %llu failed: %s\n", (unsigned long long)contextId, message);
}
calogSetErrorHandler(calog, onError, NULL);   // default logs to stderr
```

### Callbacks: script functions as values

A script can pass you a function. Keep it (retain), call it later, release it when done —
calog runs the call on the engine that owns it and returns the result to you:

```c
static CalogFnT *savedCb = NULL;

static int32_t onEvent(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    (void)userData;
    calogValueNil(result);
    if (argCount != 1 || args[0].type != calogFnE) {
        return calogFail(result, calogErrArgE, "onEvent expects a function");
    }
    calogFnRetain(args[0].as.fn);     // keep it beyond this call
    savedCb = args[0].as.fn;
    return calogOkE;
}

// ...later, from the host:
CalogValueT arg, out;
calogValueInt(&arg, 42);
calogFnInvoke(savedCb, &arg, 1, &out);   // routed to the script's thread, result returned here
calogValueFree(&out);
calogValueFree(&arg);
calogFnRelease(savedCb);
```

It works the other way too: wrap one of your natives as a function value with
`calogFnFromNative` and return it from a native, and the script gets a callable it can
invoke (which routes back to your host thread). Every engine supports this; a script invokes
the value directly, except in my-basic, where it is called with `calogInvoke(fn, ...args)`.

```c
static int32_t getAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    CalogFnT *fn;
    (void)args; (void)argCount; (void)userData;
    calogValueNil(result);
    calogFnFromNative(&fn, calog, add, NULL);   // 'add' is one of your natives
    calogValueFn(result, fn);                   // the script can now call getAdder()(2, 3)
    return calogOkE;
}
```

### Loading a script by filename

Register the engines you want searchable (order is priority), then load by base name:

```c
calogRegisterEngine(calog, &calogLuaEngine);
calogRegisterEngine(calog, &calogJsEngine);

CalogContextT *ctx = calogContextLoad(calog, "config");  // tries config.lua, then config.js
```

Or let the build decide which engines exist. Compile the calling file with
`-DCALOG_WITH_LUA -DCALOG_WITH_JS …` and call the header-inline helper — it registers
exactly the engines you linked, and references no others:

```c
calogRegisterBuiltinEngines(calog);
CalogContextT *ctx = calogContextLoad(calog, "config");
```

### Multiple runtimes

A `CalogT` is a self-contained runtime; you can create several in one process. Each is
driven by a host thread, and one thread may drive several by pumping each in turn. Keep
values and callbacks within a single runtime — they don't cross between runtimes.

---

## API at a glance

```c
// runtime
CalogT *calogCreate(void);
void    calogDestroy(CalogT *);
void    calogPump(CalogT *);                                  // service native calls here
void    calogSetErrorHandler(CalogT *, CalogErrorFnT, void *);

// natives
int32_t calogRegister(CalogT *, const char *name, CalogNativeFnT, void *userData);        // host thread
int32_t calogRegisterInline(CalogT *, const char *name, CalogNativeFnT, void *userData);  // caller thread
int32_t calogCall(CalogT *, const char *name, CalogValueT *args, int32_t n, CalogValueT *result);
int32_t calogFail(CalogValueT *result, int32_t status, const char *message);
const char *calogTypeName(CalogTypeE);

// values
void    calogValueNil/Bool/Int/Real/Agg/Fn(...);
int32_t calogValueString(CalogValueT *, const char *bytes, int64_t length);
int32_t calogValueCopy(CalogValueT *dst, const CalogValueT *src);
void    calogValueMove(CalogValueT *dst, CalogValueT *src);
void    calogValueFree(CalogValueT *);
bool    calogValueEquals(const CalogValueT *, const CalogValueT *);

// aggregates (hybrid list + map)
int32_t      calogAggCreate(CalogAggT **, CalogKindE);
void         calogAggFree(CalogAggT *);
int32_t      calogAggPush(CalogAggT *, CalogValueT *value);
int32_t      calogAggSet(CalogAggT *, CalogValueT *key, CalogValueT *value);
CalogValueT *calogAggGet(CalogAggT *, const CalogValueT *key);

// function values (script callbacks)
int32_t calogFnInvoke(CalogFnT *, CalogValueT *args, int32_t n, CalogValueT *result);
void    calogFnRetain(CalogFnT *);
void    calogFnRelease(CalogFnT *);

// contexts + engines
CalogContextT *calogContextOpen(CalogT *, const CalogEngineT *);
void           calogRegisterEngine(CalogT *, const CalogEngineT *);
CalogContextT *calogContextLoad(CalogT *, const char *baseFileName);
int32_t        calogContextEval(CalogContextT *, const char *source);   // fire-and-forget
void           calogContextClose(CalogContextT *);
uint64_t       calogContextId(const CalogContextT *);
uint64_t       calogCurrentId(void);     // 0 on the host thread
CalogT        *calogCurrent(void);
```

Statuses are `CalogStatusE` (`calogOkE`, `calogErrArgE`, `calogErrOomE`, …); `calogOkE`
is 0.

---

## Testing

```sh
make test       # full suite under AddressSanitizer + UBSan
make tsan       # ThreadSanitizer: actor core + Lua
make tsansq     # ThreadSanitizer: Squirrel
make tsanjs     # ThreadSanitizer: JavaScript
make tsanmb     # ThreadSanitizer: MY-BASIC
```

The ThreadSanitizer targets run under `setarch -R` (ASLR off) so TSan's shadow allocator
is happy on all kernels.

## Project layout

```
src/            core (broker, values, actor) + one subdir per engine (lua, js, squirrel, mybasic)
  calog.h       the entire public API
tests/          the test programs
examples/       embed.c — a minimal host
vendor/         vendored engine sources, built from source
design.md       the full design rationale and internals
```

## Design

[`design.md`](design.md) is the long-form record: the canonical value model, the
actor/threading design, the function-value lifecycle, per-engine notes, and the
build system.

## License

calog is released under the [MIT License](LICENSE.md). The vendored engines (Lua,
Duktape, Squirrel, MY-BASIC) each retain their own MIT licenses — see
[`LICENSE.md`](LICENSE.md) for the third-party notices.
