calog/src/calogInternal.h

165 lines
8 KiB
C

// 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 <pthread.h>
// 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