Several restrictive limits removed.
This commit is contained in:
parent
c11bf8481f
commit
6c644064dd
24 changed files with 1536 additions and 84 deletions
|
|
@ -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
|
||||
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
|
||||
License. `vendor/berry/` (see the upstream `LICENSE`).
|
||||
|
|
|
|||
27
README.md
27
README.md
|
|
@ -36,8 +36,9 @@ Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
|
|||
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
|
||||
responsive and drives everything from its own loop.
|
||||
- **Callbacks both ways.** A script can hand you a function value; you keep it and call
|
||||
it later, and calog routes the call back to the engine that owns it.
|
||||
- **Callbacks both ways.** A script can hand you a function value to keep and call later
|
||||
(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
|
||||
thread can drive several.
|
||||
- **Load by filename.** `calogContextLoad(calog, "config")` finds `config.lua` /
|
||||
|
|
@ -60,8 +61,11 @@ Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
|
|||
| Berry | `calogBerryEngine` | `.be` | scalars + callbacks |
|
||||
|
||||
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
|
||||
C is complete on Lua/JS/Squirrel/MY-BASIC and a v1 limit on Scheme/Wren/Berry.
|
||||
native syntax (`user.name`, `user["name"]`, `(user "name")`), and a script can hand a
|
||||
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.
|
||||
|
||||
|
|
@ -267,6 +271,21 @@ calogValueFree(&arg);
|
|||
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
|
||||
|
||||
Register the engines you want searchable (order is priority), then load by base name:
|
||||
|
|
|
|||
79
design.md
79
design.md
|
|
@ -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
|
||||
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
|
||||
callables before `squirrelContextDestroy`. Remaining limit: the reverse direction
|
||||
(a foreign `CallableT` pushed INTO Squirrel so a script can call it) returns
|
||||
`brokerErrUnsupportedE` -- Squirrel has no clean callable-userdata-with-`__gc` like
|
||||
Lua, so it needs a class instance with a `_call` metamethod + release hook.
|
||||
callables before `squirrelContextDestroy`. (The reverse direction -- a foreign `CalogFnT`
|
||||
pushed INTO Squirrel -- is now supported too: a native closure whose one free variable is
|
||||
a release-hooked userdata holding the `CalogFnT`; see sec 18.)
|
||||
|
||||
`make test` runs all seven binaries (411 checks). `make tsan` covers the actor core
|
||||
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
|
||||
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
|
||||
drop the raw and `init`'s nil return) -- then `user['name']` works. Reading a script's
|
||||
list/map back in is a v1 limit.
|
||||
drop the raw and `init`'s nil return) -- then `user['name']` works. Ingress reverses it:
|
||||
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`,
|
||||
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-
|
||||
record read per new engine), ASan/UBSan clean, a `tsan<engine>` target clean for each, and
|
||||
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*`.
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
// (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
|
||||
// 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
|
||||
// a uniquely-named hidden global (a GC root), dropped by setting that global to nil on
|
||||
// release. An aggregate crossing out becomes a Berry list or map instance (a host record
|
||||
// materializes as a map); reading a script's list/map back in is a v1 limit.
|
||||
// strings; aggregates cross both ways (out as a Berry list/map instance -- a host record
|
||||
// materializes as a map -- and a script's list/map is read back by iterating its raw
|
||||
// container). Functions cross both ways too: a Berry function out becomes a refcounted
|
||||
// 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
|
||||
// longjmps out of the VM) after releasing any CalogValueT it owns.
|
||||
|
|
@ -23,14 +25,18 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define BERRY_ERR_CAP 256
|
||||
#define BERRY_REF_CAP 32
|
||||
#define BERRY_ERR_CAP 256
|
||||
#define BERRY_REF_CAP 32
|
||||
#define BERRY_FOREIGN_INITIAL 8
|
||||
|
||||
struct CalogBerryT {
|
||||
bvm *vm;
|
||||
CalogT *broker;
|
||||
uint64_t ctxId;
|
||||
int32_t nextRef;
|
||||
bvm *vm;
|
||||
CalogT *broker;
|
||||
uint64_t ctxId;
|
||||
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
|
||||
|
|
@ -43,7 +49,9 @@ typedef struct BerryExportT {
|
|||
static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||
static void berryCallableRelease(CalogFnT *callable);
|
||||
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 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");
|
||||
}
|
||||
for (index = 0; index < argCount; index++) {
|
||||
status = berryFromValue(vm, &args[index], 0);
|
||||
status = berryFromValue(context, &args[index], 0);
|
||||
if (status != calogOkE) {
|
||||
be_pop(vm, be_top(vm) - base);
|
||||
return calogFail(result, status, "failed to marshal argument into berry");
|
||||
|
|
@ -121,9 +129,16 @@ static void berryCallableRelease(CalogFnT *callable) {
|
|||
|
||||
|
||||
void calogBerryDestroy(CalogBerryT *context) {
|
||||
int32_t index;
|
||||
|
||||
if (context == NULL) {
|
||||
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) {
|
||||
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) {
|
||||
be_pushnil(vm);
|
||||
return calogErrDepthE;
|
||||
|
|
@ -241,7 +314,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
|
|||
be_newmap(vm);
|
||||
for (index = 0; index < aggregate->arrayCount; 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_pop(vm, 2);
|
||||
}
|
||||
|
|
@ -255,7 +328,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
|
|||
} else {
|
||||
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_pop(vm, 2);
|
||||
}
|
||||
|
|
@ -263,7 +336,7 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
|
|||
className = "list";
|
||||
be_newlist(vm);
|
||||
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_pop(vm, 1);
|
||||
}
|
||||
|
|
@ -279,10 +352,24 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth)
|
|||
be_pop(vm, 2);
|
||||
return calogOkE;
|
||||
}
|
||||
case calogFnE:
|
||||
// Pushing a foreign function value into Berry is a v1 limit.
|
||||
be_pushnil(vm);
|
||||
return calogErrUnsupportedE;
|
||||
case calogFnE: {
|
||||
// Berry has no per-value finalizer, so track the foreign function on the
|
||||
// context (released at destroy) and wrap it in a native closure -- callable
|
||||
// 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);
|
||||
return calogErrTypeE;
|
||||
|
|
@ -352,11 +439,110 @@ static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, i
|
|||
calogValueFn(out, callable);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
CalogBerryT *context;
|
||||
const char *name;
|
||||
|
|
@ -408,7 +594,7 @@ static int berryTrampoline(bvm *vm) {
|
|||
be_raise(vm, "calog_error", message);
|
||||
return 0;
|
||||
}
|
||||
status = berryFromValue(vm, &result, 0);
|
||||
status = berryFromValue(context, &result, 0);
|
||||
calogValueFree(&result);
|
||||
if (status != calogOkE) {
|
||||
be_pop(vm, 1);
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
// Exposes broker-registered native functions into a Berry VM, marshals values between
|
||||
// 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,
|
||||
// dropped on release). An aggregate crossing OUT to a script becomes a Berry list or map
|
||||
// instance (so a host record is readable as user['name']); reading a script's list/map
|
||||
// back IN is a v1 limit -> calogErrUnsupportedE.
|
||||
// dropped on release). Aggregates cross both ways: OUT a host record is a Berry map
|
||||
// instance (user['name']); IN a script's list/map is read by iterating its raw container.
|
||||
// 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
|
||||
// (and drop every function value marshalled out) BEFORE calogBerryDestroy, since the
|
||||
|
|
|
|||
|
|
@ -166,6 +166,11 @@ int32_t calogAggPush(CalogAggT *aggregate, CalogValueT *value);
|
|||
int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value);
|
||||
|
||||
// ---- function values ----
|
||||
// Wrap a host native as a host-owned function value (runs on the host thread during
|
||||
// calogPump, like calogRegister, but anonymous). Hand the result to a script via a
|
||||
// native's result to give the script a callable; release your reference with
|
||||
// calogFnRelease once you no longer hold it.
|
||||
int32_t calogFnFromNative(CalogFnT **out, CalogT *calog, CalogNativeFnT fn, void *userData);
|
||||
int32_t calogFnInvoke(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result);
|
||||
void calogFnRetain(CalogFnT *fn);
|
||||
void calogFnRelease(CalogFnT *fn);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ struct CalogJsT {
|
|||
JSContext *ctx;
|
||||
CalogT *broker;
|
||||
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
|
||||
|
|
@ -49,6 +50,8 @@ typedef struct JsExportT {
|
|||
static int32_t jsCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||
static void jsCallableRelease(CalogFnT *callable);
|
||||
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 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);
|
||||
|
|
@ -76,6 +79,19 @@ int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint64_t ctxId) {
|
|||
context->broker = broker;
|
||||
context->ctxId = ctxId;
|
||||
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;
|
||||
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) {
|
||||
if (depth > CALOG_MAX_DEPTH) {
|
||||
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;
|
||||
}
|
||||
case calogFnE:
|
||||
// Pushing a foreign function value INTO JavaScript is a v1 fidelity limit
|
||||
// (export OUT is supported via calogJsExport).
|
||||
return JS_ThrowTypeError(ctx, "pushing a foreign function into JavaScript is unsupported");
|
||||
case calogFnE: {
|
||||
// Wrap the foreign function in a callable, finalized JS object; retain the
|
||||
// handle for the wrapper's lifetime (the finalizer releases it).
|
||||
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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MB_BANK_SIZE 32
|
||||
#define MB_BANK_SIZE 256
|
||||
#define MB_INITIAL_ARGS 8
|
||||
#define MB_NAME_MAX 128
|
||||
#define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE
|
||||
|
|
@ -90,13 +90,265 @@ MB_TRAMPOLINE(28)
|
|||
MB_TRAMPOLINE(29)
|
||||
MB_TRAMPOLINE(30)
|
||||
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
|
||||
|
||||
static const mb_func_t mbTrampTable[MB_BANK_SIZE] = {
|
||||
mbTramp0, mbTramp1, mbTramp2, mbTramp3, mbTramp4, mbTramp5, mbTramp6, mbTramp7,
|
||||
mbTramp8, mbTramp9, mbTramp10, mbTramp11, mbTramp12, mbTramp13, mbTramp14, mbTramp15,
|
||||
mbTramp16, mbTramp17, mbTramp18, mbTramp19, mbTramp20, mbTramp21, mbTramp22, mbTramp23,
|
||||
mbTramp24, mbTramp25, mbTramp26, mbTramp27, mbTramp28, mbTramp29, mbTramp30, mbTramp31
|
||||
mbTramp0, mbTramp1, mbTramp2, mbTramp3, mbTramp4, mbTramp5, mbTramp6, mbTramp7,
|
||||
mbTramp8, mbTramp9, mbTramp10, mbTramp11, mbTramp12, mbTramp13, mbTramp14, mbTramp15,
|
||||
mbTramp16, mbTramp17, mbTramp18, mbTramp19, mbTramp20, mbTramp21, mbTramp22, mbTramp23,
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
// "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
|
||||
// dispatches through calogCall (honoring the actor route hook). Marshalling covers
|
||||
// scalars, binary-safe strings, and aggregates crossing out (a Scheme list, or a
|
||||
// hash-table when keyed, so a record reads as (user "name")); reading a script's keyed
|
||||
// value back in is a v1 limit. A Scheme procedure crossing out becomes a refcounted
|
||||
// CalogFnT kept alive by s7_gc_protect (released with s7_gc_unprotect_at).
|
||||
// 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"); in: a list or a hash-table
|
||||
// iterated back into an aggregate). Functions cross both ways: a Scheme procedure out is
|
||||
// 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
|
||||
// source in (catch #t (lambda () (eval-string ...)) handler) so both read and run errors
|
||||
|
|
@ -34,6 +35,7 @@ struct CalogS7T {
|
|||
s7_scheme *sc;
|
||||
CalogT *broker;
|
||||
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
|
||||
|
|
@ -49,6 +51,8 @@ static void s7CallableRelease(CalogFnT *callable);
|
|||
static s7_pointer s7DispatchNative(s7_scheme *sc, s7_pointer args);
|
||||
static char *s7EscapeSource(const char *source);
|
||||
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 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.
|
||||
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");
|
||||
// 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;
|
||||
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) {
|
||||
s7_scheme *sc;
|
||||
|
||||
|
|
@ -322,9 +397,14 @@ static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32
|
|||
return list;
|
||||
}
|
||||
}
|
||||
case calogFnE:
|
||||
// Pushing a foreign function value into Scheme is a v1 limit.
|
||||
return s7_unspecified(sc);
|
||||
case calogFnE: {
|
||||
// Wrap the foreign function in an applicable c-object; retain for its lifetime
|
||||
// (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);
|
||||
}
|
||||
|
|
@ -398,6 +478,49 @@ static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, in
|
|||
calogValueFn(out, callable);
|
||||
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)) {
|
||||
CalogAggT *aggregate;
|
||||
int32_t status;
|
||||
|
|
@ -435,6 +558,6 @@ static int32_t s7ToValue(CalogS7T *context, s7_pointer obj, CalogValueT *out, in
|
|||
calogValueAgg(out, aggregate);
|
||||
return calogOkE;
|
||||
}
|
||||
// #<unspecified>, eof, symbols, hash-tables, etc. -> nil (v1).
|
||||
// #<unspecified>, eof, symbols, etc. -> nil.
|
||||
return calogOkE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// s7Adapter.h -- s7 Scheme engine adapter for the broker.
|
||||
//
|
||||
// Exposes broker-registered native functions into an s7 interpreter, marshals values
|
||||
// between Scheme and CalogValueT by value (binary-safe strings; scalars; aggregates
|
||||
// cross 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
|
||||
// s7_gc_protect, released with s7_gc_unprotect_at). Reading a script's keyed value (map)
|
||||
// back IN is a v1 limit -> calogErrUnsupportedE.
|
||||
// between Scheme and CalogValueT by value (binary-safe strings; scalars; aggregates both
|
||||
// ways -- OUT as a Scheme list or a hash-table when keyed so a record reads as (user
|
||||
// "name"), IN by iterating a list or hash-table back), and moves Scheme procedures out as
|
||||
// refcounted CalogFnT handles (kept alive by s7_gc_protect). A foreign CalogFnT pushed IN
|
||||
// becomes an applicable c-object the script calls as (f ...).
|
||||
//
|
||||
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogS7Export
|
||||
// (and drop every procedure value marshalled out) BEFORE calogS7Destroy, since the
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ static int32_t squirrelCallableInvoke(CalogValueT *args, int32_t argCount, Calo
|
|||
static void squirrelCallableRelease(CalogFnT *callable);
|
||||
static CalogSquirrelT *squirrelContextOf(HSQUIRRELVM v);
|
||||
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 int32_t squirrelPushValueDepth(HSQUIRRELVM v, const CalogValueT *value, 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, ...) {
|
||||
va_list args;
|
||||
|
||||
|
|
@ -342,10 +416,21 @@ static int32_t squirrelPushValueDepth(HSQUIRRELVM v, const CalogValueT *value, i
|
|||
}
|
||||
return calogOkE;
|
||||
}
|
||||
case calogFnE:
|
||||
// Pushing a foreign function value INTO Squirrel is the remaining v1
|
||||
// limit (export OUT is supported via calogSquirrelExport); see the header.
|
||||
return calogErrUnsupportedE;
|
||||
case calogFnE: {
|
||||
// Wrap the foreign function in a native closure whose one free variable is a
|
||||
// release-hooked userdata holding the CalogFnT (released when the closure is
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,10 @@
|
|||
// A Squirrel closure crossing the boundary becomes a refcounted CalogFnT over a
|
||||
// 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
|
||||
// argument is exported the same way during ingress marshalling. 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.
|
||||
// argument is exported the same way during ingress marshalling. A foreign CalogFnT going
|
||||
// the other way (pushed INTO Squirrel) becomes a native closure whose one free variable
|
||||
// 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.
|
||||
|
||||
#ifndef SQUIRREL_ADAPTER_H
|
||||
#define SQUIRREL_ADAPTER_H
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
if (callable == NULL) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -44,15 +44,27 @@ typedef struct WrenExportT {
|
|||
static int32_t wrenCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||
static void wrenCallableRelease(CalogFnT *callable);
|
||||
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 void wrenDispatch(WrenVM *vm);
|
||||
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 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 int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int32_t depth);
|
||||
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) {
|
||||
|
|
@ -69,6 +81,7 @@ int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) {
|
|||
wrenInitConfiguration(&config);
|
||||
config.userData = context;
|
||||
config.bindForeignMethodFn = wrenBindMethod;
|
||||
config.bindForeignClassFn = wrenBindClass;
|
||||
config.errorFn = wrenError;
|
||||
config.writeFn = wrenWrite;
|
||||
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) {
|
||||
(void)vm;
|
||||
(void)module;
|
||||
if (strcmp(className, "Calog") == 0 && isStatic && strcmp(signature, "call(_,_)") == 0) {
|
||||
return wrenDispatch;
|
||||
}
|
||||
if (strcmp(className, "CalogFn") == 0 && !isStatic && strcmp(signature, "call(_)") == 0) {
|
||||
return wrenForeignInvoke;
|
||||
}
|
||||
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) {
|
||||
WrenVM *vm;
|
||||
|
||||
|
|
@ -365,10 +472,17 @@ static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slo
|
|||
}
|
||||
return;
|
||||
}
|
||||
case calogFnE:
|
||||
// Pushing a foreign function value into Wren is a v1 limit.
|
||||
wrenSetSlotNull(vm, slot);
|
||||
case calogFnE: {
|
||||
// Wrap the foreign function in a CalogFn instance (script calls f.call([...]));
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
wrenSetSlotNull(vm, slot);
|
||||
}
|
||||
|
|
@ -451,8 +565,49 @@ static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int3
|
|||
calogValueAgg(out, aggregate);
|
||||
return calogOkE;
|
||||
}
|
||||
case WREN_TYPE_MAP:
|
||||
return calogErrUnsupportedE; // map read is a v1 limit (no key enumeration in the C API)
|
||||
case WREN_TYPE_MAP: {
|
||||
// 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_UNKNOWN: {
|
||||
// A non-primitive (typically a Fn) is captured as a callable.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
//
|
||||
// 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
|
||||
// CalogValueT by value (binary-safe strings; scalars; an aggregate crosses OUT as a Wren
|
||||
// List, or a Map when keyed so a record reads as user["name"]) and exports Wren functions
|
||||
// as refcounted CalogFnT handles (a retained WrenHandle, released with wrenReleaseHandle).
|
||||
// Numbers are IEEE doubles, so int64 magnitudes above 2^53 lose precision. Reading a
|
||||
// script's list/map back IN is a v1 limit (Wren's C API cannot enumerate a Map's keys).
|
||||
// CalogValueT by value (binary-safe strings; scalars; aggregates both ways -- OUT as a
|
||||
// Wren List, or a Map when keyed so a record reads as user["name"]; IN a List or a Map,
|
||||
// the Map via a small calog patch to wren.c that enumerates map keys, which upstream's C
|
||||
// API cannot). Functions cross both ways: OUT as a refcounted CalogFnT over a retained
|
||||
// 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
|
||||
// (and drop every function value marshalled out) BEFORE calogWrenDestroy, since the
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 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 nativeReportInline(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 testConcurrentContexts(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(void);
|
||||
static void testMapIngress(void);
|
||||
static void testMaterializedRecord(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
CalogAggT *user;
|
||||
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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
CalogFnT *callback;
|
||||
|
|
@ -293,10 +380,14 @@ int main(void) {
|
|||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegister(calog, "mapAge", nativeMapAge, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testMaterializedRecord();
|
||||
testMapIngress();
|
||||
testForeignFunction();
|
||||
testCrossThreadCallback();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 nativeReportInline(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 testConcurrentContexts(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
int32_t before;
|
||||
|
|
@ -225,10 +273,12 @@ int main(void) {
|
|||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||
calogRegister(calog, "done", nativeDone, NULL);
|
||||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testCrossThreadCallback();
|
||||
testForeignFunction();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 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 nativeReportInline(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 testContextLoad(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(void);
|
||||
static void testScriptError(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
int32_t before;
|
||||
|
|
@ -322,10 +370,12 @@ int main(void) {
|
|||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||
calogRegister(calog, "done", nativeDone, NULL);
|
||||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testCrossThreadCallback();
|
||||
testForeignFunction();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
testSingleThreadMultiRuntime();
|
||||
|
|
|
|||
|
|
@ -41,9 +41,12 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 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 nativeReportInline(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 testConcurrentContexts(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(void);
|
||||
static void testMapIngress(void);
|
||||
static void testMaterializedRecord(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
CalogAggT *user;
|
||||
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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
int32_t before;
|
||||
|
|
@ -302,11 +388,15 @@ int main(void) {
|
|||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegister(calog, "mapAge", nativeMapAge, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testMaterializedRecord();
|
||||
testMapIngress();
|
||||
testCrossThreadCallback();
|
||||
testForeignFunction();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 nativeReportInline(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 testConcurrentContexts(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
int32_t before;
|
||||
|
|
@ -225,10 +273,12 @@ int main(void) {
|
|||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||
calogRegister(calog, "done", nativeDone, NULL);
|
||||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testCrossThreadCallback();
|
||||
testForeignFunction();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ static int32_t testsRun = 0;
|
|||
static int32_t testsFailed = 0;
|
||||
|
||||
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 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 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 nativeReportInline(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 testConcurrentContexts(void);
|
||||
static void testCrossThreadCallback(void);
|
||||
static void testForeignFunction(void);
|
||||
static void testHostAndInlineNatives(void);
|
||||
static void testMapIngress(void);
|
||||
static void testMaterializedRecord(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) {
|
||||
(void)args;
|
||||
(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) {
|
||||
CalogAggT *user;
|
||||
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) {
|
||||
(void)userData;
|
||||
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) {
|
||||
CalogContextT *ctx;
|
||||
CalogFnT *callback;
|
||||
|
|
@ -293,10 +380,14 @@ int main(void) {
|
|||
calogRegister(calog, "bump", nativeBump, NULL);
|
||||
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||
calogRegister(calog, "getAdder", nativeGetAdder, NULL);
|
||||
calogRegister(calog, "mapAge", nativeMapAge, NULL);
|
||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||
|
||||
testHostAndInlineNatives();
|
||||
testMaterializedRecord();
|
||||
testMapIngress();
|
||||
testForeignFunction();
|
||||
testCrossThreadCallback();
|
||||
testScriptError();
|
||||
testConcurrentContexts();
|
||||
|
|
|
|||
11
vendor/berry/berry_conf.h
vendored
11
vendor/berry/berry_conf.h
vendored
|
|
@ -37,12 +37,13 @@
|
|||
#define BE_USE_SINGLE_FLOAT 0
|
||||
|
||||
/* Macro: BE_BYTES_MAX_SIZE
|
||||
* Maximum size in bytes of a `bytes()` object.
|
||||
* Putting too much pressure on the memory allocator can do
|
||||
* harm, so we limit the maximum size.
|
||||
* Default: 32kb
|
||||
* Maximum size in bytes of a `bytes()` object. This is a ceiling, not a
|
||||
* preallocation -- a script only uses what it allocates -- so it is set
|
||||
* generously here (calog raised it from Berry's 32 kb default). The hard
|
||||
* 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
|
||||
* Use precompiled objects to avoid creating these objects at
|
||||
|
|
|
|||
28
vendor/wren/wren.c
vendored
28
vendor/wren/wren.c
vendored
|
|
@ -4652,6 +4652,34 @@ void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot)
|
|||
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)
|
||||
{
|
||||
validateApiSlot(vm, mapSlot);
|
||||
|
|
|
|||
11
vendor/wren/wren.h
vendored
11
vendor/wren/wren.h
vendored
|
|
@ -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,
|
||||
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
|
||||
// it in [slot].
|
||||
WREN_API void wrenGetVariable(WrenVM* vm, const char* module, const char* name,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue