165 lines
8 KiB
C
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
|