Improved sharing of maps between languages.
This commit is contained in:
parent
58a9008458
commit
c11bf8481f
12 changed files with 392 additions and 58 deletions
10
README.md
10
README.md
|
|
@ -55,9 +55,13 @@ Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`,
|
||||||
| JavaScript | `calogJsEngine` | `.js` | QuickJS-ng (ES2023+, BigInt → int64) |
|
| JavaScript | `calogJsEngine` | `.js` | QuickJS-ng (ES2023+, BigInt → int64) |
|
||||||
| Squirrel 3.2 | `calogSquirrelEngine` | `.nut` | C++ VM |
|
| Squirrel 3.2 | `calogSquirrelEngine` | `.nut` | C++ VM |
|
||||||
| MY-BASIC | `calogMyBasicEngine` | `.bas` | interpreters serialize at load |
|
| MY-BASIC | `calogMyBasicEngine` | `.bas` | interpreters serialize at load |
|
||||||
| Scheme | `calogS7Engine` | `.scm` | s7; 64-bit ints, lists |
|
| Scheme | `calogS7Engine` | `.scm` | s7; 64-bit ints |
|
||||||
| Wren | `calogWrenEngine` | `.wren` | doubles only; call via `Calog.call_(…)` |
|
| Wren | `calogWrenEngine` | `.wren` | doubles only; call via `Calog.call(…)` |
|
||||||
| Berry | `calogBerryEngine` | `.be` | scalars + callbacks (aggregates: v1) |
|
| 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.
|
||||||
|
|
||||||
You can also bring your own: `CalogEngineT` is a public four-function vtable.
|
You can also bring your own: `CalogEngineT` is a public four-function vtable.
|
||||||
|
|
||||||
|
|
|
||||||
31
design.md
31
design.md
|
|
@ -897,8 +897,11 @@ under a uniquely-named hidden global (globals are GC roots) and dropped by setti
|
||||||
nil. The sharp edge: `be_pcall(vm, argc)` leaves the **result in the function's slot
|
nil. The sharp edge: `be_pcall(vm, argc)` leaves the **result in the function's slot
|
||||||
(`base+1`)**, not at `-1` (which holds the last stale argument). Vendoring needs Berry's
|
(`base+1`)**, not at `-1` (which holds the last stale argument). Vendoring needs Berry's
|
||||||
`coc` codegen prebuild plus its OS port (`be_port.c`) and module/class tables
|
`coc` codegen prebuild plus its OS port (`be_port.c`) and module/class tables
|
||||||
(`be_modtab.c`). Aggregate marshalling is a v1 limit (scalars + strings + callbacks are
|
(`be_modtab.c`). A subtlety for records: `be_newmap`/`be_newlist` push *raw* containers
|
||||||
complete).
|
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.
|
||||||
|
|
||||||
**s7 Scheme** (`.scm`) uses the *current* official s7 (an older mirror lacked `s7_free`,
|
**s7 Scheme** (`.scm`) uses the *current* official s7 (an older mirror lacked `s7_free`,
|
||||||
which would leak a heap per context). Since s7 native functions carry no user data, all
|
which would leak a heap per context). Since s7 native functions carry no user data, all
|
||||||
|
|
@ -907,20 +910,30 @@ wrapper (`(define (report . a) (apply %calog-call "report" a))`); the context ri
|
||||||
`*calog-context*` c-pointer global. Callables are kept alive by `s7_gc_protect` and
|
`*calog-context*` c-pointer global. Callables are kept alive by `s7_gc_protect` and
|
||||||
invoked with `s7_call`. Because `s7_eval_c_string` evaluates a single form, `calogS7Run`
|
invoked with `s7_call`. Because `s7_eval_c_string` evaluates a single form, `calogS7Run`
|
||||||
wraps the (escaped) source in `(catch #t (lambda () (eval-string …)) handler)`, so both
|
wraps the (escaped) source in `(catch #t (lambda () (eval-string …)) handler)`, so both
|
||||||
read and run errors surface as a value -- a marker pair the runner detects. int64 and
|
read and run errors surface as a value -- a marker pair the runner detects. An aggregate
|
||||||
Scheme lists round-trip both ways; the keyed part (map) is a v1 limit. s7's intentional
|
crossing out is a Scheme list, or an (applicable) hash-table when keyed, so a materialized
|
||||||
|
record reads as `(user "name")`; reading a script's keyed value back in is a v1 limit. s7's intentional
|
||||||
"permanent string" interning (which `s7_free` does not reclaim) is a small, bounded
|
"permanent string" interning (which `s7_free` does not reclaim) is a small, bounded
|
||||||
allocation, suppressed with a documented, allocation-site-specific `__lsan` hook. s7 is
|
allocation, suppressed with a documented, allocation-site-specific `__lsan` hook. s7 is
|
||||||
per-interpreter thread-safe -- no serialization needed (unlike my-basic).
|
per-interpreter thread-safe -- no serialization needed (unlike my-basic).
|
||||||
|
|
||||||
**Wren** (`.wren`) is the outlier: Wren has **no bare function calls**, so every native is
|
**Wren** (`.wren`) is the outlier: Wren has **no bare function calls**, so every native is
|
||||||
reached through a single foreign method. A preamble defines `class Calog { foreign static
|
reached through a single foreign method. A preamble defines `class Calog { foreign static
|
||||||
call_(name, args) }`, and scripts call `Calog.call_("report", [42])`; the C dispatcher
|
call(name, args) }`, and scripts call `Calog.call("report", [42])`; the C dispatcher
|
||||||
recovers the context from `wrenGetUserData`, marshals the argument list, and dispatches
|
recovers the context from `wrenGetUserData`, marshals the argument list, and dispatches
|
||||||
through `calogCall`. A Wren function crossing out is a retained `WrenHandle`, invoked with
|
through `calogCall`. A Wren function crossing out is a retained `WrenHandle`, invoked with
|
||||||
a cached per-arity `call(_)` handle. Wren numbers are IEEE **doubles**, so int64 above
|
a cached per-arity `call(_)` handle. Wren numbers are IEEE **doubles**, so int64 above
|
||||||
2^53 loses precision (the same edge my-basic and old-JS have). Lists round-trip; map read
|
2^53 loses precision (the same edge my-basic and old-JS have). An aggregate crossing out
|
||||||
is a v1 limit. Wren keeps no process-global state, so contexts run in parallel.
|
is a Wren `List`, or a `Map` when keyed (a record reads as `user["name"]`); reading a
|
||||||
|
script's list/map back in is a v1 limit -- Wren's C API cannot enumerate a `Map`'s keys.
|
||||||
|
Wren keeps no process-global state, so contexts run in parallel.
|
||||||
|
|
||||||
**Verified** across all seven engines: `make test` (525 checks), ASan/UBSan clean, a
|
Aggregate egress is therefore uniform across all seven engines: a host native can return a
|
||||||
`tsan<engine>` target clean for each, and gcc + clang strict on the core.
|
keyed `CalogValueT` record and every engine reads its fields with native syntax
|
||||||
|
(`user.name` / `user['name']` / `user["name"]` / `(user "name")`). The reverse -- a script
|
||||||
|
handing a list/map *back* to C -- is complete on Lua/JS/Squirrel/my-basic and a v1 limit on
|
||||||
|
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.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
// calogCall (honoring the actor route hook). Marshalling covers scalars and binary-safe
|
// calogCall (honoring the actor route hook). Marshalling covers scalars and binary-safe
|
||||||
// strings; a Berry function crossing out becomes a refcounted CalogFnT kept reachable by
|
// strings; 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
|
// a uniquely-named hidden global (a GC root), dropped by setting that global to nil on
|
||||||
// release. Aggregates (list/map) are a v1 limit -> calogErrUnsupportedE.
|
// 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.
|
||||||
//
|
//
|
||||||
// Error model: on failure the trampoline raises a Berry exception (be_raise, which
|
// Error model: on failure the trampoline raises a Berry exception (be_raise, which
|
||||||
// longjmps out of the VM) after releasing any CalogValueT it owns.
|
// longjmps out of the VM) after releasing any CalogValueT it owns.
|
||||||
|
|
@ -42,7 +43,7 @@ typedef struct BerryExportT {
|
||||||
static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static void berryCallableRelease(CalogFnT *callable);
|
static void berryCallableRelease(CalogFnT *callable);
|
||||||
static int32_t berryExportValue(CalogBerryT *context, int index, CalogFnT **out);
|
static int32_t berryExportValue(CalogBerryT *context, int index, CalogFnT **out);
|
||||||
static int32_t berryFromValue(bvm *vm, const CalogValueT *value);
|
static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth);
|
||||||
static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, int32_t depth);
|
static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, int32_t depth);
|
||||||
static int berryTrampoline(bvm *vm);
|
static int berryTrampoline(bvm *vm);
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@ static int32_t berryCallableInvoke(CalogValueT *args, int32_t argCount, CalogVal
|
||||||
return calogFail(result, calogErrDeadE, "berry callable no longer exists");
|
return calogFail(result, calogErrDeadE, "berry callable no longer exists");
|
||||||
}
|
}
|
||||||
for (index = 0; index < argCount; index++) {
|
for (index = 0; index < argCount; index++) {
|
||||||
status = berryFromValue(vm, &args[index]);
|
status = berryFromValue(vm, &args[index], 0);
|
||||||
if (status != calogOkE) {
|
if (status != calogOkE) {
|
||||||
be_pop(vm, be_top(vm) - base);
|
be_pop(vm, be_top(vm) - base);
|
||||||
return calogFail(result, status, "failed to marshal argument into berry");
|
return calogFail(result, status, "failed to marshal argument into berry");
|
||||||
|
|
@ -206,7 +207,11 @@ int32_t calogBerryExpose(CalogBerryT *context, const char *name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t berryFromValue(bvm *vm, const CalogValueT *value) {
|
static int32_t berryFromValue(bvm *vm, const CalogValueT *value, int32_t depth) {
|
||||||
|
if (depth > CALOG_MAX_DEPTH) {
|
||||||
|
be_pushnil(vm);
|
||||||
|
return calogErrDepthE;
|
||||||
|
}
|
||||||
switch (value->type) {
|
switch (value->type) {
|
||||||
case calogNilE:
|
case calogNilE:
|
||||||
be_pushnil(vm);
|
be_pushnil(vm);
|
||||||
|
|
@ -223,10 +228,57 @@ static int32_t berryFromValue(bvm *vm, const CalogValueT *value) {
|
||||||
case calogStringE:
|
case calogStringE:
|
||||||
be_pushnstring(vm, value->as.s.bytes, (size_t)value->as.s.length);
|
be_pushnstring(vm, value->as.s.bytes, (size_t)value->as.s.length);
|
||||||
return calogOkE;
|
return calogOkE;
|
||||||
case calogAggE:
|
case calogAggE: {
|
||||||
// Aggregate marshalling into Berry is a v1 limit.
|
CalogAggT *aggregate;
|
||||||
be_pushnil(vm);
|
const char *className;
|
||||||
return calogErrUnsupportedE;
|
int64_t index;
|
||||||
|
aggregate = value->as.agg;
|
||||||
|
// Populate a raw container: anything keyed (or an explicit map) is a map with
|
||||||
|
// the sequence part at integer keys, else a list. be_setindex and be_data_push
|
||||||
|
// take the value on top and do not pop, so drop it after each.
|
||||||
|
if (aggregate->pairCount > 0 || aggregate->kind == calogMapE) {
|
||||||
|
className = "map";
|
||||||
|
be_newmap(vm);
|
||||||
|
for (index = 0; index < aggregate->arrayCount; index++) {
|
||||||
|
be_pushint(vm, (bint)index);
|
||||||
|
berryFromValue(vm, &aggregate->array[index], depth + 1);
|
||||||
|
be_setindex(vm, -3);
|
||||||
|
be_pop(vm, 2);
|
||||||
|
}
|
||||||
|
for (index = 0; index < aggregate->pairCount; index++) {
|
||||||
|
const CalogValueT *key;
|
||||||
|
key = &aggregate->pairs[index].key;
|
||||||
|
if (key->type == calogStringE) {
|
||||||
|
be_pushnstring(vm, key->as.s.bytes, (size_t)key->as.s.length);
|
||||||
|
} else if (key->type == calogIntE) {
|
||||||
|
be_pushint(vm, (bint)key->as.i);
|
||||||
|
} else {
|
||||||
|
continue; // non-scalar key: no Berry equivalent, drop the pair
|
||||||
|
}
|
||||||
|
berryFromValue(vm, &aggregate->pairs[index].value, depth + 1);
|
||||||
|
be_setindex(vm, -3);
|
||||||
|
be_pop(vm, 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
className = "list";
|
||||||
|
be_newlist(vm);
|
||||||
|
for (index = 0; index < aggregate->arrayCount; index++) {
|
||||||
|
berryFromValue(vm, &aggregate->array[index], depth + 1);
|
||||||
|
be_data_push(vm, -2);
|
||||||
|
be_pop(vm, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A raw map/list is not subscriptable in a script; wrap it in its class
|
||||||
|
// instance (map(raw) / list(raw)). Calling a class leaves the instance and
|
||||||
|
// init's (nil) return above the raw, so move the instance down over the raw
|
||||||
|
// and drop the two spare slots.
|
||||||
|
be_getbuiltin(vm, className);
|
||||||
|
be_pushvalue(vm, -2);
|
||||||
|
be_call(vm, 1);
|
||||||
|
be_moveto(vm, -2, -3);
|
||||||
|
be_pop(vm, 2);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
case calogFnE:
|
case calogFnE:
|
||||||
// Pushing a foreign function value into Berry is a v1 limit.
|
// Pushing a foreign function value into Berry is a v1 limit.
|
||||||
be_pushnil(vm);
|
be_pushnil(vm);
|
||||||
|
|
@ -300,7 +352,7 @@ static int32_t berryToValue(CalogBerryT *context, int index, CalogValueT *out, i
|
||||||
calogValueFn(out, callable);
|
calogValueFn(out, callable);
|
||||||
return calogOkE;
|
return calogOkE;
|
||||||
}
|
}
|
||||||
// list / map marshalling out of Berry is a v1 limit.
|
// Reading a script's list/map into an aggregate is a v1 limit.
|
||||||
return calogErrUnsupportedE;
|
return calogErrUnsupportedE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -356,7 +408,7 @@ static int berryTrampoline(bvm *vm) {
|
||||||
be_raise(vm, "calog_error", message);
|
be_raise(vm, "calog_error", message);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
status = berryFromValue(vm, &result);
|
status = berryFromValue(vm, &result, 0);
|
||||||
calogValueFree(&result);
|
calogValueFree(&result);
|
||||||
if (status != calogOkE) {
|
if (status != calogOkE) {
|
||||||
be_pop(vm, 1);
|
be_pop(vm, 1);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
// Exposes broker-registered native functions into a Berry VM, marshals values between
|
// Exposes broker-registered native functions into a Berry VM, marshals values between
|
||||||
// Berry and CalogValueT by value (binary-safe strings; scalars), and exports Berry
|
// Berry and CalogValueT by value (binary-safe strings; scalars), and exports Berry
|
||||||
// functions as refcounted CalogFnT handles (kept alive by a GC-rooted hidden global,
|
// functions as refcounted CalogFnT handles (kept alive by a GC-rooted hidden global,
|
||||||
// dropped on release). Aggregate (list/map) marshalling is a v1 limit -- passing a
|
// dropped on release). An aggregate crossing OUT to a script becomes a Berry list or map
|
||||||
// list/map across the boundary reports calogErrUnsupportedE.
|
// instance (so a host record is readable as user['name']); reading a script's list/map
|
||||||
|
// back IN is a v1 limit -> calogErrUnsupportedE.
|
||||||
//
|
//
|
||||||
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogBerryExport
|
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogBerryExport
|
||||||
// (and drop every function value marshalled out) BEFORE calogBerryDestroy, since the
|
// (and drop every function value marshalled out) BEFORE calogBerryDestroy, since the
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@
|
||||||
// "report" a), so the dispatcher recovers the name from its first argument and the
|
// "report" a), so the dispatcher recovers the name from its first argument and the
|
||||||
// context from a hidden *calog-context* c-pointer, marshals the rest to CalogValueT, and
|
// context from a hidden *calog-context* c-pointer, marshals the rest to CalogValueT, and
|
||||||
// dispatches through calogCall (honoring the actor route hook). Marshalling covers
|
// dispatches through calogCall (honoring the actor route hook). Marshalling covers
|
||||||
// scalars, binary-safe strings, and Scheme lists (<-> aggregate list); the keyed part
|
// scalars, binary-safe strings, and aggregates crossing out (a Scheme list, or a
|
||||||
// of an aggregate (map) is a v1 limit. A Scheme procedure crossing out becomes a
|
// hash-table when keyed, so a record reads as (user "name")); reading a script's keyed
|
||||||
// refcounted CalogFnT kept alive by s7_gc_protect (released with s7_gc_unprotect_at).
|
// 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).
|
||||||
//
|
//
|
||||||
// Error model: a native failure raises a Scheme error via s7_error. calogS7Run wraps the
|
// Error model: a native failure raises a Scheme error via s7_error. calogS7Run wraps the
|
||||||
// source in (catch #t (lambda () (eval-string ...)) handler) so both read and run errors
|
// source in (catch #t (lambda () (eval-string ...)) handler) so both read and run errors
|
||||||
|
|
@ -281,17 +282,45 @@ static s7_pointer s7FromValue(CalogS7T *context, const CalogValueT *value, int32
|
||||||
return s7_make_string_with_length(sc, value->as.s.bytes, (s7_int)value->as.s.length);
|
return s7_make_string_with_length(sc, value->as.s.bytes, (s7_int)value->as.s.length);
|
||||||
case calogAggE: {
|
case calogAggE: {
|
||||||
CalogAggT *aggregate;
|
CalogAggT *aggregate;
|
||||||
s7_pointer list;
|
|
||||||
int64_t index;
|
int64_t index;
|
||||||
aggregate = value->as.agg;
|
aggregate = value->as.agg;
|
||||||
// The keyed part (map) is a v1 limit; the sequence becomes a Scheme list.
|
// Anything keyed (or an explicit map) becomes a hash-table (applicable, so a
|
||||||
list = s7_nil(sc);
|
// script reads (user "name")) with the sequence part at integer keys; a pure
|
||||||
for (index = aggregate->arrayCount - 1; index >= 0; index--) {
|
// sequence becomes a Scheme list.
|
||||||
s7_pointer element;
|
if (aggregate->pairCount > 0 || aggregate->kind == calogMapE) {
|
||||||
element = s7FromValue(context, &aggregate->array[index], depth + 1);
|
s7_pointer table;
|
||||||
list = s7_cons(sc, element, list);
|
s7_int size;
|
||||||
|
size = (s7_int)(aggregate->arrayCount + aggregate->pairCount);
|
||||||
|
table = s7_make_hash_table(sc, size > 0 ? size : 1);
|
||||||
|
for (index = 0; index < aggregate->arrayCount; index++) {
|
||||||
|
s7_hash_table_set(sc, table, s7_make_integer(sc, (s7_int)index),
|
||||||
|
s7FromValue(context, &aggregate->array[index], depth + 1));
|
||||||
|
}
|
||||||
|
for (index = 0; index < aggregate->pairCount; index++) {
|
||||||
|
const CalogValueT *key;
|
||||||
|
s7_pointer keyObj;
|
||||||
|
key = &aggregate->pairs[index].key;
|
||||||
|
if (key->type == calogStringE) {
|
||||||
|
keyObj = s7_make_string_with_length(sc, key->as.s.bytes, (s7_int)key->as.s.length);
|
||||||
|
} else if (key->type == calogIntE) {
|
||||||
|
keyObj = s7_make_integer(sc, (s7_int)key->as.i);
|
||||||
|
} else {
|
||||||
|
continue; // non-scalar key: drop the pair
|
||||||
|
}
|
||||||
|
s7_hash_table_set(sc, table, keyObj,
|
||||||
|
s7FromValue(context, &aggregate->pairs[index].value, depth + 1));
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
} else {
|
||||||
|
s7_pointer list;
|
||||||
|
list = s7_nil(sc);
|
||||||
|
for (index = aggregate->arrayCount - 1; index >= 0; index--) {
|
||||||
|
s7_pointer element;
|
||||||
|
element = s7FromValue(context, &aggregate->array[index], depth + 1);
|
||||||
|
list = s7_cons(sc, element, list);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
case calogFnE:
|
case calogFnE:
|
||||||
// Pushing a foreign function value into Scheme is a v1 limit.
|
// Pushing a foreign function value into Scheme is a v1 limit.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// s7Adapter.h -- s7 Scheme engine adapter for the broker.
|
// s7Adapter.h -- s7 Scheme engine adapter for the broker.
|
||||||
//
|
//
|
||||||
// Exposes broker-registered native functions into an s7 interpreter, marshals values
|
// Exposes broker-registered native functions into an s7 interpreter, marshals values
|
||||||
// between Scheme and CalogValueT by value (binary-safe strings; scalars; Scheme list
|
// between Scheme and CalogValueT by value (binary-safe strings; scalars; aggregates
|
||||||
// <-> aggregate list), and exports Scheme procedures as refcounted CalogFnT handles
|
// cross OUT as a Scheme list, or a hash-table when keyed so a record reads as (user
|
||||||
// (kept alive by s7_gc_protect, released with s7_gc_unprotect_at). The keyed part of an
|
// "name")), and exports Scheme procedures as refcounted CalogFnT handles (kept alive by
|
||||||
// aggregate (map) is a v1 limit -> calogErrUnsupportedE.
|
// s7_gc_protect, released with s7_gc_unprotect_at). Reading a script's keyed value (map)
|
||||||
|
// back IN is a v1 limit -> calogErrUnsupportedE.
|
||||||
//
|
//
|
||||||
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogS7Export
|
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogS7Export
|
||||||
// (and drop every procedure value marshalled out) BEFORE calogS7Destroy, since the
|
// (and drop every procedure value marshalled out) BEFORE calogS7Destroy, since the
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// Wren is class/method-oriented with no bare function calls, so all natives are reached
|
// Wren is class/method-oriented with no bare function calls, so all natives are reached
|
||||||
// through a single foreign method: a preamble defines `class Calog { foreign static
|
// through a single foreign method: a preamble defines `class Calog { foreign static
|
||||||
// call_(name, args) }`, and a script calls Calog.call_("report", [42]). The bound C
|
// call(name, args) }`, and a script calls Calog.call("report", [42]). The bound C
|
||||||
// dispatcher recovers the context from wrenGetUserData, marshals the argument list to
|
// dispatcher recovers the context from wrenGetUserData, marshals the argument list to
|
||||||
// CalogValueT, and dispatches through calogCall (honoring the actor route hook). A Wren
|
// CalogValueT, and dispatches through calogCall (honoring the actor route hook). A Wren
|
||||||
// function crossing out becomes a refcounted CalogFnT over a retained WrenHandle, invoked
|
// function crossing out becomes a refcounted CalogFnT over a retained WrenHandle, invoked
|
||||||
|
|
@ -52,7 +52,7 @@ static void wrenFromValue(CalogWrenT *context, const CalogValueT
|
||||||
static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int32_t depth);
|
static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int32_t depth);
|
||||||
static void wrenWrite(WrenVM *vm, const char *text);
|
static void wrenWrite(WrenVM *vm, const char *text);
|
||||||
|
|
||||||
static const char WREN_PREAMBLE[] = "class Calog { foreign static call_(name, args) }";
|
static const char WREN_PREAMBLE[] = "class Calog { foreign static call(name, args) }";
|
||||||
|
|
||||||
|
|
||||||
int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) {
|
int32_t calogWrenCreate(CalogWrenT **out, CalogT *broker, uint64_t ctxId) {
|
||||||
|
|
@ -177,7 +177,7 @@ void calogWrenDestroy(CalogWrenT *context) {
|
||||||
static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) {
|
static WrenForeignMethodFn wrenBindMethod(WrenVM *vm, const char *module, const char *className, bool isStatic, const char *signature) {
|
||||||
(void)vm;
|
(void)vm;
|
||||||
(void)module;
|
(void)module;
|
||||||
if (strcmp(className, "Calog") == 0 && isStatic && strcmp(signature, "call_(_,_)") == 0) {
|
if (strcmp(className, "Calog") == 0 && isStatic && strcmp(signature, "call(_,_)") == 0) {
|
||||||
return wrenDispatch;
|
return wrenDispatch;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -294,7 +294,7 @@ static int32_t wrenExportSlot(CalogWrenT *context, int slot, CalogFnT **out) {
|
||||||
|
|
||||||
|
|
||||||
int32_t calogWrenExpose(CalogWrenT *context, const char *name) {
|
int32_t calogWrenExpose(CalogWrenT *context, const char *name) {
|
||||||
// Every native is reachable through Calog.call_(name, args); nothing per-name to do.
|
// Every native is reachable through Calog.call(name, args); nothing per-name to do.
|
||||||
if (calogLookup(context->broker, name) == NULL) {
|
if (calogLookup(context->broker, name) == NULL) {
|
||||||
return calogErrNotFoundE;
|
return calogErrNotFoundE;
|
||||||
}
|
}
|
||||||
|
|
@ -331,11 +331,37 @@ static void wrenFromValue(CalogWrenT *context, const CalogValueT *value, int slo
|
||||||
CalogAggT *aggregate;
|
CalogAggT *aggregate;
|
||||||
int64_t index;
|
int64_t index;
|
||||||
aggregate = value->as.agg;
|
aggregate = value->as.agg;
|
||||||
wrenSetSlotNewList(vm, slot); // the keyed part (map) is a v1 limit
|
// Anything keyed (or an explicit map) becomes a Wren Map with the sequence
|
||||||
wrenEnsureSlots(vm, slot + 2);
|
// part at numeric keys (a script reads user["name"]); a pure sequence
|
||||||
for (index = 0; index < aggregate->arrayCount; index++) {
|
// becomes a Wren List. slot+1 = key scratch, slot+2 = value scratch.
|
||||||
wrenFromValue(context, &aggregate->array[index], slot + 1, depth + 1);
|
if (aggregate->pairCount > 0 || aggregate->kind == calogMapE) {
|
||||||
wrenInsertInList(vm, slot, -1, slot + 1);
|
wrenSetSlotNewMap(vm, slot);
|
||||||
|
wrenEnsureSlots(vm, slot + 3);
|
||||||
|
for (index = 0; index < aggregate->arrayCount; index++) {
|
||||||
|
wrenSetSlotDouble(vm, slot + 1, (double)index);
|
||||||
|
wrenFromValue(context, &aggregate->array[index], slot + 2, depth + 1);
|
||||||
|
wrenSetMapValue(vm, slot, slot + 1, slot + 2);
|
||||||
|
}
|
||||||
|
for (index = 0; index < aggregate->pairCount; index++) {
|
||||||
|
const CalogValueT *key;
|
||||||
|
key = &aggregate->pairs[index].key;
|
||||||
|
if (key->type == calogStringE) {
|
||||||
|
wrenSetSlotBytes(vm, slot + 1, key->as.s.bytes, (size_t)key->as.s.length);
|
||||||
|
} else if (key->type == calogIntE) {
|
||||||
|
wrenSetSlotDouble(vm, slot + 1, (double)key->as.i);
|
||||||
|
} else {
|
||||||
|
continue; // non-scalar key: drop the pair
|
||||||
|
}
|
||||||
|
wrenFromValue(context, &aggregate->pairs[index].value, slot + 2, depth + 1);
|
||||||
|
wrenSetMapValue(vm, slot, slot + 1, slot + 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wrenSetSlotNewList(vm, slot);
|
||||||
|
wrenEnsureSlots(vm, slot + 2);
|
||||||
|
for (index = 0; index < aggregate->arrayCount; index++) {
|
||||||
|
wrenFromValue(context, &aggregate->array[index], slot + 1, depth + 1);
|
||||||
|
wrenInsertInList(vm, slot, -1, slot + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +452,7 @@ static int32_t wrenToValue(CalogWrenT *context, int slot, CalogValueT *out, int3
|
||||||
return calogOkE;
|
return calogOkE;
|
||||||
}
|
}
|
||||||
case WREN_TYPE_MAP:
|
case WREN_TYPE_MAP:
|
||||||
return calogErrUnsupportedE; // map read is a v1 limit
|
return calogErrUnsupportedE; // map read is a v1 limit (no key enumeration in the C API)
|
||||||
case WREN_TYPE_FOREIGN:
|
case WREN_TYPE_FOREIGN:
|
||||||
case WREN_TYPE_UNKNOWN: {
|
case WREN_TYPE_UNKNOWN: {
|
||||||
// A non-primitive (typically a Fn) is captured as a callable.
|
// A non-primitive (typically a Fn) is captured as a callable.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// wrenAdapter.h -- Wren engine adapter for the broker.
|
// wrenAdapter.h -- Wren engine adapter for the broker.
|
||||||
//
|
//
|
||||||
// Wren has no bare function calls, so natives are reached through one foreign method:
|
// Wren has no bare function calls, so natives are reached through one foreign method:
|
||||||
// scripts call Calog.call_("name", [args]). The adapter marshals values between Wren and
|
// scripts call Calog.call("name", [args]). The adapter marshals values between Wren and
|
||||||
// CalogValueT by value (binary-safe strings; scalars; Wren list <-> aggregate list) and
|
// CalogValueT by value (binary-safe strings; scalars; an aggregate crosses OUT as a Wren
|
||||||
// exports Wren functions as refcounted CalogFnT handles (a retained WrenHandle, released
|
// List, or a Map when keyed so a record reads as user["name"]) and exports Wren functions
|
||||||
// with wrenReleaseHandle). Numbers are IEEE doubles, so int64 magnitudes above 2^53 lose
|
// as refcounted CalogFnT handles (a retained WrenHandle, released with wrenReleaseHandle).
|
||||||
// precision. The keyed part of an aggregate (map) is a v1 limit.
|
// 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).
|
||||||
//
|
//
|
||||||
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogWrenExport
|
// Lifetime note (as with Lua/JS): release every CalogFnT obtained from calogWrenExport
|
||||||
// (and drop every function value marshalled out) BEFORE calogWrenDestroy, since the
|
// (and drop every function value marshalled out) BEFORE calogWrenDestroy, since the
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,25 @@ static _Atomic bool scriptDone = false;
|
||||||
static _Atomic int32_t errorCount = 0;
|
static _Atomic int32_t errorCount = 0;
|
||||||
static _Atomic uint64_t errorCtxId = 0;
|
static _Atomic uint64_t errorCtxId = 0;
|
||||||
static CalogFnT *_Atomic storedCb = NULL;
|
static CalogFnT *_Atomic storedCb = NULL;
|
||||||
|
static char storedName[32] = { 0 };
|
||||||
|
static _Atomic int32_t nameLen = -1;
|
||||||
static int32_t testsRun = 0;
|
static int32_t testsRun = 0;
|
||||||
static int32_t testsFailed = 0;
|
static int32_t testsFailed = 0;
|
||||||
|
|
||||||
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
||||||
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static void onError(uint64_t contextId, const char *message, void *userData);
|
static void onError(uint64_t contextId, const char *message, void *userData);
|
||||||
static void pumpUntilDone(void);
|
static void pumpUntilDone(void);
|
||||||
static void testConcurrentContexts(void);
|
static void testConcurrentContexts(void);
|
||||||
static void testCrossThreadCallback(void);
|
static void testCrossThreadCallback(void);
|
||||||
static void testHostAndInlineNatives(void);
|
static void testHostAndInlineNatives(void);
|
||||||
|
static void testMaterializedRecord(void);
|
||||||
static void testScriptError(void);
|
static void testScriptError(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -72,6 +77,31 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
CalogAggT *user;
|
||||||
|
CalogValueT key;
|
||||||
|
CalogValueT val;
|
||||||
|
int32_t status;
|
||||||
|
|
||||||
|
(void)args;
|
||||||
|
(void)argCount;
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
status = calogAggCreate(&user, calogMapE);
|
||||||
|
if (status != calogOkE) {
|
||||||
|
return calogFail(result, status, "makeUser could not allocate");
|
||||||
|
}
|
||||||
|
calogValueString(&key, "name", 4);
|
||||||
|
calogValueString(&val, "ada", 3);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueString(&key, "age", 3);
|
||||||
|
calogValueInt(&val, 36);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueAgg(result, user);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -94,6 +124,25 @@ static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
if (argCount != 1 || args[0].type != calogStringE) {
|
||||||
|
return calogFail(result, calogErrArgE, "reportName expects one string");
|
||||||
|
}
|
||||||
|
length = (size_t)args[0].as.s.length;
|
||||||
|
if (length >= sizeof(storedName)) {
|
||||||
|
length = sizeof(storedName) - 1;
|
||||||
|
}
|
||||||
|
memcpy(storedName, args[0].as.s.bytes, length);
|
||||||
|
storedName[length] = '\0';
|
||||||
|
atomic_store(&nameLen, (int32_t)length);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -149,6 +198,23 @@ static void testHostAndInlineNatives(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testMaterializedRecord(void) {
|
||||||
|
CalogContextT *ctx;
|
||||||
|
|
||||||
|
ctx = calogContextOpen(calog, &calogBerryEngine);
|
||||||
|
atomic_store(&scriptDone, false);
|
||||||
|
atomic_store(&nameLen, -1);
|
||||||
|
atomic_store(&reportedValue, 0);
|
||||||
|
calogContextEval(ctx, "u = makeUser()\nreport(u['age'])\nreportName(u['name'])\ndone()");
|
||||||
|
pumpUntilDone();
|
||||||
|
|
||||||
|
CHECK(atomic_load(&reportedValue) == 36, "read an int field from a materialized record map");
|
||||||
|
CHECK(atomic_load(&nameLen) == 3 && memcmp(storedName, "ada", 3) == 0, "read a string field from a materialized record map");
|
||||||
|
|
||||||
|
calogContextClose(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void testCrossThreadCallback(void) {
|
static void testCrossThreadCallback(void) {
|
||||||
CalogContextT *ctx;
|
CalogContextT *ctx;
|
||||||
CalogFnT *callback;
|
CalogFnT *callback;
|
||||||
|
|
@ -225,9 +291,12 @@ int main(void) {
|
||||||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||||
calogRegister(calog, "done", nativeDone, NULL);
|
calogRegister(calog, "done", nativeDone, NULL);
|
||||||
calogRegister(calog, "bump", nativeBump, NULL);
|
calogRegister(calog, "bump", nativeBump, NULL);
|
||||||
|
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||||
|
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||||
|
|
||||||
testHostAndInlineNatives();
|
testHostAndInlineNatives();
|
||||||
|
testMaterializedRecord();
|
||||||
testCrossThreadCallback();
|
testCrossThreadCallback();
|
||||||
testScriptError();
|
testScriptError();
|
||||||
testConcurrentContexts();
|
testConcurrentContexts();
|
||||||
|
|
|
||||||
|
|
@ -35,20 +35,25 @@ static _Atomic bool scriptDone = false;
|
||||||
static _Atomic int32_t errorCount = 0;
|
static _Atomic int32_t errorCount = 0;
|
||||||
static _Atomic uint64_t errorCtxId = 0;
|
static _Atomic uint64_t errorCtxId = 0;
|
||||||
static CalogFnT *_Atomic storedCb = NULL;
|
static CalogFnT *_Atomic storedCb = NULL;
|
||||||
|
static char storedName[32] = { 0 };
|
||||||
|
static _Atomic int32_t nameLen = -1;
|
||||||
static int32_t testsRun = 0;
|
static int32_t testsRun = 0;
|
||||||
static int32_t testsFailed = 0;
|
static int32_t testsFailed = 0;
|
||||||
|
|
||||||
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
||||||
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static void onError(uint64_t contextId, const char *message, void *userData);
|
static void onError(uint64_t contextId, const char *message, void *userData);
|
||||||
static void pumpUntilDone(void);
|
static void pumpUntilDone(void);
|
||||||
static void testConcurrentContexts(void);
|
static void testConcurrentContexts(void);
|
||||||
static void testCrossThreadCallback(void);
|
static void testCrossThreadCallback(void);
|
||||||
static void testHostAndInlineNatives(void);
|
static void testHostAndInlineNatives(void);
|
||||||
|
static void testMaterializedRecord(void);
|
||||||
static void testScriptError(void);
|
static void testScriptError(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -81,6 +86,31 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
CalogAggT *user;
|
||||||
|
CalogValueT key;
|
||||||
|
CalogValueT val;
|
||||||
|
int32_t status;
|
||||||
|
|
||||||
|
(void)args;
|
||||||
|
(void)argCount;
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
status = calogAggCreate(&user, calogMapE);
|
||||||
|
if (status != calogOkE) {
|
||||||
|
return calogFail(result, status, "makeUser could not allocate");
|
||||||
|
}
|
||||||
|
calogValueString(&key, "name", 4);
|
||||||
|
calogValueString(&val, "ada", 3);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueString(&key, "age", 3);
|
||||||
|
calogValueInt(&val, 36);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueAgg(result, user);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -103,6 +133,25 @@ static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
if (argCount != 1 || args[0].type != calogStringE) {
|
||||||
|
return calogFail(result, calogErrArgE, "reportName expects one string");
|
||||||
|
}
|
||||||
|
length = (size_t)args[0].as.s.length;
|
||||||
|
if (length >= sizeof(storedName)) {
|
||||||
|
length = sizeof(storedName) - 1;
|
||||||
|
}
|
||||||
|
memcpy(storedName, args[0].as.s.bytes, length);
|
||||||
|
storedName[length] = '\0';
|
||||||
|
atomic_store(&nameLen, (int32_t)length);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -158,6 +207,23 @@ static void testHostAndInlineNatives(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testMaterializedRecord(void) {
|
||||||
|
CalogContextT *ctx;
|
||||||
|
|
||||||
|
ctx = calogContextOpen(calog, &calogS7Engine);
|
||||||
|
atomic_store(&scriptDone, false);
|
||||||
|
atomic_store(&nameLen, -1);
|
||||||
|
atomic_store(&reportedValue, 0);
|
||||||
|
calogContextEval(ctx, "(begin (define u (makeUser)) (report (u \"age\")) (reportName (u \"name\")) (done))");
|
||||||
|
pumpUntilDone();
|
||||||
|
|
||||||
|
CHECK(atomic_load(&reportedValue) == 36, "read an int field from a materialized record map");
|
||||||
|
CHECK(atomic_load(&nameLen) == 3 && memcmp(storedName, "ada", 3) == 0, "read a string field from a materialized record map");
|
||||||
|
|
||||||
|
calogContextClose(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void testCrossThreadCallback(void) {
|
static void testCrossThreadCallback(void) {
|
||||||
CalogContextT *ctx;
|
CalogContextT *ctx;
|
||||||
CalogFnT *callback;
|
CalogFnT *callback;
|
||||||
|
|
@ -234,9 +300,12 @@ int main(void) {
|
||||||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||||
calogRegister(calog, "done", nativeDone, NULL);
|
calogRegister(calog, "done", nativeDone, NULL);
|
||||||
calogRegister(calog, "bump", nativeBump, NULL);
|
calogRegister(calog, "bump", nativeBump, NULL);
|
||||||
|
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||||
|
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||||
|
|
||||||
testHostAndInlineNatives();
|
testHostAndInlineNatives();
|
||||||
|
testMaterializedRecord();
|
||||||
testCrossThreadCallback();
|
testCrossThreadCallback();
|
||||||
testScriptError();
|
testScriptError();
|
||||||
testConcurrentContexts();
|
testConcurrentContexts();
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,25 @@ static _Atomic bool scriptDone = false;
|
||||||
static _Atomic int32_t errorCount = 0;
|
static _Atomic int32_t errorCount = 0;
|
||||||
static _Atomic uint64_t errorCtxId = 0;
|
static _Atomic uint64_t errorCtxId = 0;
|
||||||
static CalogFnT *_Atomic storedCb = NULL;
|
static CalogFnT *_Atomic storedCb = NULL;
|
||||||
|
static char storedName[32] = { 0 };
|
||||||
|
static _Atomic int32_t nameLen = -1;
|
||||||
static int32_t testsRun = 0;
|
static int32_t testsRun = 0;
|
||||||
static int32_t testsFailed = 0;
|
static int32_t testsFailed = 0;
|
||||||
|
|
||||||
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
||||||
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
||||||
static void onError(uint64_t contextId, const char *message, void *userData);
|
static void onError(uint64_t contextId, const char *message, void *userData);
|
||||||
static void pumpUntilDone(void);
|
static void pumpUntilDone(void);
|
||||||
static void testConcurrentContexts(void);
|
static void testConcurrentContexts(void);
|
||||||
static void testCrossThreadCallback(void);
|
static void testCrossThreadCallback(void);
|
||||||
static void testHostAndInlineNatives(void);
|
static void testHostAndInlineNatives(void);
|
||||||
|
static void testMaterializedRecord(void);
|
||||||
static void testScriptError(void);
|
static void testScriptError(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -72,6 +77,31 @@ static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *resu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeMakeUser(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
CalogAggT *user;
|
||||||
|
CalogValueT key;
|
||||||
|
CalogValueT val;
|
||||||
|
int32_t status;
|
||||||
|
|
||||||
|
(void)args;
|
||||||
|
(void)argCount;
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
status = calogAggCreate(&user, calogMapE);
|
||||||
|
if (status != calogOkE) {
|
||||||
|
return calogFail(result, status, "makeUser could not allocate");
|
||||||
|
}
|
||||||
|
calogValueString(&key, "name", 4);
|
||||||
|
calogValueString(&val, "ada", 3);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueString(&key, "age", 3);
|
||||||
|
calogValueInt(&val, 36);
|
||||||
|
calogAggSet(user, &key, &val);
|
||||||
|
calogValueAgg(result, user);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -94,6 +124,25 @@ static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t nativeReportName(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
(void)userData;
|
||||||
|
calogValueNil(result);
|
||||||
|
if (argCount != 1 || args[0].type != calogStringE) {
|
||||||
|
return calogFail(result, calogErrArgE, "reportName expects one string");
|
||||||
|
}
|
||||||
|
length = (size_t)args[0].as.s.length;
|
||||||
|
if (length >= sizeof(storedName)) {
|
||||||
|
length = sizeof(storedName) - 1;
|
||||||
|
}
|
||||||
|
memcpy(storedName, args[0].as.s.bytes, length);
|
||||||
|
storedName[length] = '\0';
|
||||||
|
atomic_store(&nameLen, (int32_t)length);
|
||||||
|
return calogOkE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
||||||
(void)userData;
|
(void)userData;
|
||||||
calogValueNil(result);
|
calogValueNil(result);
|
||||||
|
|
@ -138,7 +187,7 @@ static void testHostAndInlineNatives(void) {
|
||||||
CHECK(ctx != NULL, "opened a Wren context");
|
CHECK(ctx != NULL, "opened a Wren context");
|
||||||
|
|
||||||
atomic_store(&scriptDone, false);
|
atomic_store(&scriptDone, false);
|
||||||
calogContextEval(ctx, "Calog.call_(\"report\", [42])\nCalog.call_(\"reportInline\", [1])\nCalog.call_(\"done\", [])");
|
calogContextEval(ctx, "Calog.call(\"report\", [42])\nCalog.call(\"reportInline\", [1])\nCalog.call(\"done\", [])");
|
||||||
pumpUntilDone();
|
pumpUntilDone();
|
||||||
|
|
||||||
CHECK(atomic_load(&reportedValue) == 42, "host native received the argument");
|
CHECK(atomic_load(&reportedValue) == 42, "host native received the argument");
|
||||||
|
|
@ -149,6 +198,23 @@ static void testHostAndInlineNatives(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void testMaterializedRecord(void) {
|
||||||
|
CalogContextT *ctx;
|
||||||
|
|
||||||
|
ctx = calogContextOpen(calog, &calogWrenEngine);
|
||||||
|
atomic_store(&scriptDone, false);
|
||||||
|
atomic_store(&nameLen, -1);
|
||||||
|
atomic_store(&reportedValue, 0);
|
||||||
|
calogContextEval(ctx, "var u = Calog.call(\"makeUser\", [])\nCalog.call(\"report\", [u[\"age\"]])\nCalog.call(\"reportName\", [u[\"name\"]])\nCalog.call(\"done\", [])");
|
||||||
|
pumpUntilDone();
|
||||||
|
|
||||||
|
CHECK(atomic_load(&reportedValue) == 36, "read an int field from a materialized record map");
|
||||||
|
CHECK(atomic_load(&nameLen) == 3 && memcmp(storedName, "ada", 3) == 0, "read a string field from a materialized record map");
|
||||||
|
|
||||||
|
calogContextClose(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void testCrossThreadCallback(void) {
|
static void testCrossThreadCallback(void) {
|
||||||
CalogContextT *ctx;
|
CalogContextT *ctx;
|
||||||
CalogFnT *callback;
|
CalogFnT *callback;
|
||||||
|
|
@ -159,7 +225,7 @@ static void testCrossThreadCallback(void) {
|
||||||
ctx = calogContextOpen(calog, &calogWrenEngine);
|
ctx = calogContextOpen(calog, &calogWrenEngine);
|
||||||
atomic_store(&scriptDone, false);
|
atomic_store(&scriptDone, false);
|
||||||
atomic_store(&storedCb, NULL);
|
atomic_store(&storedCb, NULL);
|
||||||
calogContextEval(ctx, "Calog.call_(\"setCb\", [Fn.new {|x| x + 100 }])\nCalog.call_(\"done\", [])");
|
calogContextEval(ctx, "Calog.call(\"setCb\", [Fn.new {|x| x + 100 }])\nCalog.call(\"done\", [])");
|
||||||
pumpUntilDone();
|
pumpUntilDone();
|
||||||
|
|
||||||
callback = atomic_load(&storedCb);
|
callback = atomic_load(&storedCb);
|
||||||
|
|
@ -201,7 +267,7 @@ static void testConcurrentContexts(void) {
|
||||||
atomic_store(&bumpCount, 0);
|
atomic_store(&bumpCount, 0);
|
||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
ctxs[i] = calogContextOpen(calog, &calogWrenEngine);
|
ctxs[i] = calogContextOpen(calog, &calogWrenEngine);
|
||||||
calogContextEval(ctxs[i], "Calog.call_(\"bump\", [])\nCalog.call_(\"bump\", [])\nCalog.call_(\"bump\", [])");
|
calogContextEval(ctxs[i], "Calog.call(\"bump\", [])\nCalog.call(\"bump\", [])\nCalog.call(\"bump\", [])");
|
||||||
}
|
}
|
||||||
for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 9; i++) {
|
for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 9; i++) {
|
||||||
calogPump(calog);
|
calogPump(calog);
|
||||||
|
|
@ -225,9 +291,12 @@ int main(void) {
|
||||||
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
calogRegister(calog, "setCb", nativeSetCb, NULL);
|
||||||
calogRegister(calog, "done", nativeDone, NULL);
|
calogRegister(calog, "done", nativeDone, NULL);
|
||||||
calogRegister(calog, "bump", nativeBump, NULL);
|
calogRegister(calog, "bump", nativeBump, NULL);
|
||||||
|
calogRegister(calog, "makeUser", nativeMakeUser, NULL);
|
||||||
|
calogRegister(calog, "reportName", nativeReportName, NULL);
|
||||||
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
calogRegisterInline(calog, "reportInline", nativeReportInline, NULL);
|
||||||
|
|
||||||
testHostAndInlineNatives();
|
testHostAndInlineNatives();
|
||||||
|
testMaterializedRecord();
|
||||||
testCrossThreadCallback();
|
testCrossThreadCallback();
|
||||||
testScriptError();
|
testScriptError();
|
||||||
testConcurrentContexts();
|
testConcurrentContexts();
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ int main(void) {
|
||||||
CHECK(loadAndRun("clTest") == 15, "loaded and ran a .scm Scheme script by base name");
|
CHECK(loadAndRun("clTest") == 15, "loaded and ran a .scm Scheme script by base name");
|
||||||
remove("clTest.scm");
|
remove("clTest.scm");
|
||||||
|
|
||||||
writeScript("clTest.wren", "Calog.call_(\"hit\", [16])");
|
writeScript("clTest.wren", "Calog.call(\"hit\", [16])");
|
||||||
CHECK(loadAndRun("clTest") == 16, "loaded and ran a .wren script by base name");
|
CHECK(loadAndRun("clTest") == 16, "loaded and ran a .wren script by base name");
|
||||||
remove("clTest.wren");
|
remove("clTest.wren");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue