Several restrictive limits removed.

This commit is contained in:
Scott Duensing 2026-07-02 00:08:08 -05:00
parent c11bf8481f
commit 6c644064dd
24 changed files with 1536 additions and 84 deletions

View file

@ -55,5 +55,11 @@ applies to its files; the full text ships alongside each engine's source.
- **Wren** — Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors. MIT - **Wren** — Copyright (c) 2013-2021 Robert Nystrom and Wren Contributors. MIT
License. `vendor/wren/LICENSE` (amalgamated into `vendor/wren/wren.c`). License. `vendor/wren/LICENSE` (amalgamated into `vendor/wren/wren.c`).
> Note: `vendor/wren/wren.c` and `wren.h` carry one small calog patch adding a public
> C map-key iterator (`wrenGetMapCapacity` / `wrenGetMapEntry`), which upstream Wren
> lacks; it mirrors Wren's own internal `map_iterate`. The additions are bracketed by
> `--- calog patch ---` comments. Re-apply them if the amalgamation is regenerated from
> upstream (`util/generate_amalgamation.py`).
- **Berry** — Copyright (c) 2018-2026 Guan Wenliang and Berry contributors. MIT - **Berry** — Copyright (c) 2018-2026 Guan Wenliang and Berry contributors. MIT
License. `vendor/berry/` (see the upstream `LICENSE`). License. `vendor/berry/` (see the upstream `LICENSE`).

View file

@ -36,8 +36,9 @@ Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
native inline on the script's thread when you want it.) 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 - **Fire-and-forget scripts.** Scripts run on their own context threads; the host stays
responsive and drives everything from its own loop. responsive and drives everything from its own loop.
- **Callbacks both ways.** A script can hand you a function value; you keep it and call - **Callbacks both ways.** A script can hand you a function value to keep and call later
it later, and calog routes the call back to the engine that owns it. (routed back to the engine that owns it); and you can hand a native to a script as a
callable value with `calogFnFromNative` (every engine but MY-BASIC).
- **Many runtimes.** Independent `CalogT` runtimes coexist in one process; one host - **Many runtimes.** Independent `CalogT` runtimes coexist in one process; one host
thread can drive several. thread can drive several.
- **Load by filename.** `calogContextLoad(calog, "config")` finds `config.lua` / - **Load by filename.** `calogContextLoad(calog, "config")` finds `config.lua` /
@ -60,8 +61,11 @@ Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
| Berry | `calogBerryEngine` | `.be` | scalars + callbacks | | Berry | `calogBerryEngine` | `.be` | scalars + callbacks |
A native can return a keyed `CalogValueT` record and **every** engine reads its fields with A native can return a keyed `CalogValueT` record and **every** engine reads its fields with
native syntax (`user.name`, `user["name"]`, `(user "name")`). Handing a list/map *back* to native syntax (`user.name`, `user["name"]`, `(user "name")`), and a script can hand a
C is complete on Lua/JS/Squirrel/MY-BASIC and a v1 limit on Scheme/Wren/Berry. 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 but MY-BASIC (which has no first-class
callable values).
You can also bring your own: `CalogEngineT` is a public four-function vtable. You can also bring your own: `CalogEngineT` is a public four-function vtable.
@ -267,6 +271,21 @@ calogValueFree(&arg);
calogFnRelease(savedCb); 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 but MY-BASIC supports this.
```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 ### Loading a script by filename
Register the engines you want searchable (order is priority), then load by base name: Register the engines you want searchable (order is priority), then load by base name:

View file

@ -839,10 +839,9 @@ marshals the return; `squirrelCallableRelease` `sq_release`s on the owner thread
Single-threaded `testSquirrel` covers export+invoke-from-C, a closure passed as an Single-threaded `testSquirrel` covers export+invoke-from-C, a closure passed as an
argument and called back through the broker, and the not-found/type-error paths; argument and called back through the broker, and the not-found/type-error paths;
ASan-clean (no addref/release leak). Caveat (same as Lua): release exported ASan-clean (no addref/release leak). Caveat (same as Lua): release exported
callables before `squirrelContextDestroy`. Remaining limit: the reverse direction callables before `squirrelContextDestroy`. (The reverse direction -- a foreign `CalogFnT`
(a foreign `CallableT` pushed INTO Squirrel so a script can call it) returns pushed INTO Squirrel -- is now supported too: a native closure whose one free variable is
`brokerErrUnsupportedE` -- Squirrel has no clean callable-userdata-with-`__gc` like a release-hooked userdata holding the `CalogFnT`; see sec 18.)
Lua, so it needs a class instance with a `_call` metamethod + release hook.
`make test` runs all seven binaries (411 checks). `make tsan` covers the actor core `make test` runs all seven binaries (411 checks). `make tsan` covers the actor core
and the Lua engine path; `make tsansq` the Squirrel path. and the Lua engine path; `make tsansq` the Squirrel path.
@ -900,8 +899,9 @@ nil. The sharp edge: `be_pcall(vm, argc)` leaves the **result in the function's
(`be_modtab.c`). A subtlety for records: `be_newmap`/`be_newlist` push *raw* containers (`be_modtab.c`). A subtlety for records: `be_newmap`/`be_newlist` push *raw* containers
that a script cannot subscript, so an aggregate crossing out is wrapped in its `map`/`list` that a script cannot subscript, so an aggregate crossing out is wrapped in its `map`/`list`
class instance (`map(raw)` via `be_getbuiltin` + `be_call`, then `be_moveto`/`be_pop` to class instance (`map(raw)` via `be_getbuiltin` + `be_call`, then `be_moveto`/`be_pop` to
drop the raw and `init`'s nil return) -- then `user['name']` works. Reading a script's drop the raw and `init`'s nil return) -- then `user['name']` works. Ingress reverses it:
list/map back in is a v1 limit. a list/map instance holds its raw container in the hidden `.p` member, iterated with
`be_pushiter`/`be_iter_next` (see sec 18).
**s7 Scheme** (`.scm`) uses the *current* official s7 (an older mirror lacked `s7_free`, **s7 Scheme** (`.scm`) uses the *current* official s7 (an older mirror lacked `s7_free`,
which would leak a heap per context). Since s7 native functions carry no user data, all which would leak a heap per context). Since s7 native functions carry no user data, all
@ -937,3 +937,70 @@ Berry/s7/Wren.
**Verified** across all seven engines: `make test` (531 checks, including a materialized- **Verified** across all seven engines: `make test` (531 checks, including a materialized-
record read per new engine), ASan/UBSan clean, a `tsan<engine>` target clean for each, and record read per new engine), ASan/UBSan clean, a `tsan<engine>` target clean for each, and
gcc + clang strict on the core. gcc + clang strict on the core.
## 18. Closing the v1 marshalling limits (function-into-script, aggregate ingress)
The engines above shipped with two directional gaps in the value bridge: a foreign
function value could not be pushed *into* a script (only Lua did it), and a script could
not hand a keyed aggregate (map) *back* to C on the three newest engines. Both are now
closed everywhere they can be, with one honest exception each.
**New public API** `calogFnFromNative(out, calog, fn, userData)` -- wraps one of your
natives as a host-owned function value (owner id 0, runs on the host thread during
`calogPump`, like `calogRegister` but anonymous). Without it, function-into-script was
unusable from `calog.h` alone (`calogFnCreate` is internal), so a host could only forward
a *script*-derived callable, never one of its own natives. Return the result from a native
and the script gets a callable that routes back to the host.
**Function value -> script** (each `*FromValue` `calogFnE` case): an engine callable
object wraps the `CalogFnT*`, a trampoline marshals the script's args -> `calogFnInvoke`
-> marshals the result, and a finalizer runs `calogFnRelease`; `calogFnRetain` at push.
Per engine:
- **Lua** (pre-existing): userdata + `__call` + `__gc` metatable -- the reference for the rest.
- **JS**: a `JSClassDef` with both `.call` and `.finalizer`; the finalizer only receives
the runtime, so `JS_SetRuntimeOpaque` carries the context to it.
- **Squirrel**: a native closure whose single free variable is a release-hooked userdata
holding the `CalogFnT` (freeing the closure frees the userdata -> the hook releases).
- **s7**: an applicable c-object (`s7_make_c_type` + `s7_c_type_set_ref` for the call,
`s7_c_type_set_free` for release); the `ref` fn gets `(obj . args)`, so the object is
`s7_car`. Script calls `(f ...)`.
- **Berry**: no per-value finalizer exists, so the `CalogFnT`s pushed into a context are
*tracked on the context* and released together in `calogBerryDestroy`; the callable is a
native closure over `(context, CalogFnT)` comptr upvalues. (`berryFromValue` gained the
context parameter so it could record them.)
- **Wren**: a `foreign class CalogFn { construct new() {} foreign call(args) }` whose
`call` takes a *list* (Wren method arity is fixed, so a list absorbs any argument count);
finalize releases. Script calls `f.call([...])`. Gotcha: Wren requires newlines between
class members, so the preamble is multi-line.
- **MY-BASIC**: inherent gap -- BASIC has no first-class callable values to invoke.
**Aggregate ingress** (`*ToValue` map/list): Lua/JS/Squirrel/MY-BASIC already read both.
Added:
- **s7**: read a hash-table by `s7_make_iterator` + `s7_iterate` (each yields a
`(key . value)` cons; gotcha: at end `s7_iterate` returns a *non-pair* sentinel even
when the at-end flag was still false, so guard `if (!s7_is_pair(pair)) break`).
- **Berry**: a list/map instance's raw container is its hidden `.p` member; iterate with
`be_pushiter`/`be_iter_hasnext`/`be_iter_next` (which take the *container* index with the
iterator kept on top, pushing one value for a list and key+value for a map -- restore the
stack to `[container, iterator]` after each entry).
- **Wren**: a `List` reads back directly. A `Map` needs key enumeration, which upstream
Wren's C API lacks (`wrenGetMapValue` is by-key only) -- so calog adds a small patch to
the vendored `wren.c`/`wren.h` (`wrenGetMapCapacity` / `wrenGetMapEntry`, mirroring Wren's
own internal `map_iterate`), and the adapter walks the raw table skipping empty slots.
With the patch, Wren too reads maps back in. (Documented in LICENSE.md; re-apply if the
amalgamation is regenerated.)
**Deliberately not "fixed" (inherent to the engine's value model):** MY-BASIC 32-bit ints
(over 2^31 range-checked to an error, not silently truncated), MY-BASIC NUL-in-string
truncation and serialize-at-load, and JS/Wren int64 above 2^53 (IEEE doubles -- JS *could*
emit a BigInt but that breaks arithmetic mixing with Number, a worse trap than the
documented precision edge). `WREN_MAX_CALL_ARITY` (16) is pinned to Wren's own engine
limit (`MAX_PARAMETERS`) and can't be raised. `MB_BANK_SIZE` (the MY-BASIC native cap) was
32 -- the one hard cap a real app could hit, since MY-BASIC natives can't carry userdata
so each needs a hand-materialized slot trampoline; it is now **256** (the trampoline bank
and its `[MB_BANK_SIZE]` table are regenerated together, so a count mismatch fails to
compile). Berry's `BE_BYTES_MAX_SIZE` was likewise raised from 32 kb to 256 MB.
**Verified**: `make test` (539 checks) with a `testForeignFunction` per engine and a
`testMapIngress` on s7 and Berry (the Berry one nests a list to exercise list ingress);
ASan-clean on every engine (retain/release balanced); gcc strict; per-engine `tsan*`.

View file

@ -4,10 +4,12 @@
// (a comptr) and the native's name -- so the trampoline recovers its binding via // (a comptr) and the native's name -- so the trampoline recovers its binding via
// be_getupval, marshals the Berry arguments to CalogValueT, and dispatches through // be_getupval, marshals the Berry arguments to CalogValueT, and dispatches through
// calogCall (honoring the actor route hook). Marshalling covers scalars and binary-safe // calogCall (honoring the actor route hook). Marshalling covers scalars and binary-safe
// strings; a Berry function crossing out becomes a refcounted CalogFnT kept reachable by // strings; aggregates cross both ways (out as a Berry list/map instance -- a host record
// a uniquely-named hidden global (a GC root), dropped by setting that global to nil on // materializes as a map -- and a script's list/map is read back by iterating its raw
// release. An aggregate crossing out becomes a Berry list or map instance (a host record // container). Functions cross both ways too: a Berry function out becomes a refcounted
// materializes as a map); reading a script's list/map back in is a v1 limit. // CalogFnT kept reachable by a hidden global (a GC root); a foreign CalogFnT pushed in
// becomes a native closure the script calls directly (tracked on the context and released
// at destroy, since Berry has no per-value finalizer).
// //
// Error model: on failure the trampoline raises a Berry exception (be_raise, which // Error model: on failure the trampoline raises a Berry exception (be_raise, which
// longjmps out of the VM) after releasing any CalogValueT it owns. // longjmps out of the VM) after releasing any CalogValueT it owns.
@ -23,14 +25,18 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define BERRY_ERR_CAP 256 #define BERRY_ERR_CAP 256
#define BERRY_REF_CAP 32 #define BERRY_REF_CAP 32
#define BERRY_FOREIGN_INITIAL 8
struct CalogBerryT { struct CalogBerryT {
bvm *vm; bvm *vm;
CalogT *broker; CalogT *broker;
uint64_t ctxId; uint64_t ctxId;
int32_t nextRef; int32_t nextRef;
CalogFnT **foreignFns; // foreign function values pushed into this VM
int32_t foreignCount;
int32_t foreignCap;
}; };
// Backs a CalogFnT exported from this VM: the owning context and the name of the hidden // Backs a CalogFnT exported from this VM: the owning context and the name of the hidden
@ -43,7 +49,9 @@ typedef struct BerryExportT {
static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void berryCallableRelease(CalogFnT *callable); static void berryCallableRelease(CalogFnT *callable);
static int32_t berryExportValue(CalogBerryT *context, int index, CalogFnT **out); static int32_t berryExportValue(CalogBerryT *context, int index, CalogFnT **out);
static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth); static int berryForeignCall(bvm *vm);
static int32_t berryFromValue(CalogBerryT *context, const CalogValueT *value, int32_t depth);
static int32_t berryTrackForeign(CalogBerryT *context, CalogFnT *callable);
static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, int32_t depth); static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, int32_t depth);
static int berryTrampoline(bvm *vm); static int berryTrampoline(bvm *vm);
@ -86,7 +94,7 @@ static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogVal
return calogFail(result, calogErrDeadE, "berry callable no longer exists"); return calogFail(result, calogErrDeadE, "berry callable no longer exists");
} }
for (index = 0; index < argCount; index++) { for (index = 0; index < argCount; index++) {
status = berryFromValue(vm, &args[index], 0); status = berryFromValue(context, &args[index], 0);
if (status != calogOkE) { if (status != calogOkE) {
be_pop(vm, be_top(vm) - base); be_pop(vm, be_top(vm) - base);
return calogFail(result, status, "failed to marshal argument into berry"); return calogFail(result, status, "failed to marshal argument into berry");
@ -121,9 +129,16 @@ static void berryCallableRelease(CalogFnT *callable) {
void calogBerryDestroy(CalogBerryT *context) { void calogBerryDestroy(CalogBerryT *context) {
int32_t index;
if (context == NULL) { if (context == NULL) {
return; return;
} }
// Release the foreign function values pushed into this VM (see berryTrackForeign).
for (index = 0; index < context->foreignCount; index++) {
calogFnRelease(context->foreignFns[index]);
}
free(context->foreignFns);
if (context->vm != NULL) { if (context->vm != NULL) {
be_vm_delete(context->vm); be_vm_delete(context->vm);
} }
@ -207,7 +222,65 @@ int32_t calogBerryExpose(CalogBerryT *context, const char *name) {
} }
static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth) { // Call trampoline for a foreign CalogFnT pushed into Berry as a native closure. Its two
// upvalues are the context and the CalogFnT (both comptrs), pushed above the arguments.
static int berryForeignCall(bvm *vm) {
CalogBerryT *context;
CalogFnT *callable;
CalogValueT *args;
CalogValueT result;
int argc;
int index;
int32_t status;
char message[BERRY_ERR_CAP];
argc = be_top(vm); // arguments occupy stack slots 1..argc
be_getupval(vm, 0, 0);
context = (CalogBerryT *)be_tocomptr(vm, -1);
be_getupval(vm, 0, 1);
callable = (CalogFnT *)be_tocomptr(vm, -1);
be_pop(vm, 2);
args = NULL;
if (argc > 0) {
args = (CalogValueT *)calloc((size_t)argc, sizeof(CalogValueT));
if (args == NULL) {
be_raise(vm, "memory_error", "out of memory marshalling function-value args");
return 0;
}
}
for (index = 0; index < argc; index++) {
status = berryToValue(context, index + 1, &args[index], 0);
if (status != calogOkE) {
int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&args[cleanup]);
}
free(args);
be_raise(vm, "value_error", "failed to marshal a function-value argument");
return 0;
}
}
status = calogFnInvoke(callable, args, argc, &result);
for (index = 0; index < argc; index++) {
calogValueFree(&args[index]);
}
free(args);
if (status != calogOkE) {
snprintf(message, sizeof(message), "%s", (result.type == calogStringE) ? result.as.s.bytes : "function value failed");
calogValueFree(&result);
be_raise(vm, "calog_error", message);
return 0;
}
berryFromValue(context, &result, 0);
calogValueFree(&result);
be_return(vm);
}
static int32_t berryFromValue(CalogBerryT *context, const CalogValueT *value, int32_t depth) {
bvm *vm;
vm = context->vm;
if (depth > CALOG_MAX_DEPTH) { if (depth > CALOG_MAX_DEPTH) {
be_pushnil(vm); be_pushnil(vm);
return calogErrDepthE; return calogErrDepthE;
@ -241,7 +314,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
be_newmap(vm); be_newmap(vm);
for (index = 0; index < aggregate->arrayCount; index++) { for (index = 0; index < aggregate->arrayCount; index++) {
be_pushint(vm, (bint)index); be_pushint(vm, (bint)index);
berryFromValue(vm, &aggregate->array[index], depth + 1); berryFromValue(context, &aggregate->array[index], depth + 1);
be_setindex(vm, -3); be_setindex(vm, -3);
be_pop(vm, 2); be_pop(vm, 2);
} }
@ -255,7 +328,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
} else { } else {
continue; // non-scalar key: no Berry equivalent, drop the pair continue; // non-scalar key: no Berry equivalent, drop the pair
} }
berryFromValue(vm, &aggregate->pairs[index].value, depth + 1); berryFromValue(context, &aggregate->pairs[index].value, depth + 1);
be_setindex(vm, -3); be_setindex(vm, -3);
be_pop(vm, 2); be_pop(vm, 2);
} }
@ -263,7 +336,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
className = "list"; className = "list";
be_newlist(vm); be_newlist(vm);
for (index = 0; index < aggregate->arrayCount; index++) { for (index = 0; index < aggregate->arrayCount; index++) {
berryFromValue(vm, &aggregate->array[index], depth + 1); berryFromValue(context, &aggregate->array[index], depth + 1);
be_data_push(vm, -2); be_data_push(vm, -2);
be_pop(vm, 1); be_pop(vm, 1);
} }
@ -279,10 +352,24 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
be_pop(vm, 2); be_pop(vm, 2);
return calogOkE; return calogOkE;
} }
case calogFnE: case calogFnE: {
// Pushing a foreign function value into Berry is a v1 limit. // Berry has no per-value finalizer, so track the foreign function on the
be_pushnil(vm); // context (released at destroy) and wrap it in a native closure -- callable
return calogErrUnsupportedE; // as f(x) -- whose upvalues are the context and the CalogFnT.
if (berryTrackForeign(context, value->as.fn) != calogOkE) {
be_pushnil(vm);
return calogErrOomE;
}
be_pushntvclosure(vm, berryForeignCall, 2);
be_pushcomptr(vm, context);
be_setupval(vm, -2, 0);
be_pop(vm, 1);
be_pushcomptr(vm, value->as.fn);
be_setupval(vm, -2, 1);
be_pop(vm, 1);
calogFnRetain(value->as.fn);
return calogOkE;
}
} }
be_pushnil(vm); be_pushnil(vm);
return calogErrTypeE; return calogErrTypeE;
@ -352,11 +439,110 @@ static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, i
calogValueFn(out, callable); calogValueFn(out, callable);
return calogOkE; return calogOkE;
} }
// Reading a script's list/map into an aggregate is a v1 limit. // A list/map instance holds its raw container in the hidden ".p" member. be_iter_*
// take the CONTAINER index with the iterator on top of the stack (be_iter_next pushes
// one value for a list, key+value for a map), so keep the container at -2 and restore
// the iterator to the top after reading each entry.
if (be_islistinstance(vm, index)) {
CalogAggT *aggregate;
int base;
int32_t status;
status = calogAggCreate(&aggregate, calogListE);
if (status != calogOkE) {
return status;
}
base = be_top(vm);
be_getmember(vm, index, ".p"); // raw list -> top
be_pushiter(vm, -1); // iterator on top; container at -2
while (be_iter_hasnext(vm, -2)) {
CalogValueT element;
be_iter_next(vm, -2); // value on top
status = berryToValue(context, -1, &element, depth + 1);
be_pop(vm, be_top(vm) - (base + 2)); // restore to [container, iterator]
if (status != calogOkE) {
be_pop(vm, 2);
calogAggFree(aggregate);
return status;
}
status = calogAggPush(aggregate, &element);
if (status != calogOkE) {
calogValueFree(&element);
be_pop(vm, 2);
calogAggFree(aggregate);
return status;
}
}
be_pop(vm, 2); // container + iterator
calogValueAgg(out, aggregate);
return calogOkE;
}
if (be_ismapinstance(vm, index)) {
CalogAggT *aggregate;
int base;
int32_t status;
status = calogAggCreate(&aggregate, calogMapE);
if (status != calogOkE) {
return status;
}
base = be_top(vm);
be_getmember(vm, index, ".p"); // raw map -> top
be_pushiter(vm, -1); // iterator on top; container at -2
while (be_iter_hasnext(vm, -2)) {
CalogValueT key;
CalogValueT value;
be_iter_next(vm, -2); // key at -2, value at -1 (above the iterator)
status = berryToValue(context, -2, &key, depth + 1);
if (status != calogOkE) {
be_pop(vm, be_top(vm) - base);
calogAggFree(aggregate);
return status;
}
status = berryToValue(context, -1, &value, depth + 1);
if (status != calogOkE) {
calogValueFree(&key);
be_pop(vm, be_top(vm) - base);
calogAggFree(aggregate);
return status;
}
be_pop(vm, be_top(vm) - (base + 2)); // restore to [container, iterator]
status = calogAggSet(aggregate, &key, &value);
if (status != calogOkE) {
calogValueFree(&key);
calogValueFree(&value);
be_pop(vm, 2);
calogAggFree(aggregate);
return status;
}
}
be_pop(vm, 2); // container + iterator
calogValueAgg(out, aggregate);
return calogOkE;
}
// Other reference types have no CalogValueT equivalent.
return calogErrUnsupportedE; return calogErrUnsupportedE;
} }
// Record a foreign function pushed into this VM so it is released at destroy (Berry has
// no per-value finalizer to release it when the wrapping closure is collected).
static int32_t berryTrackForeign(CalogBerryT *context, CalogFnT *callable) {
if (context->foreignCount == context->foreignCap) {
int32_t newCap;
CalogFnT **resized;
newCap = (context->foreignCap == 0) ? BERRY_FOREIGN_INITIAL : context->foreignCap * CALOG_GROWTH_FACTOR;
resized = (CalogFnT **)realloc(context->foreignFns, (size_t)newCap * sizeof(CalogFnT *));
if (resized == NULL) {
return calogErrOomE;
}
context->foreignFns = resized;
context->foreignCap = newCap;
}
context->foreignFns[context->foreignCount] = callable;
context->foreignCount++;
return calogOkE;
}
static int berryTrampoline(bvm *vm) { static int berryTrampoline(bvm *vm) {
CalogBerryT *context; CalogBerryT *context;
const char *name; const char *name;
@ -408,7 +594,7 @@ static int berryTrampoline(bvm *vm) {
be_raise(vm, "calog_error", message); be_raise(vm, "calog_error", message);
return 0; return 0;
} }
status = berryFromValue(vm, &result, 0); status = berryFromValue(context, &result, 0);
calogValueFree(&result); calogValueFree(&result);
if (status != calogOkE) { if (status != calogOkE) {
be_pop(vm, 1); be_pop(vm, 1);

View file

@ -3,9 +3,9 @@
// Exposes broker-registered native functions into a Berry VM, marshals values between // Exposes broker-registered native functions into a Berry VM, marshals values between
// Berry and CalogValueT by value (binary-safe strings; scalars), and exports Berry // Berry and CalogValueT by value (binary-safe strings; scalars), and exports Berry
// functions as refcounted CalogFnT handles (kept alive by a GC-rooted hidden global, // functions as refcounted CalogFnT handles (kept alive by a GC-rooted hidden global,
// dropped on release). An aggregate crossing OUT to a script becomes a Berry list or map // dropped on release). Aggregates cross both ways: OUT a host record is a Berry map
// instance (so a host record is readable as user['name']); reading a script's list/map // instance (user['name']); IN a script's list/map is read by iterating its raw container.
// back IN is a v1 limit -> calogErrUnsupportedE. // A foreign CalogFnT pushed IN becomes a native closure the script calls directly.
// //
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogBerryExport // Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogBerryExport
// (and drop every function value marshalled out) BEFORE calogBerryDestroy, since the // (and drop every function value marshalled out) BEFORE calogBerryDestroy, since the

View file

@ -166,6 +166,11 @@ int32_t calogAggPush(CalogAggT *aggregate, CalogValueT *value);
int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value); int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value);
// ---- function values ---- // ---- 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); int32_t calogFnInvoke(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result);
void calogFnRetain(CalogFnT *fn); void calogFnRetain(CalogFnT *fn);
void calogFnRelease(CalogFnT *fn); void calogFnRelease(CalogFnT *fn);

View file

@ -37,6 +37,7 @@ struct CalogJsT {
JSContext *ctx; JSContext *ctx;
CalogT *broker; CalogT *broker;
uint64_t ctxId; uint64_t ctxId;
JSClassID fnClassId; // class for a foreign CalogFnT pushed into JS (callable + finalized)
}; };
// Backs a CalogFnT exported from this context: the retained JS function value (kept // Backs a CalogFnT exported from this context: the retained JS function value (kept
@ -49,6 +50,8 @@ typedef struct JsExportT {
static int32_t jsCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t jsCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void jsCallableRelease(CalogFnT *callable); static void jsCallableRelease(CalogFnT *callable);
static int32_t jsExportValue(CalogJsT *context, JSValueConst fn, CalogFnT **out); static int32_t jsExportValue(CalogJsT *context, JSValueConst fn, CalogFnT **out);
static JSValue jsForeignCall(JSContext *ctx, JSValueConst funcObj, JSValueConst thisVal, int argc, JSValueConst *argv, int flags);
static void jsForeignFinalize(JSRuntime *rt, JSValueConst val);
static JSValue jsFromValue(JSContext *ctx, const CalogValueT *value, int32_t depth); static JSValue jsFromValue(JSContext *ctx, const CalogValueT *value, int32_t depth);
static int32_t jsToValue(JSContext *ctx, JSValueConst val, CalogValueT *out, int32_t depth); static int32_t jsToValue(JSContext *ctx, JSValueConst val, CalogValueT *out, int32_t depth);
static JSValue jsTrampoline(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data); static JSValue jsTrampoline(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data);
@ -76,6 +79,19 @@ int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint64_t ctxId) {
context->broker = broker; context->broker = broker;
context->ctxId = ctxId; context->ctxId = ctxId;
JS_SetContextOpaque(context->ctx, context); JS_SetContextOpaque(context->ctx, context);
// A callable, finalized class wraps a foreign CalogFnT pushed into JS. The runtime
// opaque lets the finalizer (which gets only the runtime) recover the context.
JS_SetRuntimeOpaque(context->rt, context);
JS_NewClassID(context->rt, &context->fnClassId);
{
JSClassDef def;
def.class_name = "CalogFn";
def.finalizer = jsForeignFinalize;
def.gc_mark = NULL;
def.call = jsForeignCall;
def.exotic = NULL;
JS_NewClass(context->rt, context->fnClassId, &def);
}
*out = context; *out = context;
return calogOkE; return calogOkE;
} }
@ -238,6 +254,68 @@ int32_t calogJsExpose(CalogJsT *context, const char *name) {
} }
// Call handler for a foreign CalogFnT wrapped as a JS object: marshal the JS arguments,
// invoke the function value (routed to its owning context), and marshal the result back.
static JSValue jsForeignCall(JSContext *ctx, JSValueConst funcObj, JSValueConst thisVal, int argc, JSValueConst *argv, int flags) {
CalogJsT *context;
CalogFnT *callable;
CalogValueT *cargs;
CalogValueT result;
JSValue jsResult;
int index;
int32_t status;
(void)thisVal;
(void)flags;
context = (CalogJsT *)JS_GetContextOpaque(ctx);
callable = (CalogFnT *)JS_GetOpaque(funcObj, context->fnClassId);
cargs = NULL;
if (argc > 0) {
cargs = (CalogValueT *)calloc((size_t)argc, sizeof(CalogValueT));
if (cargs == NULL) {
return JS_ThrowOutOfMemory(ctx);
}
}
for (index = 0; index < argc; index++) {
status = jsToValue(ctx, argv[index], &cargs[index], 0);
if (status != calogOkE) {
int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&cargs[cleanup]);
}
free(cargs);
return JS_ThrowTypeError(ctx, "failed to marshal a function-value argument");
}
}
status = calogFnInvoke(callable, cargs, argc, &result);
for (index = 0; index < argc; index++) {
calogValueFree(&cargs[index]);
}
free(cargs);
if (status != calogOkE) {
jsResult = JS_ThrowTypeError(ctx, "%s", (result.type == calogStringE) ? result.as.s.bytes : "function value failed");
calogValueFree(&result);
return jsResult;
}
jsResult = jsFromValue(ctx, &result, 0);
calogValueFree(&result);
return jsResult;
}
// Finalizer for that wrapper: drop the reference the push took (see the calogFnE case).
static void jsForeignFinalize(JSRuntime *rt, JSValueConst val) {
CalogJsT *context;
CalogFnT *callable;
context = (CalogJsT *)JS_GetRuntimeOpaque(rt);
callable = (CalogFnT *)JS_GetOpaque(val, context->fnClassId);
if (callable != NULL) {
calogFnRelease(callable);
}
}
static JSValue jsFromValue(JSContext *ctx, const CalogValueT *value, int32_t depth) { static JSValue jsFromValue(JSContext *ctx, const CalogValueT *value, int32_t depth) {
if (depth > CALOG_MAX_DEPTH) { if (depth > CALOG_MAX_DEPTH) {
return JS_ThrowRangeError(ctx, "value nesting too deep"); return JS_ThrowRangeError(ctx, "value nesting too deep");
@ -301,10 +379,20 @@ static JSValue jsFromValue(JSContext *ctx, const CalogValueT *value, int32_t dep
} }
return container; return container;
} }
case calogFnE: case calogFnE: {
// Pushing a foreign function value INTO JavaScript is a v1 fidelity limit // Wrap the foreign function in a callable, finalized JS object; retain the
// (export OUT is supported via calogJsExport). // handle for the wrapper's lifetime (the finalizer releases it).
return JS_ThrowTypeError(ctx, "pushing a foreign function into JavaScript is unsupported"); CalogJsT *context;
JSValue obj;
context = (CalogJsT *)JS_GetContextOpaque(ctx);
obj = JS_NewObjectClass(ctx, context->fnClassId);
if (JS_IsException(obj)) {
return obj;
}
JS_SetOpaque(obj, value->as.fn);
calogFnRetain(value->as.fn);
return obj;
}
} }
return JS_ThrowTypeError(ctx, "unknown value type"); return JS_ThrowTypeError(ctx, "unknown value type");
} }

View file

@ -15,7 +15,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define MB_BANK_SIZE 32 #define MB_BANK_SIZE 256
#define MB_INITIAL_ARGS 8 #define MB_INITIAL_ARGS 8
#define MB_NAME_MAX 128 #define MB_NAME_MAX 128
#define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE #define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE
@ -90,13 +90,265 @@ MB_TRAMPOLINE(28)
MB_TRAMPOLINE(29) MB_TRAMPOLINE(29)
MB_TRAMPOLINE(30) MB_TRAMPOLINE(30)
MB_TRAMPOLINE(31) MB_TRAMPOLINE(31)
MB_TRAMPOLINE(32)
MB_TRAMPOLINE(33)
MB_TRAMPOLINE(34)
MB_TRAMPOLINE(35)
MB_TRAMPOLINE(36)
MB_TRAMPOLINE(37)
MB_TRAMPOLINE(38)
MB_TRAMPOLINE(39)
MB_TRAMPOLINE(40)
MB_TRAMPOLINE(41)
MB_TRAMPOLINE(42)
MB_TRAMPOLINE(43)
MB_TRAMPOLINE(44)
MB_TRAMPOLINE(45)
MB_TRAMPOLINE(46)
MB_TRAMPOLINE(47)
MB_TRAMPOLINE(48)
MB_TRAMPOLINE(49)
MB_TRAMPOLINE(50)
MB_TRAMPOLINE(51)
MB_TRAMPOLINE(52)
MB_TRAMPOLINE(53)
MB_TRAMPOLINE(54)
MB_TRAMPOLINE(55)
MB_TRAMPOLINE(56)
MB_TRAMPOLINE(57)
MB_TRAMPOLINE(58)
MB_TRAMPOLINE(59)
MB_TRAMPOLINE(60)
MB_TRAMPOLINE(61)
MB_TRAMPOLINE(62)
MB_TRAMPOLINE(63)
MB_TRAMPOLINE(64)
MB_TRAMPOLINE(65)
MB_TRAMPOLINE(66)
MB_TRAMPOLINE(67)
MB_TRAMPOLINE(68)
MB_TRAMPOLINE(69)
MB_TRAMPOLINE(70)
MB_TRAMPOLINE(71)
MB_TRAMPOLINE(72)
MB_TRAMPOLINE(73)
MB_TRAMPOLINE(74)
MB_TRAMPOLINE(75)
MB_TRAMPOLINE(76)
MB_TRAMPOLINE(77)
MB_TRAMPOLINE(78)
MB_TRAMPOLINE(79)
MB_TRAMPOLINE(80)
MB_TRAMPOLINE(81)
MB_TRAMPOLINE(82)
MB_TRAMPOLINE(83)
MB_TRAMPOLINE(84)
MB_TRAMPOLINE(85)
MB_TRAMPOLINE(86)
MB_TRAMPOLINE(87)
MB_TRAMPOLINE(88)
MB_TRAMPOLINE(89)
MB_TRAMPOLINE(90)
MB_TRAMPOLINE(91)
MB_TRAMPOLINE(92)
MB_TRAMPOLINE(93)
MB_TRAMPOLINE(94)
MB_TRAMPOLINE(95)
MB_TRAMPOLINE(96)
MB_TRAMPOLINE(97)
MB_TRAMPOLINE(98)
MB_TRAMPOLINE(99)
MB_TRAMPOLINE(100)
MB_TRAMPOLINE(101)
MB_TRAMPOLINE(102)
MB_TRAMPOLINE(103)
MB_TRAMPOLINE(104)
MB_TRAMPOLINE(105)
MB_TRAMPOLINE(106)
MB_TRAMPOLINE(107)
MB_TRAMPOLINE(108)
MB_TRAMPOLINE(109)
MB_TRAMPOLINE(110)
MB_TRAMPOLINE(111)
MB_TRAMPOLINE(112)
MB_TRAMPOLINE(113)
MB_TRAMPOLINE(114)
MB_TRAMPOLINE(115)
MB_TRAMPOLINE(116)
MB_TRAMPOLINE(117)
MB_TRAMPOLINE(118)
MB_TRAMPOLINE(119)
MB_TRAMPOLINE(120)
MB_TRAMPOLINE(121)
MB_TRAMPOLINE(122)
MB_TRAMPOLINE(123)
MB_TRAMPOLINE(124)
MB_TRAMPOLINE(125)
MB_TRAMPOLINE(126)
MB_TRAMPOLINE(127)
MB_TRAMPOLINE(128)
MB_TRAMPOLINE(129)
MB_TRAMPOLINE(130)
MB_TRAMPOLINE(131)
MB_TRAMPOLINE(132)
MB_TRAMPOLINE(133)
MB_TRAMPOLINE(134)
MB_TRAMPOLINE(135)
MB_TRAMPOLINE(136)
MB_TRAMPOLINE(137)
MB_TRAMPOLINE(138)
MB_TRAMPOLINE(139)
MB_TRAMPOLINE(140)
MB_TRAMPOLINE(141)
MB_TRAMPOLINE(142)
MB_TRAMPOLINE(143)
MB_TRAMPOLINE(144)
MB_TRAMPOLINE(145)
MB_TRAMPOLINE(146)
MB_TRAMPOLINE(147)
MB_TRAMPOLINE(148)
MB_TRAMPOLINE(149)
MB_TRAMPOLINE(150)
MB_TRAMPOLINE(151)
MB_TRAMPOLINE(152)
MB_TRAMPOLINE(153)
MB_TRAMPOLINE(154)
MB_TRAMPOLINE(155)
MB_TRAMPOLINE(156)
MB_TRAMPOLINE(157)
MB_TRAMPOLINE(158)
MB_TRAMPOLINE(159)
MB_TRAMPOLINE(160)
MB_TRAMPOLINE(161)
MB_TRAMPOLINE(162)
MB_TRAMPOLINE(163)
MB_TRAMPOLINE(164)
MB_TRAMPOLINE(165)
MB_TRAMPOLINE(166)
MB_TRAMPOLINE(167)
MB_TRAMPOLINE(168)
MB_TRAMPOLINE(169)
MB_TRAMPOLINE(170)
MB_TRAMPOLINE(171)
MB_TRAMPOLINE(172)
MB_TRAMPOLINE(173)
MB_TRAMPOLINE(174)
MB_TRAMPOLINE(175)
MB_TRAMPOLINE(176)
MB_TRAMPOLINE(177)
MB_TRAMPOLINE(178)
MB_TRAMPOLINE(179)
MB_TRAMPOLINE(180)
MB_TRAMPOLINE(181)
MB_TRAMPOLINE(182)
MB_TRAMPOLINE(183)
MB_TRAMPOLINE(184)
MB_TRAMPOLINE(185)
MB_TRAMPOLINE(186)
MB_TRAMPOLINE(187)
MB_TRAMPOLINE(188)
MB_TRAMPOLINE(189)
MB_TRAMPOLINE(190)
MB_TRAMPOLINE(191)
MB_TRAMPOLINE(192)
MB_TRAMPOLINE(193)
MB_TRAMPOLINE(194)
MB_TRAMPOLINE(195)
MB_TRAMPOLINE(196)
MB_TRAMPOLINE(197)
MB_TRAMPOLINE(198)
MB_TRAMPOLINE(199)
MB_TRAMPOLINE(200)
MB_TRAMPOLINE(201)
MB_TRAMPOLINE(202)
MB_TRAMPOLINE(203)
MB_TRAMPOLINE(204)
MB_TRAMPOLINE(205)
MB_TRAMPOLINE(206)
MB_TRAMPOLINE(207)
MB_TRAMPOLINE(208)
MB_TRAMPOLINE(209)
MB_TRAMPOLINE(210)
MB_TRAMPOLINE(211)
MB_TRAMPOLINE(212)
MB_TRAMPOLINE(213)
MB_TRAMPOLINE(214)
MB_TRAMPOLINE(215)
MB_TRAMPOLINE(216)
MB_TRAMPOLINE(217)
MB_TRAMPOLINE(218)
MB_TRAMPOLINE(219)
MB_TRAMPOLINE(220)
MB_TRAMPOLINE(221)
MB_TRAMPOLINE(222)
MB_TRAMPOLINE(223)
MB_TRAMPOLINE(224)
MB_TRAMPOLINE(225)
MB_TRAMPOLINE(226)
MB_TRAMPOLINE(227)
MB_TRAMPOLINE(228)
MB_TRAMPOLINE(229)
MB_TRAMPOLINE(230)
MB_TRAMPOLINE(231)
MB_TRAMPOLINE(232)
MB_TRAMPOLINE(233)
MB_TRAMPOLINE(234)
MB_TRAMPOLINE(235)
MB_TRAMPOLINE(236)
MB_TRAMPOLINE(237)
MB_TRAMPOLINE(238)
MB_TRAMPOLINE(239)
MB_TRAMPOLINE(240)
MB_TRAMPOLINE(241)
MB_TRAMPOLINE(242)
MB_TRAMPOLINE(243)
MB_TRAMPOLINE(244)
MB_TRAMPOLINE(245)
MB_TRAMPOLINE(246)
MB_TRAMPOLINE(247)
MB_TRAMPOLINE(248)
MB_TRAMPOLINE(249)
MB_TRAMPOLINE(250)
MB_TRAMPOLINE(251)
MB_TRAMPOLINE(252)
MB_TRAMPOLINE(253)
MB_TRAMPOLINE(254)
MB_TRAMPOLINE(255)
#undef MB_TRAMPOLINE #undef MB_TRAMPOLINE
static const mb_func_t mbTrampTable[MB_BANK_SIZE] = { static const mb_func_t mbTrampTable[MB_BANK_SIZE] = {
mbTramp0, mbTramp1, mbTramp2, mbTramp3, mbTramp4, mbTramp5, mbTramp6, mbTramp7, mbTramp0, mbTramp1, mbTramp2, mbTramp3, mbTramp4, mbTramp5, mbTramp6, mbTramp7,
mbTramp8, mbTramp9, mbTramp10, mbTramp11, mbTramp12, mbTramp13, mbTramp14, mbTramp15, mbTramp8, mbTramp9, mbTramp10, mbTramp11, mbTramp12, mbTramp13, mbTramp14, mbTramp15,
mbTramp16, mbTramp17, mbTramp18, mbTramp19, mbTramp20, mbTramp21, mbTramp22, mbTramp23, mbTramp16, mbTramp17, mbTramp18, mbTramp19, mbTramp20, mbTramp21, mbTramp22, mbTramp23,
mbTramp24, mbTramp25, mbTramp26, mbTramp27, mbTramp28, mbTramp29, mbTramp30, mbTramp31 mbTramp24, mbTramp25, mbTramp26, mbTramp27, mbTramp28, mbTramp29, mbTramp30, mbTramp31,
mbTramp32, mbTramp33, mbTramp34, mbTramp35, mbTramp36, mbTramp37, mbTramp38, mbTramp39,
mbTramp40, mbTramp41, mbTramp42, mbTramp43, mbTramp44, mbTramp45, mbTramp46, mbTramp47,
mbTramp48, mbTramp49, mbTramp50, mbTramp51, mbTramp52, mbTramp53, mbTramp54, mbTramp55,
mbTramp56, mbTramp57, mbTramp58, mbTramp59, mbTramp60, mbTramp61, mbTramp62, mbTramp63,
mbTramp64, mbTramp65, mbTramp66, mbTramp67, mbTramp68, mbTramp69, mbTramp70, mbTramp71,
mbTramp72, mbTramp73, mbTramp74, mbTramp75, mbTramp76, mbTramp77, mbTramp78, mbTramp79,
mbTramp80, mbTramp81, mbTramp82, mbTramp83, mbTramp84, mbTramp85, mbTramp86, mbTramp87,
mbTramp88, mbTramp89, mbTramp90, mbTramp91, mbTramp92, mbTramp93, mbTramp94, mbTramp95,
mbTramp96, mbTramp97, mbTramp98, mbTramp99, mbTramp100, mbTramp101, mbTramp102, mbTramp103,
mbTramp104, mbTramp105, mbTramp106, mbTramp107, mbTramp108, mbTramp109, mbTramp110, mbTramp111,
mbTramp112, mbTramp113, mbTramp114, mbTramp115, mbTramp116, mbTramp117, mbTramp118, mbTramp119,
mbTramp120, mbTramp121, mbTramp122, mbTramp123, mbTramp124, mbTramp125, mbTramp126, mbTramp127,
mbTramp128, mbTramp129, mbTramp130, mbTramp131, mbTramp132, mbTramp133, mbTramp134, mbTramp135,
mbTramp136, mbTramp137, mbTramp138, mbTramp139, mbTramp140, mbTramp141, mbTramp142, mbTramp143,
mbTramp144, mbTramp145, mbTramp146, mbTramp147, mbTramp148, mbTramp149, mbTramp150, mbTramp151,
mbTramp152, mbTramp153, mbTramp154, mbTramp155, mbTramp156, mbTramp157, mbTramp158, mbTramp159,
mbTramp160, mbTramp161, mbTramp162, mbTramp163, mbTramp164, mbTramp165, mbTramp166, mbTramp167,
mbTramp168, mbTramp169, mbTramp170, mbTramp171, mbTramp172, mbTramp173, mbTramp174, mbTramp175,
mbTramp176, mbTramp177, mbTramp178, mbTramp179, mbTramp180, mbTramp181, mbTramp182, mbTramp183,
mbTramp184, mbTramp185, mbTramp186, mbTramp187, mbTramp188, mbTramp189, mbTramp190, mbTramp191,
mbTramp192, mbTramp193, mbTramp194, mbTramp195, mbTramp196, mbTramp197, mbTramp198, mbTramp199,
mbTramp200, mbTramp201, mbTramp202, mbTramp203, mbTramp204, mbTramp205, mbTramp206, mbTramp207,
mbTramp208, mbTramp209, mbTramp210, mbTramp211, mbTramp212, mbTramp213, mbTramp214, mbTramp215,
mbTramp216, mbTramp217, mbTramp218, mbTramp219, mbTramp220, mbTramp221, mbTramp222, mbTramp223,
mbTramp224, mbTramp225, mbTramp226, mbTramp227, mbTramp228, mbTramp229, mbTramp230, mbTramp231,
mbTramp232, mbTramp233, mbTramp234, mbTramp235, mbTramp236, mbTramp237, mbTramp238, mbTramp239,
mbTramp240, mbTramp241, mbTramp242, mbTramp243, mbTramp244, mbTramp245, mbTramp246, mbTramp247,
mbTramp248, mbTramp249, mbTramp250, mbTramp251, mbTramp252, mbTramp253, mbTramp254, mbTramp255
}; };

View file

@ -5,10 +5,11 @@
// "report" a), so the dispatcher recovers the name from its first argument and the // "report" a), so the dispatcher recovers the name from its first argument and the
// context from a hidden *calog-context* c-pointer, marshals the rest to CalogValueT, and // context from a hidden *calog-context* c-pointer, marshals the rest to CalogValueT, and
// dispatches through calogCall (honoring the actor route hook). Marshalling covers // dispatches through calogCall (honoring the actor route hook). Marshalling covers
// scalars, binary-safe strings, and aggregates crossing out (a Scheme list, or a // scalars, binary-safe strings, and aggregates both ways (out: a Scheme list, or a
// hash-table when keyed, so a record reads as (user "name")); reading a script's keyed // hash-table when keyed, so a record reads as (user "name"); in: a list or a hash-table
// value back in is a v1 limit. A Scheme procedure crossing out becomes a refcounted // iterated back into an aggregate). Functions cross both ways: a Scheme procedure out is
// CalogFnT kept alive by s7_gc_protect (released with s7_gc_unprotect_at). // a refcounted CalogFnT kept alive by s7_gc_protect; a foreign CalogFnT pushed in is an
// applicable c-object the script calls as (f ...).
// //
// Error model: a native failure raises a Scheme error via s7_error. calogS7Run wraps the // Error model: a native failure raises a Scheme error via s7_error. calogS7Run wraps the
// source in (catch #t (lambda () (eval-string ...)) handler) so both read and run errors // source in (catch #t (lambda () (eval-string ...)) handler) so both read and run errors
@ -34,6 +35,7 @@ struct CalogS7T {
s7_scheme *sc; s7_scheme *sc;
CalogT *broker; CalogT *broker;
uint64_t ctxId; uint64_t ctxId;
s7_int fnType; // c-object type for a foreign CalogFnT pushed into Scheme (applicable + freed)
}; };
// Backs a CalogFnT exported from this interpreter: the owning context, the protected // Backs a CalogFnT exported from this interpreter: the owning context, the protected
@ -49,6 +51,8 @@ static void s7CallableRelease(CalogFnT *callable);
static s7_pointer s7DispatchNative(s7_scheme *sc, s7_pointer args); static s7_pointer s7DispatchNative(s7_scheme *sc, s7_pointer args);
static char *s7EscapeSource(const char *source); static char *s7EscapeSource(const char *source);
static int32_t s7ExportValue(CalogS7T *context, s7_pointer proc, CalogFnT **out); static int32_t s7ExportValue(CalogS7T *context, s7_pointer proc, CalogFnT **out);
static s7_pointer s7ForeignApply(s7_scheme *sc, s7_pointer args);
static void s7ForeignFree(void *value);
static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32_t depth); static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32_t depth);
static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, int32_t depth); static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, int32_t depth);
@ -72,6 +76,11 @@ int32_t calogS7Create(CalogS7T **out, CalogT *broker, uint64_t ctxId) {
// single generic native dispatcher. // single generic native dispatcher.
s7_define_variable(context->sc, S7_CONTEXT_VAR, s7_make_c_pointer(context->sc, context)); s7_define_variable(context->sc, S7_CONTEXT_VAR, s7_make_c_pointer(context->sc, context));
s7_define_function(context->sc, "%calog-call", s7DispatchNative, 1, 0, true, "calog native dispatch"); s7_define_function(context->sc, "%calog-call", s7DispatchNative, 1, 0, true, "calog native dispatch");
// A c-object type for a foreign function pushed into Scheme: applicable via its ref
// hook (so (f 2 3) invokes it) and released by its free hook.
context->fnType = s7_make_c_type(context->sc, "calog-fn");
s7_c_type_set_ref(context->sc, context->fnType, s7ForeignApply);
s7_c_type_set_free(context->sc, context->fnType, s7ForeignFree);
*out = context; *out = context;
return calogOkE; return calogOkE;
} }
@ -262,6 +271,72 @@ int32_t calogS7Expose(CalogS7T *context, const char *name) {
} }
// Applied when a foreign-function c-object is called: args is (obj arg1 arg2 ...), so the
// first element is the object itself. Marshal the rest, invoke, and return the result.
static s7_pointer s7ForeignApply(s7_scheme *sc, s7_pointer args) {
CalogS7T *context;
CalogFnT *callable;
s7_pointer rest;
s7_pointer node;
CalogValueT *cargs;
CalogValueT result;
s7_pointer sresult;
int argCount;
int index;
int32_t status;
context = (CalogS7T *)s7_c_pointer(s7_name_to_value(sc, S7_CONTEXT_VAR));
callable = (CalogFnT *)s7_c_object_value(s7_car(args));
rest = s7_cdr(args);
argCount = s7_list_length(sc, rest);
cargs = NULL;
if (argCount > 0) {
cargs = (CalogValueT *)calloc((size_t)argCount, sizeof(CalogValueT));
if (cargs == NULL) {
return s7_error(sc, s7_make_symbol(sc, "memory-error"), s7_make_string(sc, "out of memory marshalling function-value args"));
}
}
node = rest;
for (index = 0; index < argCount; index++) {
status = s7ToValue(context, s7_car(node), &cargs[index], 0);
if (status != calogOkE) {
int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&cargs[cleanup]);
}
free(cargs);
return s7_error(sc, s7_make_symbol(sc, "wrong-type-arg"), s7_make_string(sc, "failed to marshal a function-value argument"));
}
node = s7_cdr(node);
}
status = calogFnInvoke(callable, cargs, argCount, &result);
for (index = 0; index < argCount; index++) {
calogValueFree(&cargs[index]);
}
free(cargs);
if (status != calogOkE) {
s7_pointer message;
message = s7_make_string(sc, (result.type == calogStringE) ? result.as.s.bytes : "function value failed");
calogValueFree(&result);
return s7_error(sc, s7_make_symbol(sc, "calog-error"), message);
}
sresult = s7FromValue(context, &result, 0);
calogValueFree(&result);
return sresult;
}
// Free hook for that c-object: drop the reference the push took.
static void s7ForeignFree(void *value) {
CalogFnT *callable;
callable = (CalogFnT *)value;
if (callable != NULL) {
calogFnRelease(callable);
}
}
static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32_t depth) { static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32_t depth) {
s7_scheme *sc; s7_scheme *sc;
@ -322,9 +397,14 @@ static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32
return list; return list;
} }
} }
case calogFnE: case calogFnE: {
// Pushing a foreign function value into Scheme is a v1 limit. // Wrap the foreign function in an applicable c-object; retain for its lifetime
return s7_unspecified(sc); // (the free hook releases it).
s7_pointer obj;
obj = s7_make_c_object(sc, context->fnType, value->as.fn);
calogFnRetain(value->as.fn);
return obj;
}
} }
return s7_unspecified(sc); return s7_unspecified(sc);
} }
@ -398,6 +478,49 @@ static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, in
calogValueFn(out, callable); calogValueFn(out, callable);
return calogOkE; return calogOkE;
} }
if (s7_is_hash_table(obj)) {
CalogAggT *aggregate;
s7_pointer iter;
unsigned int loc;
int32_t status;
status = calogAggCreate(&aggregate, calogMapE);
if (status != calogOkE) {
return status;
}
iter = s7_make_iterator(sc, obj); // each iterate yields a (key . value) pair
loc = s7_gc_protect(sc, iter);
while (!s7_iterator_is_at_end(sc, iter)) {
s7_pointer pair;
CalogValueT key;
CalogValueT value;
pair = s7_iterate(sc, iter); // a (key . value) cons, or a non-pair at end
if (!s7_is_pair(pair)) {
break;
}
status = s7ToValue(context, s7_car(pair), &key, depth + 1);
if (status != calogOkE) {
break;
}
status = s7ToValue(context, s7_cdr(pair), &value, depth + 1);
if (status != calogOkE) {
calogValueFree(&key);
break;
}
status = calogAggSet(aggregate, &key, &value);
if (status != calogOkE) {
calogValueFree(&key);
calogValueFree(&value);
break;
}
}
s7_gc_unprotect_at(sc, loc);
if (status != calogOkE) {
calogAggFree(aggregate);
return status;
}
calogValueAgg(out, aggregate);
return calogOkE;
}
if (s7_is_null(sc, obj)) { if (s7_is_null(sc, obj)) {
CalogAggT *aggregate; CalogAggT *aggregate;
int32_t status; int32_t status;
@ -435,6 +558,6 @@ static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, in
calogValueAgg(out, aggregate); calogValueAgg(out, aggregate);
return calogOkE; return calogOkE;
} }
// #<unspecified>, eof, symbols, hash-tables, etc. -> nil (v1). // #<unspecified>, eof, symbols, etc. -> nil.
return calogOkE; return calogOkE;
} }

View file

@ -1,11 +1,11 @@
// s7Adapter.h -- s7 Scheme engine adapter for the broker. // s7Adapter.h -- s7 Scheme engine adapter for the broker.
// //
// Exposes broker-registered native functions into an s7 interpreter, marshals values // Exposes broker-registered native functions into an s7 interpreter, marshals values
// between Scheme and CalogValueT by value (binary-safe strings; scalars; aggregates // between Scheme and CalogValueT by value (binary-safe strings; scalars; aggregates both
// cross OUT as a Scheme list, or a hash-table when keyed so a record reads as (user // ways -- OUT as a Scheme list or a hash-table when keyed so a record reads as (user
// "name")), and exports Scheme procedures as refcounted CalogFnT handles (kept alive by // "name"), IN by iterating a list or hash-table back), and moves Scheme procedures out as
// s7_gc_protect, released with s7_gc_unprotect_at). Reading a script's keyed value (map) // refcounted CalogFnT handles (kept alive by s7_gc_protect). A foreign CalogFnT pushed IN
// back IN is a v1 limit -> calogErrUnsupportedE. // becomes an applicable c-object the script calls as (f ...).
// //
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogS7Export // Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogS7Export
// (and drop every procedure value marshalled out) BEFORE calogS7Destroy, since the // (and drop every procedure value marshalled out) BEFORE calogS7Destroy, since the

View file

@ -52,6 +52,8 @@ static int32_t squirrelCallableInvoke(CalogValueT *args, int32_t argCount, Calo
static void squirrelCallableRelease(CalogFnT *callable); static void squirrelCallableRelease(CalogFnT *callable);
static CalogSquirrelT *squirrelContextOf(HSQUIRRELVM v); static CalogSquirrelT *squirrelContextOf(HSQUIRRELVM v);
static int32_t squirrelExportAt(CalogSquirrelT *context, SQInteger idx, CalogFnT **out); static int32_t squirrelExportAt(CalogSquirrelT *context, SQInteger idx, CalogFnT **out);
static SQInteger squirrelForeignCall(HSQUIRRELVM v);
static SQInteger squirrelForeignRelease(SQUserPointer p, SQInteger size);
static void squirrelPrint(HSQUIRRELVM v, const SQChar *format, ...); static void squirrelPrint(HSQUIRRELVM v, const SQChar *format, ...);
static int32_t squirrelPushValueDepth(HSQUIRRELVM v, const CalogValueT *value, int32_t depth); static int32_t squirrelPushValueDepth(HSQUIRRELVM v, const CalogValueT *value, int32_t depth);
static int32_t squirrelToValueDepth(HSQUIRRELVM v, SQInteger idx, CalogValueT *out, int32_t depth); static int32_t squirrelToValueDepth(HSQUIRRELVM v, SQInteger idx, CalogValueT *out, int32_t depth);
@ -249,6 +251,78 @@ int32_t calogSquirrelExpose(CalogSquirrelT *context, const char *name) {
} }
// Call trampoline for a foreign CalogFnT pushed into Squirrel as a native closure. Its
// single free variable is a release-hooked userdata holding the CalogFnT; the VM pushes
// that free variable after the arguments, so it sits at the top of the stack.
static SQInteger squirrelForeignCall(HSQUIRRELVM v) {
CalogFnT *callable;
SQUserPointer ptr;
CalogValueT *args;
CalogValueT result;
SQInteger top;
int32_t argCount;
int32_t index;
int32_t status;
top = sq_gettop(v);
sq_getuserdata(v, top, &ptr, NULL);
callable = *(CalogFnT **)ptr;
argCount = (int32_t)(top - 2); // 'this' at 1, args at 2..top-1, free variable at top
if (argCount < 0) {
argCount = 0;
}
args = NULL;
if (argCount > 0) {
args = (CalogValueT *)calloc((size_t)argCount, sizeof(CalogValueT));
if (args == NULL) {
return sq_throwerror(v, _SC("out of memory marshalling function-value args"));
}
}
for (index = 0; index < argCount; index++) {
status = squirrelToValueDepth(v, index + 2, &args[index], 0);
if (status != calogOkE) {
int32_t cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&args[cleanup]);
}
free(args);
return sq_throwerror(v, _SC("failed to marshal a function-value argument"));
}
}
status = calogFnInvoke(callable, args, argCount, &result);
for (index = 0; index < argCount; index++) {
calogValueFree(&args[index]);
}
free(args);
if (status != calogOkE) {
const SQChar *message;
message = (result.type == calogStringE) ? result.as.s.bytes : _SC("function value failed");
sq_throwerror(v, message);
calogValueFree(&result);
return SQ_ERROR;
}
status = squirrelPushValueDepth(v, &result, 0);
calogValueFree(&result);
if (status != calogOkE) {
return sq_throwerror(v, _SC("failed to marshal a function-value result"));
}
return 1;
}
// Release hook on that userdata: drop the reference the push took.
static SQInteger squirrelForeignRelease(SQUserPointer p, SQInteger size) {
CalogFnT *callable;
(void)size;
callable = *(CalogFnT **)p;
if (callable != NULL) {
calogFnRelease(callable);
}
return 1;
}
static void squirrelPrint(HSQUIRRELVM v, const SQChar *format, ...) { static void squirrelPrint(HSQUIRRELVM v, const SQChar *format, ...) {
va_list args; va_list args;
@ -342,10 +416,21 @@ static int32_t squirrelPushValueDepth(HSQUIRRELVM v, const CalogValueT *value, i
} }
return calogOkE; return calogOkE;
} }
case calogFnE: case calogFnE: {
// Pushing a foreign function value INTO Squirrel is the remaining v1 // Wrap the foreign function in a native closure whose one free variable is a
// limit (export OUT is supported via calogSquirrelExport); see the header. // release-hooked userdata holding the CalogFnT (released when the closure is
return calogErrUnsupportedE; // collected). Retain for the wrapper's lifetime.
CalogFnT **slot;
slot = (CalogFnT **)sq_newuserdata(v, sizeof(CalogFnT *));
if (slot == NULL) {
return calogErrOomE;
}
*slot = value->as.fn;
sq_setreleasehook(v, -1, squirrelForeignRelease);
sq_newclosure(v, squirrelForeignCall, 1); // pops the userdata as the free variable
calogFnRetain(value->as.fn);
return calogOkE;
}
} }
return calogErrTypeE; return calogErrTypeE;
} }

View file

@ -8,13 +8,10 @@
// A Squirrel closure crossing the boundary becomes a refcounted CalogFnT over a // A Squirrel closure crossing the boundary becomes a refcounted CalogFnT over a
// pinned HSQOBJECT (sq_addref/sq_release, mirroring Lua's luaL_ref lifecycle): // pinned HSQOBJECT (sq_addref/sq_release, mirroring Lua's luaL_ref lifecycle):
// calogSquirrelExport fetches a named global closure, and a closure passed as a native // calogSquirrelExport fetches a named global closure, and a closure passed as a native
// argument is exported the same way during ingress marshalling. As with Lua, // argument is exported the same way during ingress marshalling. A foreign CalogFnT going
// release every exported CalogFnT BEFORE calogSquirrelDestroy, since sq_release // the other way (pushed INTO Squirrel) becomes a native closure whose one free variable
// touches the VM. // is a release-hooked userdata, so a script can call it directly. As with Lua, release
// // every exported CalogFnT BEFORE calogSquirrelDestroy, since sq_release touches the VM.
// Remaining v1 fidelity limit: the reverse direction (a foreign CalogFnT
// marshalled INTO Squirrel so a script can call it) is not yet supported -- pushing
// such a value returns calogErrUnsupportedE.
#ifndef SQUIRREL_ADAPTER_H #ifndef SQUIRREL_ADAPTER_H
#define SQUIRREL_ADAPTER_H #define SQUIRREL_ADAPTER_H

View file

@ -196,6 +196,12 @@ int32_t calogFnCreate(CalogFnT **out, CalogT *runtime, CalogNativeFnT fn, void *
} }
int32_t calogFnFromNative(CalogFnT **out, CalogT *calog, CalogNativeFnT fn, void *userData) {
// A host-owned callable (owner id 0 = host): no engine handle to release.
return calogFnCreate(out, calog, fn, userData, NULL, 0);
}
void calogFnFinalize(CalogFnT *callable) { void calogFnFinalize(CalogFnT *callable) {
if (callable == NULL) { if (callable == NULL) {
return; return;

View file

@ -44,15 +44,27 @@ typedef struct WrenExportT {
static int32_t wrenCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t wrenCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void wrenCallableRelease(CalogFnT *callable); static void wrenCallableRelease(CalogFnT *callable);
static WrenHandle *wrenCallHandle(CalogWrenT *context, int arity); static WrenHandle *wrenCallHandle(CalogWrenT *context, int arity);
static WrenForeignClassMethods wrenBindClass(WrenVM *vm, const char *module, const char *className);
static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature); static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature);
static void wrenDispatch(WrenVM *vm); static void wrenDispatch(WrenVM *vm);
static void wrenError(WrenVM *vm, WrenErrorType type, const char *module, int line, const char *message); static void wrenError(WrenVM *vm, WrenErrorType type, const char *module, int line, const char *message);
static int32_t wrenExportSlot(CalogWrenT *context, int slot, CalogFnT **out); static int32_t wrenExportSlot(CalogWrenT *context, int slot, CalogFnT **out);
static void wrenForeignAllocate(WrenVM *vm);
static void wrenForeignFinalize(void *data);
static void wrenForeignInvoke(WrenVM *vm);
static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slot, int32_t depth); static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slot, int32_t depth);
static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int32_t depth); static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int32_t depth);
static void wrenWrite(WrenVM *vm, const char *text); static void wrenWrite(WrenVM *vm, const char *text);
static const char WREN_PREAMBLE[] = "class Calog { foreign static call(name, args) }"; // A foreign function pushed into Wren becomes a CalogFn instance; scripts invoke it as
// f.call([args]) (a list, since Wren method arity is fixed). Calog.call(name, args)
// reaches natives (Wren has no bare function calls).
static const char WREN_PREAMBLE[] =
"class Calog { foreign static call(name, args) }\n"
"foreign class CalogFn {\n"
" construct new() {}\n"
" foreign call(args)\n"
"}";
int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) { int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) {
@ -69,6 +81,7 @@ int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) {
wrenInitConfiguration(&config); wrenInitConfiguration(&config);
config.userData = context; config.userData = context;
config.bindForeignMethodFn = wrenBindMethod; config.bindForeignMethodFn = wrenBindMethod;
config.bindForeignClassFn = wrenBindClass;
config.errorFn = wrenError; config.errorFn = wrenError;
config.writeFn = wrenWrite; config.writeFn = wrenWrite;
context->vm = wrenNewVM(&config); context->vm = wrenNewVM(&config);
@ -174,12 +187,30 @@ void calogWrenDestroy(CalogWrenT *context) {
} }
static WrenForeignClassMethods wrenBindClass(WrenVM *vm, const char *module, const char *className) {
WrenForeignClassMethods methods;
(void)vm;
(void)module;
methods.allocate = NULL;
methods.finalize = NULL;
if (strcmp(className, "CalogFn") == 0) {
methods.allocate = wrenForeignAllocate;
methods.finalize = wrenForeignFinalize;
}
return methods;
}
static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) { static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) {
(void)vm; (void)vm;
(void)module; (void)module;
if (strcmp(className, "Calog") == 0 && isStatic && strcmp(signature, "call(_,_)") == 0) { if (strcmp(className, "Calog") == 0 && isStatic && strcmp(signature, "call(_,_)") == 0) {
return wrenDispatch; return wrenDispatch;
} }
if (strcmp(className, "CalogFn") == 0 && !isStatic && strcmp(signature, "call(_)") == 0) {
return wrenForeignInvoke;
}
return NULL; return NULL;
} }
@ -302,6 +333,82 @@ int32_t calogWrenExpose(CalogWrenT *context, const char *name) {
} }
// Allocate hook for CalogFn (only reached if a script does CalogFn.new(); the host
// creates instances directly with wrenSetSlotNewForeign). Start empty.
static void wrenForeignAllocate(WrenVM *vm) {
CalogFnT **data;
data = (CalogFnT **)wrenSetSlotNewForeign(vm, 0, 0, sizeof(CalogFnT *));
*data = NULL;
}
// Finalizer for a CalogFn instance: drop the reference the push took.
static void wrenForeignFinalize(void *data) {
CalogFnT *callable;
callable = *(CalogFnT **)data;
if (callable != NULL) {
calogFnRelease(callable);
}
}
// CalogFn.call(args): invoke the wrapped function value with the argument list.
static void wrenForeignInvoke(WrenVM *vm) {
CalogWrenT *context;
CalogFnT *callable;
CalogValueT *cargs;
CalogValueT result;
int argCount;
int index;
int32_t status;
char message[WREN_ERR_CAP];
context = (CalogWrenT *)wrenGetUserData(vm);
callable = *(CalogFnT **)wrenGetSlotForeign(vm, 0); // slot 0 is the CalogFn instance
argCount = wrenGetListCount(vm, 1); // slot 1 is the argument list
cargs = NULL;
if (argCount > 0) {
cargs = (CalogValueT *)calloc((size_t)argCount, sizeof(CalogValueT));
if (cargs == NULL) {
wrenSetSlotString(vm, 0, "out of memory marshalling function-value args");
wrenAbortFiber(vm, 0);
return;
}
}
wrenEnsureSlots(vm, 3); // slot 2 is a scratch slot for list elements
for (index = 0; index < argCount; index++) {
wrenGetListElement(vm, 1, index, 2);
status = wrenToValue(context, 2, &cargs[index], 0);
if (status != calogOkE) {
int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&cargs[cleanup]);
}
free(cargs);
wrenSetSlotString(vm, 0, "failed to marshal a function-value argument");
wrenAbortFiber(vm, 0);
return;
}
}
status = calogFnInvoke(callable, cargs, argCount, &result);
for (index = 0; index < argCount; index++) {
calogValueFree(&cargs[index]);
}
free(cargs);
if (status != calogOkE) {
snprintf(message, sizeof(message), "%s", (result.type == calogStringE) ? result.as.s.bytes : "function value failed");
calogValueFree(&result);
wrenSetSlotString(vm, 0, message);
wrenAbortFiber(vm, 0);
return;
}
wrenFromValue(context, &result, 0, 0); // return value in slot 0
calogValueFree(&result);
}
static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slot, int32_t depth) { static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slot, int32_t depth) {
WrenVM *vm; WrenVM *vm;
@ -365,10 +472,17 @@ static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slo
} }
return; return;
} }
case calogFnE: case calogFnE: {
// Pushing a foreign function value into Wren is a v1 limit. // Wrap the foreign function in a CalogFn instance (script calls f.call([...]));
wrenSetSlotNull(vm, slot); // retain for its lifetime -- the finalizer releases it.
CalogFnT **data;
wrenEnsureSlots(vm, slot + 2);
wrenGetVariable(vm, "main", "CalogFn", slot + 1);
data = (CalogFnT **)wrenSetSlotNewForeign(vm, slot, slot + 1, sizeof(CalogFnT *));
*data = value->as.fn;
calogFnRetain(value->as.fn);
return; return;
}
} }
wrenSetSlotNull(vm, slot); wrenSetSlotNull(vm, slot);
} }
@ -451,8 +565,49 @@ static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int3
calogValueAgg(out, aggregate); calogValueAgg(out, aggregate);
return calogOkE; return calogOkE;
} }
case WREN_TYPE_MAP: case WREN_TYPE_MAP: {
return calogErrUnsupportedE; // map read is a v1 limit (no key enumeration in the C API) // Enumerate the map via the calog patch (wrenGetMapCapacity/wrenGetMapEntry):
// walk the raw table, skipping empty slots. slot+1 = key, slot+2 = value
// scratch; marshal the value before the key so a nested-aggregate key can't
// clobber the value slot.
CalogAggT *aggregate;
int capacity;
int index;
int32_t status;
status = calogAggCreate(&aggregate, calogMapE);
if (status != calogOkE) {
return status;
}
capacity = wrenGetMapCapacity(vm, slot);
wrenEnsureSlots(vm, slot + 3);
for (index = 0; index < capacity; index++) {
CalogValueT key;
CalogValueT value;
if (!wrenGetMapEntry(vm, slot, index, slot + 1, slot + 2)) {
continue;
}
status = wrenToValue(context, slot + 2, &value, depth + 1);
if (status != calogOkE) {
calogAggFree(aggregate);
return status;
}
status = wrenToValue(context, slot + 1, &key, depth + 1);
if (status != calogOkE) {
calogValueFree(&value);
calogAggFree(aggregate);
return status;
}
status = calogAggSet(aggregate, &key, &value);
if (status != calogOkE) {
calogValueFree(&key);
calogValueFree(&value);
calogAggFree(aggregate);
return status;
}
}
calogValueAgg(out, aggregate);
return calogOkE;
}
case WREN_TYPE_FOREIGN: case WREN_TYPE_FOREIGN:
case WREN_TYPE_UNKNOWN: { case WREN_TYPE_UNKNOWN: {
// A non-primitive (typically a Fn) is captured as a callable. // A non-primitive (typically a Fn) is captured as a callable.

View file

@ -2,11 +2,12 @@
// //
// Wren has no bare function calls, so natives are reached through one foreign method: // Wren has no bare function calls, so natives are reached through one foreign method:
// scripts call Calog.call("name", [args]). The adapter marshals values between Wren and // scripts call Calog.call("name", [args]). The adapter marshals values between Wren and
// CalogValueT by value (binary-safe strings; scalars; an aggregate crosses OUT as a Wren // CalogValueT by value (binary-safe strings; scalars; aggregates both ways -- OUT as a
// List, or a Map when keyed so a record reads as user["name"]) and exports Wren functions // Wren List, or a Map when keyed so a record reads as user["name"]; IN a List or a Map,
// as refcounted CalogFnT handles (a retained WrenHandle, released with wrenReleaseHandle). // the Map via a small calog patch to wren.c that enumerates map keys, which upstream's C
// Numbers are IEEE doubles, so int64 magnitudes above 2^53 lose precision. Reading a // API cannot). Functions cross both ways: OUT as a refcounted CalogFnT over a retained
// script's list/map back IN is a v1 limit (Wren's C API cannot enumerate a Map's keys). // WrenHandle; a foreign CalogFnT pushed IN becomes a CalogFn instance the script calls as
// f.call([...]). Numbers are IEEE doubles, so int64 magnitudes above 2^53 lose precision.
// //
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogWrenExport // Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogWrenExport
// (and drop every function value marshalled out) BEFORE calogWrenDestroy, since the // (and drop every function value marshalled out) BEFORE calogWrenDestroy, since the

View file

@ -32,9 +32,12 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -43,7 +46,9 @@ static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(void); static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testMapIngress(void);
static void testMaterializedRecord(void); static void testMaterializedRecord(void);
static void testScriptError(void); static void testScriptError(void);
@ -57,6 +62,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -77,6 +93,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogAggT *user; CalogAggT *user;
CalogValueT key; CalogValueT key;
@ -102,6 +136,26 @@ static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *
} }
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogValueT key;
CalogValueT *found;
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogAggE) {
return calogFail(result, calogErrArgE, "mapAge expects a map");
}
// Read a field out of a map the script built and handed to C (aggregate ingress).
calogValueString(&key, "age", 3);
found = calogAggGet(args[0].as.agg, &key);
calogValueFree(&key);
if (found != NULL && found->type == calogIntE) {
atomic_store(&reportedValue, found->as.i);
}
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -215,6 +269,39 @@ static void testMaterializedRecord(void) {
} }
static void testMapIngress(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogBerryEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script builds a map (with a nested list, exercising list ingress too) and
// hands it to a native, which reads a field.
calogContextEval(ctx, "m = {'age': 9, 'tags': [1, 2]}\nmapAge(m)\ndone()");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 9, "native read a field from a map the script built");
calogContextClose(ctx);
}
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogBerryEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and calls it.
calogContextEval(ctx, "u = getAdder()\nreport(u(2, 3))\ndone()");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testCrossThreadCallback(void) { static void testCrossThreadCallback(void) {
CalogContextT *ctx; CalogContextT *ctx;
CalogFnT *callback; CalogFnT *callback;
@ -293,10 +380,14 @@ int main(void) {
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "makeUser", nativeMakeUser, NULL); calogRegister(calog, "makeUser", nativeMakeUser, NULL);
calogRegister(calog, "reportName", nativeReportName, NULL); calogRegister(calog, "reportName", nativeReportName, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegister(calog, "mapAge", nativeMapAge, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testMaterializedRecord(); testMaterializedRecord();
testMapIngress();
testForeignFunction();
testCrossThreadCallback(); testCrossThreadCallback();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();

View file

@ -30,8 +30,10 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -39,6 +41,7 @@ static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(void); static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testScriptError(void); static void testScriptError(void);
@ -52,6 +55,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -72,6 +86,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -176,6 +208,22 @@ static void testCrossThreadCallback(void) {
} }
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogJsEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and calls it.
calogContextEval(ctx, "report(getAdder()(2, 3))\ndone()");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testScriptError(void) { static void testScriptError(void) {
CalogContextT *ctx; CalogContextT *ctx;
int32_t before; int32_t before;
@ -225,10 +273,12 @@ int main(void) {
calogRegister(calog, "setCb", nativeSetCb, NULL); calogRegister(calog, "setCb", nativeSetCb, NULL);
calogRegister(calog, "done", nativeDone, NULL); calogRegister(calog, "done", nativeDone, NULL);
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testCrossThreadCallback(); testCrossThreadCallback();
testForeignFunction();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();

View file

@ -33,9 +33,11 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeCheckRuntime(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeCheckRuntime(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -44,6 +46,7 @@ static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testContextLoad(void); static void testContextLoad(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testScriptError(void); static void testScriptError(void);
static void testSingleThreadMultiRuntime(void); static void testSingleThreadMultiRuntime(void);
@ -58,6 +61,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -95,6 +109,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -202,6 +234,22 @@ static void testCrossThreadCallback(void) {
} }
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogLuaEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and calls it.
calogContextEval(ctx, "report(getAdder()(2, 3)); done()");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testScriptError(void) { static void testScriptError(void) {
CalogContextT *ctx; CalogContextT *ctx;
int32_t before; int32_t before;
@ -322,10 +370,12 @@ int main(void) {
calogRegister(calog, "setCb", nativeSetCb, NULL); calogRegister(calog, "setCb", nativeSetCb, NULL);
calogRegister(calog, "done", nativeDone, NULL); calogRegister(calog, "done", nativeDone, NULL);
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testCrossThreadCallback(); testCrossThreadCallback();
testForeignFunction();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();
testSingleThreadMultiRuntime(); testSingleThreadMultiRuntime();

View file

@ -41,9 +41,12 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -52,7 +55,9 @@ static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(void); static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testMapIngress(void);
static void testMaterializedRecord(void); static void testMaterializedRecord(void);
static void testScriptError(void); static void testScriptError(void);
@ -66,6 +71,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -86,6 +102,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogAggT *user; CalogAggT *user;
CalogValueT key; CalogValueT key;
@ -111,6 +145,26 @@ static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *
} }
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogValueT key;
CalogValueT *found;
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogAggE) {
return calogFail(result, calogErrArgE, "mapAge expects a map");
}
// Read a field out of a map the script built and handed to C (aggregate ingress).
calogValueString(&key, "age", 3);
found = calogAggGet(args[0].as.agg, &key);
calogValueFree(&key);
if (found != NULL && found->type == calogIntE) {
atomic_store(&reportedValue, found->as.i);
}
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -224,6 +278,22 @@ static void testMaterializedRecord(void) {
} }
static void testMapIngress(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogS7Engine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script builds a hash-table and hands it to a native, which reads a field.
calogContextEval(ctx, "(begin (define m (make-hash-table)) (hash-table-set! m \"age\" 9) (mapAge m) (done))");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 9, "native read a field from a map the script built");
calogContextClose(ctx);
}
static void testCrossThreadCallback(void) { static void testCrossThreadCallback(void) {
CalogContextT *ctx; CalogContextT *ctx;
CalogFnT *callback; CalogFnT *callback;
@ -251,6 +321,22 @@ static void testCrossThreadCallback(void) {
} }
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogS7Engine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and applies it.
calogContextEval(ctx, "(begin (report ((getAdder) 2 3)) (done))");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testScriptError(void) { static void testScriptError(void) {
CalogContextT *ctx; CalogContextT *ctx;
int32_t before; int32_t before;
@ -302,11 +388,15 @@ int main(void) {
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "makeUser", nativeMakeUser, NULL); calogRegister(calog, "makeUser", nativeMakeUser, NULL);
calogRegister(calog, "reportName", nativeReportName, NULL); calogRegister(calog, "reportName", nativeReportName, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegister(calog, "mapAge", nativeMapAge, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testMaterializedRecord(); testMaterializedRecord();
testMapIngress();
testCrossThreadCallback(); testCrossThreadCallback();
testForeignFunction();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();

View file

@ -30,8 +30,10 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -39,6 +41,7 @@ static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(void); static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testScriptError(void); static void testScriptError(void);
@ -52,6 +55,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -72,6 +86,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -176,6 +208,22 @@ static void testCrossThreadCallback(void) {
} }
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogSquirrelEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and calls it.
calogContextEval(ctx, "report(getAdder()(2, 3))\ndone()");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testScriptError(void) { static void testScriptError(void) {
CalogContextT *ctx; CalogContextT *ctx;
int32_t before; int32_t before;
@ -225,10 +273,12 @@ int main(void) {
calogRegister(calog, "setCb", nativeSetCb, NULL); calogRegister(calog, "setCb", nativeSetCb, NULL);
calogRegister(calog, "done", nativeDone, NULL); calogRegister(calog, "done", nativeDone, NULL);
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testCrossThreadCallback(); testCrossThreadCallback();
testForeignFunction();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();

View file

@ -32,9 +32,12 @@ static int32_t testsRun = 0;
static int32_t testsFailed = 0; static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
@ -43,7 +46,9 @@ static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntilDone(void); static void pumpUntilDone(void);
static void testConcurrentContexts(void); static void testConcurrentContexts(void);
static void testCrossThreadCallback(void); static void testCrossThreadCallback(void);
static void testForeignFunction(void);
static void testHostAndInlineNatives(void); static void testHostAndInlineNatives(void);
static void testMapIngress(void);
static void testMaterializedRecord(void); static void testMaterializedRecord(void);
static void testScriptError(void); static void testScriptError(void);
@ -57,6 +62,17 @@ static void checkImpl(bool condition, const char *message, const char *file, int
} }
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
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;
}
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args; (void)args;
(void)argCount; (void)argCount;
@ -77,6 +93,24 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
} }
static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogFnT *callable;
int32_t status;
(void)args;
(void)argCount;
(void)userData;
calogValueNil(result);
// A host-owned function value handed to the script; calling it routes back here.
status = calogFnFromNative(&callable, calog, nativeAdd, NULL);
if (status != calogOkE) {
return calogFail(result, status, "getAdder could not allocate");
}
calogValueFn(result, callable);
return calogOkE;
}
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogAggT *user; CalogAggT *user;
CalogValueT key; CalogValueT key;
@ -102,6 +136,26 @@ static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *
} }
static int32_t nativeMapAge(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogValueT key;
CalogValueT *found;
(void)userData;
calogValueNil(result);
if (argCount != 1 || args[0].type != calogAggE) {
return calogFail(result, calogErrArgE, "mapAge expects a map");
}
// Read a field out of a Wren map the script built and handed to C (map ingress).
calogValueString(&key, "age", 3);
found = calogAggGet(args[0].as.agg, &key);
calogValueFree(&key);
if (found != NULL && found->type == calogIntE) {
atomic_store(&reportedValue, found->as.i);
}
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData; (void)userData;
calogValueNil(result); calogValueNil(result);
@ -215,6 +269,39 @@ static void testMaterializedRecord(void) {
} }
static void testMapIngress(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogWrenEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script builds a Wren Map and hands it to a native, which reads a field
// (exercises the calog wren.c patch that enumerates map keys from C).
calogContextEval(ctx, "Calog.call(\"mapAge\", [{\"age\": 9}])\nCalog.call(\"done\", [])");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 9, "native read a field from a Wren map the script built");
calogContextClose(ctx);
}
static void testForeignFunction(void) {
CalogContextT *ctx;
ctx = calogContextOpen(calog, &calogWrenEngine);
atomic_store(&scriptDone, false);
atomic_store(&reportedValue, 0);
// The script receives a host-owned function value and calls it as f.call([...]).
calogContextEval(ctx, "var f = Calog.call(\"getAdder\", [])\nCalog.call(\"report\", [f.call([2, 3])])\nCalog.call(\"done\", [])");
pumpUntilDone();
CHECK(atomic_load(&reportedValue) == 5, "script called a foreign function value pushed in from the host");
calogContextClose(ctx);
}
static void testCrossThreadCallback(void) { static void testCrossThreadCallback(void) {
CalogContextT *ctx; CalogContextT *ctx;
CalogFnT *callback; CalogFnT *callback;
@ -293,10 +380,14 @@ int main(void) {
calogRegister(calog, "bump", nativeBump, NULL); calogRegister(calog, "bump", nativeBump, NULL);
calogRegister(calog, "makeUser", nativeMakeUser, NULL); calogRegister(calog, "makeUser", nativeMakeUser, NULL);
calogRegister(calog, "reportName", nativeReportName, NULL); calogRegister(calog, "reportName", nativeReportName, NULL);
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
calogRegister(calog, "mapAge", nativeMapAge, NULL);
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
testHostAndInlineNatives(); testHostAndInlineNatives();
testMaterializedRecord(); testMaterializedRecord();
testMapIngress();
testForeignFunction();
testCrossThreadCallback(); testCrossThreadCallback();
testScriptError(); testScriptError();
testConcurrentContexts(); testConcurrentContexts();

View file

@ -37,12 +37,13 @@
#define BE_USE_SINGLE_FLOAT 0 #define BE_USE_SINGLE_FLOAT 0
/* Macro: BE_BYTES_MAX_SIZE /* Macro: BE_BYTES_MAX_SIZE
* Maximum size in bytes of a `bytes()` object. * Maximum size in bytes of a `bytes()` object. This is a ceiling, not a
* Putting too much pressure on the memory allocator can do * preallocation -- a script only uses what it allocates -- so it is set
* harm, so we limit the maximum size. * generously here (calog raised it from Berry's 32 kb default). The hard
* Default: 32kb * ceiling is ~INT32_MAX because a bytes object's size/len fields are int32;
* a host can also raise vm->bytesmaxsize at runtime.
**/ **/
#define BE_BYTES_MAX_SIZE (32*1024) /* 32 kb default value */ #define BE_BYTES_MAX_SIZE (256*1024*1024) /* 256 MB (calog) */
/* Macro: BE_USE_PRECOMPILED_OBJECT /* Macro: BE_USE_PRECOMPILED_OBJECT
* Use precompiled objects to avoid creating these objects at * Use precompiled objects to avoid creating these objects at

28
vendor/wren/wren.c vendored
View file

@ -4652,6 +4652,34 @@ void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
vm->apiStack[valueSlot] = value; vm->apiStack[valueSlot] = value;
} }
// --- calog patch: map key enumeration (upstream Wren has no C map iterator) ---
int wrenGetMapCapacity(WrenVM* vm, int mapSlot)
{
validateApiSlot(vm, mapSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
return (int)AS_MAP(vm->apiStack[mapSlot])->capacity;
}
bool wrenGetMapEntry(WrenVM* vm, int mapSlot, int index, int keySlot, int valueSlot)
{
validateApiSlot(vm, mapSlot);
validateApiSlot(vm, keySlot);
validateApiSlot(vm, valueSlot);
ASSERT(IS_MAP(vm->apiStack[mapSlot]), "Slot must hold a map.");
ObjMap* map = AS_MAP(vm->apiStack[mapSlot]);
if (index < 0 || (uint32_t)index >= map->capacity) return false;
MapEntry* entry = &map->entries[index];
if (IS_UNDEFINED(entry->key)) return false;
vm->apiStack[keySlot] = entry->key;
vm->apiStack[valueSlot] = entry->value;
return true;
}
// --- end calog patch ---
void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
{ {
validateApiSlot(vm, mapSlot); validateApiSlot(vm, mapSlot);

11
vendor/wren/wren.h vendored
View file

@ -528,6 +528,17 @@ WREN_API void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlo
WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, WREN_API void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot,
int removedValueSlot); int removedValueSlot);
// --- calog patch: map key enumeration (upstream Wren has no C map iterator) ---
// Returns the raw hash-table capacity of the map in [mapSlot] (>= wrenGetMapCount).
// Iterate indices 0..capacity-1 and call wrenGetMapEntry to visit each occupied slot.
WREN_API int wrenGetMapCapacity(WrenVM* vm, int mapSlot);
// Stores the key and value of the map entry at raw table [index] into [keySlot] and
// [valueSlot] and returns true; returns false if that slot is empty (skip it). Mirrors
// Wren's own internal map_iterate/keyIteratorValue.
WREN_API bool wrenGetMapEntry(WrenVM* vm, int mapSlot, int index, int keySlot, int valueSlot);
// --- end calog patch ---
// Looks up the top level variable with [name] in resolved [module] and stores // Looks up the top level variable with [name] in resolved [module] and stores
// it in [slot]. // it in [slot].
WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name, WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,