// calogInternal.h -- internal API shared across calog's own translation units. // // NOT part of the embedding surface (that is calog.h). Host programs include calog.h // only. #ifndef CALOG_INTERNAL_H #define CALOG_INTERNAL_H #include "calog.h" #include // The host context's id (the thread that owns the runtime). A callable owned by the host // (ownerCtxId == CALOG_HOST_ID) has no separate interpreter to outlive. #define CALOG_HOST_ID 0 // A registry entry, resolved by name. runInline true means the native runs on the // calling thread (calogRegisterInline); false means it runs on the host thread. struct CalogEntryT { char *name; CalogNativeFnT fn; void *userData; bool runInline; }; typedef struct CalogEntryT CalogEntryT; // One active-context registry slot. context is NULL when free; generation is bumped // on reuse so a stale id (of a closed + recycled context) resolves dead, never // misrouting to the recycler. typedef struct CalogRegistrySlotT { CalogContextT *context; uint32_t generation; } CalogRegistrySlotT; // Invoked when a CalogFnT's last reference drops, so the owning engine can release // its closure. The broker frees the CalogFnT shell after this returns; reach the // closure handle with calogFnUserData. // // CONTRACT: the userData passed to calogFnCreate must be a SINGLE heap allocation, and // this hook's only cleanup (beyond the interpreter op) must be free(userData). When the // owning context is gone, calogFnFinalize skips this hook and frees userData directly -- // the interpreter (and the closure handle in it) is already destroyed. typedef void (*CalogReleaseFnT)(CalogFnT *fn); // Hooks the actor layer installs on the runtime (calogActorInit stores them in CalogT) // so calogCall, calogFnInvoke, and the final calogFnRelease marshal to the owning // thread instead of running inline. NULL on a bare broker (single-threaded use runs // inline). The route hook is handed its runtime; the callable hooks reach it through // the CalogFnT (calogFnRuntime). See design.md sec 6/10. typedef int32_t (*CalogRouteFnT)(CalogT *calog, CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result); typedef int32_t (*CalogInvokeHookT)(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result); typedef void (*CalogReleaseHookT)(CalogFnT *fn); // The runtime. Owns the native-function registry AND the active-context registry, so // a host's contexts are its property -- calogDestroy closes every open one. Both // registries grow dynamically with no preset limit (context ids are 64-bit: a 32-bit // slot index + 32-bit generation, so neither the live count nor open/close churn is // capped in practice). All per-runtime actor state (host context, routing hooks, error // sink) lives here too, so independent CalogT runtimes coexist in one process; each is // created and pumped by its own host thread. struct CalogT { // native-function registry (broker.c) CalogEntryT *slots; int64_t slotCount; int64_t entryCount; // active-context registry (context.c), guarded by ctxMutex pthread_mutex_t ctxMutex; CalogRegistrySlotT *ctxSlots; int64_t ctxCount; // high-water slot count int64_t ctxCap; int64_t *ctxFree; // recycled slot indices int64_t ctxFreeCount; int64_t ctxFreeCap; // actor layer (context.c): installed by calogActorInit, all NULL on a bare broker CalogContextT *hostContext; // id 0, no thread; driven by calogPump pthread_t hostThread; // thread that ran calogActorInit (this runtime's host) CalogRouteFnT routeHook; CalogInvokeHookT invokeHook; CalogReleaseHookT releaseHook; CalogErrorFnT errorHandler; void *errorUserData; // engines available to calogContextLoad (calogRegisterEngine), search-priority order const CalogEngineT **engines; int64_t engineCount; int64_t engineCap; }; // ---- registry (calogCreate/calogDestroy compose these with the actor layer) ---- CalogT *calogBrokerCreate(void); void calogBrokerDestroy(CalogT *calog); CalogEntryT *calogLookup(CalogT *calog, const char *name); // Visit every registered entry EXCEPT internal "__"-prefixed ones (used by createInterpreter // to expose natives to scripts; __ natives stay reachable via calogCall but are not exposed). // Safe only while the registry is frozen -- i.e. before any context starts. void calogForEach(CalogT *calog, void (*visit)(const CalogEntryT *entry, void *ud), void *ud); // ---- callable lifecycle (driven by the engine adapters) ---- int32_t calogFnCreate(CalogFnT **out, CalogT *runtime, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint64_t ownerCtxId); void calogFnFinalize(CalogFnT *fn); CalogNativeFnT calogFnNative(const CalogFnT *fn); void calogFnMarkDead(CalogFnT *fn); uint64_t calogFnOwner(const CalogFnT *fn); CalogT *calogFnRuntime(const CalogFnT *fn); void *calogFnUserData(const CalogFnT *fn); // ---- actor layer (composed into calogCreate/calogDestroy) ---- int32_t calogActorInit(CalogT *calog); void calogActorShutdown(CalogT *calog); CalogT *calogContextBroker(const CalogContextT *context); void *calogContextInterp(CalogContextT *context); bool calogContextRegistered(CalogT *runtime, uint64_t ctxId); // ---- shared helpers (one source of truth; value.c) ---- // A formatted engine error message is copied into a stack buffer of this size before the // interpreter's throw/longjmp releases the underlying result. #define CALOG_ERR_MSG_CAP 256 // Exact-representable int64 range as doubles: a finite double N is an exact integer in // int64 range iff floor(N) == N and CALOG_INT64_MIN_DOUBLE <= N < CALOG_INT64_MAX_DOUBLE. // The upper bound 2^63 is itself not representable as an int64, hence the strict <. #define CALOG_INT64_MIN_DOUBLE (-9223372036854775808.0) #define CALOG_INT64_MAX_DOUBLE (9223372036854775808.0) // Decode one ASCII hex digit to 0..15, or -1 if the byte is not a hex digit. The single // source for hex-nibble decoding (crypto hex strings and JSON \\uXXXX escapes). static inline int32_t calogHexNibble(unsigned char c) { if (c >= '0' && c <= '9') { return (int32_t)(c - '0'); } if (c >= 'a' && c <= 'f') { return (int32_t)(c - 'a' + 10); } if (c >= 'A' && c <= 'F') { return (int32_t)(c - 'A' + 10); } return -1; } // Grow *buffer to hold at least `needed` elements of `elemSize` bytes, doubling from a // minimum and guarding both the capacity multiply and the size_t byte multiply against // overflow. The single growth policy for every dynamic array in the project. int32_t calogGrow(void **buffer, int64_t *cap, int64_t needed, size_t elemSize); // Canonical "an engine's number is a double" ingress: emit an int64 when the double is an // exact integer in int64 range, otherwise a real. void calogValueFromDouble(CalogValueT *out, double number); // Canonical egress classification: true if a hybrid aggregate materializes as a keyed // map/object/table, false if a sequence/array/list. bool calogAggIsKeyed(const CalogAggT *aggregate); // Build map[key] = value with correct ownership on failure (key is a C string; its byte // length is strlen(key)). Returns calogOkE or an error, releasing anything it built. int32_t calogMapSetInt(CalogAggT *map, const char *key, int64_t value); int32_t calogMapSetStr(CalogAggT *map, const char *key, const char *bytes, int64_t length); int32_t calogMapSetBool(CalogAggT *map, const char *key, bool flag); // Refcounted process-wide library registry: retain once per calog...Register, release once // per ...Shutdown. release invokes freeAll (while holding initMutex) exactly when the last // reference drops. void calogRegistryRetain(pthread_mutex_t *initMutex, int32_t *refCount); void calogRegistryRelease(pthread_mutex_t *initMutex, int32_t *refCount, void (*freeAll)(void)); #endif