// calog.h -- embed multi-language scripting into a C/C++ host. // // calog is a script broker: register native C functions once and call them from any // embedded engine (Lua, JavaScript/QuickJS, Squirrel, my-basic), passing values by a // single canonical type. This header is the entire public surface. A CalogT is a // self-contained runtime -- independent runtimes may coexist in one process, and one // host thread may drive several by pumping each in turn (don't share values across // them). See design.md. // // Threading model (design.md sec 6): // - Scripts run fire-and-forget on their own context threads; calogContextEval // returns immediately. // - A registered native runs on the HOST thread (the thread that calls // calogCreate/calogPump), serialized, so host C code is never called // concurrently and needs no locking. The host services those calls by calling // calogPump() in its own loop. // - calogRegisterInline is the escape hatch: an inline native runs directly on the // calling script's thread (no host hop) -- YOU guarantee it is thread-safe. // - A script error is delivered to the error handler (calogSetErrorHandler) on the // host thread during calogPump; script results come back by calling natives. #ifndef CALOG_H #define CALOG_H #include #include #include // Maximum nesting depth honored by the recursive copy/marshal paths. A deeper // structure, or a cycle, fails with calogErrDepthE; the free path relies on the same // invariant (aggregates must be acyclic). #define CALOG_MAX_DEPTH 64 // Growth multiplier for growable buffers. Must stay a power of two (the registry // derives its probe mask from a power-of-two slot count). #define CALOG_GROWTH_FACTOR 2 typedef enum CalogStatusE { calogOkE = 0, calogErrOomE = 1, calogErrDepthE = 2, calogErrTypeE = 3, calogErrNotFoundE = 4, calogErrArgE = 5, calogErrRangeE = 6, calogErrUnsupportedE = 7, calogErrDeadE = 8 } CalogStatusE; typedef enum CalogTypeE { calogNilE = 0, calogBoolE = 1, calogIntE = 2, calogRealE = 3, calogStringE = 4, calogAggE = 5, calogFnE = 6 } CalogTypeE; // Disambiguates an empty or mixed container so it round-trips deterministically onto // engines (such as my-basic) that model lists and dicts as distinct values. typedef enum CalogKindE { calogListE = 0, calogMapE = 1, calogBothE = 2 } CalogKindE; typedef struct CalogT CalogT; // the scripting runtime (registry + actor) typedef struct CalogContextT CalogContextT; // one engine instance on its own thread typedef struct CalogAggT CalogAggT; typedef struct CalogFnT CalogFnT; // a script function value (refcounted) typedef struct CalogPairT CalogPairT; typedef struct CalogStringT CalogStringT; typedef struct CalogValueT CalogValueT; // Length-prefixed, binary-safe string. bytes is always NUL-terminated at // bytes[length] so plain C consumers can read it, while length permits embedded NULs. struct CalogStringT { char *bytes; int64_t length; }; struct CalogValueT { CalogTypeE type; union { bool b; int64_t i; double r; CalogStringT s; CalogAggT *agg; CalogFnT *fn; } as; }; struct CalogPairT { CalogValueT key; CalogValueT value; }; // Hybrid container: the sequence part lives in array[0, arrayCount), the keyed part // in pairs[0, pairCount). Only scalar and string keys are meaningful (see // calogValueEquals); aggregate/function keys match by pointer identity only. struct CalogAggT { CalogKindE kind; CalogValueT *array; int64_t arrayCount; int64_t arrayCap; CalogPairT *pairs; int64_t pairCount; int64_t pairCap; }; // The uniform native-function signature. result is nil on entry; on failure write a // message into result with calogFail and return a non-zero CalogStatusE. typedef int32_t (*CalogNativeFnT)(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); // Delivered on the host thread (during calogPump) when a fire-and-forget script // fails: contextId is the failing context, message is the engine's error text. typedef void (*CalogErrorFnT)(uint64_t contextId, const char *message, void *userData); // Per-engine interpreter lifecycle + execution. Every hook runs on the context's OWN // thread. createInterpreter exposes every registered native (calog does this via the // adapter); a NULL engine makes a synthetic context with no interpreter. Embedders // may supply a custom engine, but normally pass a built-in vtable below. typedef struct CalogEngineT { const char *name; // NULL-terminated list of file extensions this engine handles, without the leading // dot (e.g. {"lua", NULL}). calogContextLoad uses it to pick an engine by filename. const char *const *extensions; int32_t (*createInterpreter)(CalogContextT *context, void **interpOut); void (*destroyInterpreter)(void *interp); int32_t (*runSource)(void *interp, const char *source, CalogValueT *result); } CalogEngineT; // ---- runtime ---- CalogT *calogCreate(void); void calogDestroy(CalogT *calog); void calogPump(CalogT *calog); // run pending script->native calls on THIS thread void calogSetErrorHandler(CalogT *calog, CalogErrorFnT fn, void *userData); // ---- natives ---- int32_t calogRegister(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the host thread int32_t calogRegisterInline(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the calling script thread int32_t calogCall(CalogT *calog, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result); int32_t calogFail(CalogValueT *result, int32_t status, const char *message); const char *calogTypeName(CalogTypeE type); // ---- values ---- void calogValueNil(CalogValueT *value); void calogValueBool(CalogValueT *value, bool b); void calogValueInt(CalogValueT *value, int64_t i); void calogValueReal(CalogValueT *value, double r); int32_t calogValueString(CalogValueT *value, const char *bytes, int64_t length); void calogValueAgg(CalogValueT *value, CalogAggT *aggregate); void calogValueFn(CalogValueT *value, CalogFnT *fn); int32_t calogValueCopy(CalogValueT *dst, const CalogValueT *src); void calogValueMove(CalogValueT *dst, CalogValueT *src); void calogValueFree(CalogValueT *value); bool calogValueEquals(const CalogValueT *a, const CalogValueT *b); // ---- aggregates ---- int32_t calogAggCreate(CalogAggT **out, CalogKindE kind); void calogAggFree(CalogAggT *aggregate); CalogValueT *calogAggGet(CalogAggT *aggregate, const CalogValueT *key); int32_t calogAggPush(CalogAggT *aggregate, CalogValueT *value); int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value); // ---- function values ---- // Wrap a host native as a host-owned function value (runs on the host thread during // calogPump, like calogRegister, but anonymous). Hand the result to a script via a // native's result to give the script a callable; release your reference with // calogFnRelease once you no longer hold it. int32_t calogFnFromNative(CalogFnT **out, CalogT *calog, CalogNativeFnT fn, void *userData); int32_t calogFnInvoke(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result); void calogFnRetain(CalogFnT *fn); void calogFnRelease(CalogFnT *fn); // ---- contexts + engines ---- CalogContextT *calogContextOpen(CalogT *calog, const CalogEngineT *engine); // create + start; NULL on failure // Make an engine available to calogContextLoad. Call at setup, before loading; // registration order is the search priority. void calogRegisterEngine(CalogT *calog, const CalogEngineT *engine); // Search the registered engines for a readable file "." (first // engine, then first extension, that names an existing file wins), open a context on // that engine, load the file into it (fire-and-forget), and return the context. NULL if // no file matched or the load failed. CalogContextT *calogContextLoad(CalogT *calog, const char *baseFileName); int32_t calogContextEval(CalogContextT *context, const char *source); // fire-and-forget void calogContextClose(CalogContextT *context); uint64_t calogContextId(const CalogContextT *context); uint64_t calogCurrentId(void); // context id of the calling thread (0 = host thread) CalogT *calogCurrent(void); // runtime of the calling context (NULL if none) extern const CalogEngineT calogLuaEngine; extern const CalogEngineT calogJsEngine; extern const CalogEngineT calogSquirrelEngine; extern const CalogEngineT calogMyBasicEngine; extern const CalogEngineT calogBerryEngine; extern const CalogEngineT calogS7Engine; extern const CalogEngineT calogWrenEngine; // Register the built-in engines selected at build time, so calogContextLoad works // without hand-registering each. Define CALOG_WITH_LUA / _JS / _SQUIRREL / _MYBASIC (in // the TU that calls this -- normally via your build's engine flags) for each engine you // link; only those are referenced here, so unselected engines and their archives stay // unlinked. The listed order is the load-search priority. Being header-inline, this // emits nothing unless actually called. static inline void calogRegisterBuiltinEngines(CalogT *calog) { #ifdef CALOG_WITH_LUA calogRegisterEngine(calog, &calogLuaEngine); #endif #ifdef CALOG_WITH_JS calogRegisterEngine(calog, &calogJsEngine); #endif #ifdef CALOG_WITH_SQUIRREL calogRegisterEngine(calog, &calogSquirrelEngine); #endif #ifdef CALOG_WITH_MYBASIC calogRegisterEngine(calog, &calogMyBasicEngine); #endif #ifdef CALOG_WITH_BERRY calogRegisterEngine(calog, &calogBerryEngine); #endif #ifdef CALOG_WITH_S7 calogRegisterEngine(calog, &calogS7Engine); #endif #ifdef CALOG_WITH_WREN calogRegisterEngine(calog, &calogWrenEngine); #endif (void)calog; // no-op if the build selected no engines } #endif