calog/src/mruby/mrubyAdapter.c
2026-07-04 20:25:00 -05:00

714 lines
26 KiB
C

// mrubyAdapter.c -- mruby (Ruby) engine adapter for the broker.
//
// Each exposed native is a Kernel method bound to one shared trampoline. mrb_func_t carries no
// userData, so the trampoline recovers the native's name from the current call's method id
// (mrb_get_mid) and the owning context from mrb->ud, then marshals the Ruby arguments to
// CalogValueT and dispatches through calogCall (honoring the actor route hook). Marshalling covers
// scalars and binary-safe strings; aggregates cross both ways (Array<->list, Hash<->map). Functions
// cross both ways too: a Ruby proc out becomes a refcounted CalogFnT pinned as a GC root
// (mrb_gc_register) and dropped on release; a foreign CalogFnT in becomes a real Ruby Proc
// (mrb_proc_new_cfunc_with_env) carrying the CalogFnT in its env -- the script calls it as f.call(x).
// Foreign functions are tracked on the context and released at destroy (a cfunc proc's env has no
// per-value finalizer). puts/print/p are provided (routed to stdout); there is no file/socket IO.
//
// Error model: on failure the trampoline raises an mruby exception (mrb_raise, which longjmps out
// of the VM) after freeing any CalogValueT it owns. GC-arena save/restore brackets marshalling so
// transient objects created here do not overflow the arena.
#define _POSIX_C_SOURCE 200809L
#include "mrubyAdapter.h"
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/compile.h>
#include <mruby/error.h>
#include <mruby/hash.h>
#include <mruby/proc.h>
#include <mruby/string.h>
#include <mruby/variable.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MRUBY_FOREIGN_INITIAL 8
struct CalogMrubyT {
mrb_state *mrb;
CalogT *broker;
uint64_t ctxId;
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 pinned Ruby callable
// (a Proc/Method), kept GC-reachable by mrb_gc_register until the CalogFnT is released.
typedef struct MrubyExportT {
CalogMrubyT *context;
mrb_value callable;
} MrubyExportT;
static int32_t mrubyCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void mrubyCallableRelease(CalogFnT *callable);
static int32_t mrubyExportValue(CalogMrubyT *context, mrb_value callable, CalogFnT **out);
static mrb_value mrubyForeignCall(mrb_state *mrb, mrb_value self);
static int32_t mrubyFromValue(CalogMrubyT *context, const CalogValueT *value, mrb_value *out, int32_t depth);
static int32_t mrubyInvokeArgs(CalogMrubyT *context, CalogFnT *callable, mrb_value *argv, mrb_int argc, mrb_value *out, char *errbuf, size_t errcap);
static mrb_value mrubyMethodMissing(mrb_state *mrb, mrb_value self);
static mrb_value mrubyP(mrb_state *mrb, mrb_value self);
static mrb_value mrubyPrint(mrb_state *mrb, mrb_value self);
static mrb_value mrubyPuts(mrb_state *mrb, mrb_value self);
static int32_t mrubyToValue(CalogMrubyT *context, mrb_value value, CalogValueT *out, int32_t depth);
static int32_t mrubyTrackForeign(CalogMrubyT *context, CalogFnT *callable);
static mrb_value mrubyTrampoline(mrb_state *mrb, mrb_value self);
int32_t calogMrubyCreate(CalogMrubyT **out, CalogT *broker, uint64_t ctxId) {
CalogMrubyT *context;
mrb_state *mrb;
*out = NULL;
context = (CalogMrubyT *)calloc(1, sizeof(*context));
if (context == NULL) {
return calogErrOomE;
}
mrb = mrb_open();
if (mrb == NULL) {
free(context);
return calogErrOomE;
}
context->mrb = mrb;
context->broker = broker;
context->ctxId = ctxId;
mrb->ud = context; // the trampoline recovers the context from here
// Ruby ergonomics without pulling mruby-io: puts/print/p route to stdout. Defined on Kernel
// so a bare `puts "x"` works at top level (calog scripts still use fs*/net* for real IO).
mrb_define_method(mrb, mrb->kernel_module, "puts", mrubyPuts, MRB_ARGS_ANY());
mrb_define_method(mrb, mrb->kernel_module, "print", mrubyPrint, MRB_ARGS_ANY());
mrb_define_method(mrb, mrb->kernel_module, "p", mrubyP, MRB_ARGS_ANY());
// Bare-name export resolution: a top-level `exportedFn(args)` that is not a defined method
// resolves to a broker export (see mrubyMethodMissing), like the hook engines.
mrb_define_method(mrb, mrb->kernel_module, "method_missing", mrubyMethodMissing, MRB_ARGS_ANY());
*out = context;
return calogOkE;
}
static int32_t mrubyCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
MrubyExportT *export;
CalogMrubyT *context;
mrb_state *mrb;
mrb_value *argv;
mrb_value ret;
int arena;
int32_t index;
int32_t status;
export = (MrubyExportT *)userData;
context = export->context;
mrb = context->mrb;
calogValueNil(result);
argv = NULL;
if (argCount > 0) {
argv = (mrb_value *)calloc((size_t)argCount, sizeof(mrb_value));
if (argv == NULL) {
return calogFail(result, calogErrOomE, "out of memory calling mruby callable");
}
}
arena = mrb_gc_arena_save(mrb);
for (index = 0; index < argCount; index++) {
status = mrubyFromValue(context, &args[index], &argv[index], 0);
if (status != calogOkE) {
mrb_gc_arena_restore(mrb, arena);
free(argv);
return calogFail(result, status, "failed to marshal argument into mruby");
}
}
ret = mrb_funcall_argv(mrb, export->callable, mrb_intern_lit(mrb, "call"), (mrb_int)argCount, argv);
free(argv);
if (mrb->exc != NULL) {
mrb_value exc;
mrb_value message;
const char *text;
exc = mrb_obj_value(mrb->exc);
mrb->exc = NULL;
message = mrb_funcall_argv(mrb, exc, mrb_intern_lit(mrb, "message"), 0, NULL);
text = mrb_string_p(message) ? RSTRING_PTR(message) : "mruby callable raised";
status = calogFail(result, calogErrArgE, text);
mrb_gc_arena_restore(mrb, arena);
return status;
}
status = mrubyToValue(context, ret, result, 0);
mrb_gc_arena_restore(mrb, arena);
return status;
}
static void mrubyCallableRelease(CalogFnT *callable) {
MrubyExportT *export;
export = (MrubyExportT *)calogFnUserData(callable);
mrb_gc_unregister(export->context->mrb, export->callable);
free(export);
}
void calogMrubyDestroy(CalogMrubyT *context) {
int32_t index;
if (context == NULL) {
return;
}
// Release the foreign function values pushed into this VM (see mrubyTrackForeign): a cfunc
// proc's env has no per-value finalizer, so they are held until the context is destroyed.
for (index = 0; index < context->foreignCount; index++) {
calogFnRelease(context->foreignFns[index]);
}
free(context->foreignFns);
if (context->mrb != NULL) {
mrb_close(context->mrb);
}
free(context);
}
int32_t calogMrubyExport(CalogMrubyT *context, const char *name, CalogFnT **out) {
mrb_state *mrb;
mrb_value self;
mrb_value method;
mrb_value sym;
int arena;
int32_t status;
mrb = context->mrb;
*out = NULL;
// Object#method returns a Method for a top-level `def name` (public or private). A Proc/lambda
// held in a global ($name) also resolves through method_missing/global lookup below.
self = mrb_top_self(mrb);
sym = mrb_symbol_value(mrb_intern_cstr(mrb, name));
arena = mrb_gc_arena_save(mrb);
method = mrb_funcall_argv(mrb, self, mrb_intern_lit(mrb, "method"), 1, &sym);
if (mrb->exc != NULL) {
mrb->exc = NULL;
mrb_gc_arena_restore(mrb, arena);
return calogErrNotFoundE;
}
status = mrubyExportValue(context, method, out);
mrb_gc_arena_restore(mrb, arena);
return status;
}
static int32_t mrubyExportValue(CalogMrubyT *context, mrb_value callable, CalogFnT **out) {
mrb_state *mrb;
MrubyExportT *export;
int32_t status;
mrb = context->mrb;
*out = NULL;
export = (MrubyExportT *)malloc(sizeof(*export));
if (export == NULL) {
return calogErrOomE;
}
export->context = context;
export->callable = callable;
// Pin the callable as a GC root so it survives until the CalogFnT is released.
mrb_gc_register(mrb, callable);
status = calogFnCreate(out, context->broker, mrubyCallableInvoke, export, mrubyCallableRelease, context->ctxId);
if (status != calogOkE) {
mrb_gc_unregister(mrb, callable);
free(export);
return status;
}
return calogOkE;
}
int32_t calogMrubyExpose(CalogMrubyT *context, const char *name) {
mrb_state *mrb;
mrb = context->mrb;
if (calogLookup(context->broker, name) == NULL) {
return calogErrNotFoundE;
}
// A public Kernel method routed to the shared trampoline, so `name(args)` works at top level
// in any context. The trampoline recovers `name` from the call's method id.
mrb_define_method(mrb, mrb->kernel_module, name, mrubyTrampoline, MRB_ARGS_ANY());
return calogOkE;
}
// Call target for a foreign CalogFnT pushed into mruby as a Proc. The CalogFnT travels in the
// proc's cfunc env (slot 0, a cptr); the context comes from mrb->ud.
static mrb_value mrubyForeignCall(mrb_state *mrb, mrb_value self) {
CalogMrubyT *context;
CalogFnT *callable;
mrb_value *argv;
mrb_int argc;
mrb_value out;
int arena;
int32_t status;
char message[CALOG_ERR_MSG_CAP];
(void)self;
context = (CalogMrubyT *)mrb->ud;
callable = (CalogFnT *)mrb_cptr(mrb_proc_cfunc_env_get(mrb, 0));
mrb_get_args(mrb, "*", &argv, &argc);
arena = mrb_gc_arena_save(mrb);
status = mrubyInvokeArgs(context, callable, argv, argc, &out, message, sizeof(message));
mrb_gc_arena_restore(mrb, arena);
if (status != calogOkE) {
mrb_raise(mrb, E_RUNTIME_ERROR, message);
}
return out;
}
static int32_t mrubyFromValue(CalogMrubyT *context, const CalogValueT *value, mrb_value *out, int32_t depth) {
mrb_state *mrb;
mrb = context->mrb;
*out = mrb_nil_value();
if (depth >= CALOG_MAX_DEPTH) {
return calogErrDepthE;
}
switch (value->type) {
case calogNilE:
return calogOkE;
case calogBoolE:
*out = mrb_bool_value(value->as.b);
return calogOkE;
case calogIntE:
*out = mrb_int_value(mrb, (mrb_int)value->as.i);
return calogOkE;
case calogRealE:
*out = mrb_float_value(mrb, (mrb_float)value->as.r);
return calogOkE;
case calogStringE:
*out = mrb_str_new(mrb, value->as.s.bytes, (mrb_int)value->as.s.length);
return calogOkE;
case calogAggE: {
CalogAggT *aggregate;
int64_t index;
int32_t status;
mrb_value child;
aggregate = value->as.agg;
if (calogAggIsKeyed(aggregate)) {
mrb_value map;
map = mrb_hash_new(mrb);
for (index = 0; index < aggregate->arrayCount; index++) {
status = mrubyFromValue(context, &aggregate->array[index], &child, depth + 1);
if (status != calogOkE) {
return status;
}
mrb_hash_set(mrb, map, mrb_int_value(mrb, (mrb_int)index), child);
}
for (index = 0; index < aggregate->pairCount; index++) {
mrb_value key;
status = mrubyFromValue(context, &aggregate->pairs[index].key, &key, depth + 1);
if (status != calogOkE) {
return status;
}
status = mrubyFromValue(context, &aggregate->pairs[index].value, &child, depth + 1);
if (status != calogOkE) {
return status;
}
mrb_hash_set(mrb, map, key, child);
}
*out = map;
return calogOkE;
}
*out = mrb_ary_new_capa(mrb, (mrb_int)aggregate->arrayCount);
for (index = 0; index < aggregate->arrayCount; index++) {
status = mrubyFromValue(context, &aggregate->array[index], &child, depth + 1);
if (status != calogOkE) {
return status;
}
mrb_ary_push(mrb, *out, child);
}
return calogOkE;
}
case calogFnE: {
// A foreign callable becomes a real Ruby Proc carrying the CalogFnT in its env;
// the script calls it as f.call(x) / f.(x). Tracked on the context and released at
// destroy (a cfunc proc's env has no per-value finalizer).
mrb_value env[1];
if (mrubyTrackForeign(context, value->as.fn) != calogOkE) {
return calogErrOomE;
}
env[0] = mrb_cptr_value(mrb, value->as.fn);
*out = mrb_obj_value(mrb_proc_new_cfunc_with_env(mrb, mrubyForeignCall, 1, env));
calogFnRetain(value->as.fn);
return calogOkE;
}
}
return calogErrTypeE;
}
// Marshal argv[0..argc) into CalogValueT, invoke `callable`, and marshal the result into *out.
// Non-raising: returns calogOkE (out set) or an error, copying a message into errbuf so the caller
// can release its own references before raising (the mruby raise longjmps out).
static int32_t mrubyInvokeArgs(CalogMrubyT *context, CalogFnT *callable, mrb_value *argv, mrb_int argc, mrb_value *out, char *errbuf, size_t errcap) {
CalogValueT *args;
CalogValueT result;
mrb_int index;
int32_t status;
*out = mrb_nil_value();
args = NULL;
if (argc > 0) {
args = (CalogValueT *)calloc((size_t)argc, sizeof(CalogValueT));
if (args == NULL) {
snprintf(errbuf, errcap, "out of memory marshalling arguments");
return calogErrOomE;
}
}
for (index = 0; index < argc; index++) {
status = mrubyToValue(context, argv[index], &args[index], 0);
if (status != calogOkE) {
mrb_int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&args[cleanup]);
}
free(args);
snprintf(errbuf, errcap, "failed to marshal an argument");
return status;
}
}
status = calogFnInvoke(callable, args, (int32_t)argc, &result);
for (index = 0; index < argc; index++) {
calogValueFree(&args[index]);
}
free(args);
if (status != calogOkE) {
snprintf(errbuf, errcap, "%s", (result.type == calogStringE) ? result.as.s.bytes : "call failed");
calogValueFree(&result);
return status;
}
status = mrubyFromValue(context, &result, out, 0);
calogValueFree(&result);
if (status != calogOkE) {
snprintf(errbuf, errcap, "failed to marshal the result");
}
return status;
}
// Kernel#method_missing: a bare name called at TOP LEVEL that is not a defined method may be a
// broker export. Resolve and invoke it -- but only when self is the top-level main object, so a
// missing method on any other receiver (obj.foo) stays a NoMethodError and is never hijacked.
// This gives Ruby the bare-name export resolution the hook engines have (no VM change needed).
static mrb_value mrubyMethodMissing(mrb_state *mrb, mrb_value self) {
CalogMrubyT *context;
mrb_value *argv;
mrb_int argc;
mrb_sym missing;
const char *name;
CalogValueT nameArg;
CalogValueT resolved;
mrb_value out;
int arena;
int32_t status;
char message[CALOG_ERR_MSG_CAP];
context = (CalogMrubyT *)mrb->ud;
mrb_get_args(mrb, "*", &argv, &argc); // argv[0] is the method name (a Symbol), rest are args
if (argc < 1 || !mrb_symbol_p(argv[0])) {
mrb_raise(mrb, E_NOMETHOD_ERROR, "method_missing without a method name");
}
missing = mrb_symbol(argv[0]);
if (!mrb_obj_equal(mrb, self, mrb_top_self(mrb))) {
mrb_raisef(mrb, E_NOMETHOD_ERROR, "undefined method '%n'", missing);
}
name = mrb_sym_name(mrb, missing);
if (calogValueString(&nameArg, name, (int64_t)strlen(name)) != calogOkE) {
mrb_raisef(mrb, E_NOMETHOD_ERROR, "undefined method '%n'", missing);
}
calogValueNil(&resolved);
calogCall(context->broker, "__calogExportResolve", &nameArg, 1, &resolved);
calogValueFree(&nameArg);
if (resolved.type != calogFnE) {
calogValueFree(&resolved);
mrb_raisef(mrb, E_NOMETHOD_ERROR, "undefined method '%n'", missing);
}
// It is an export: invoke with the remaining args. Keep our resolved reference across the
// invoke (guards against a concurrent unexport), release it, THEN raise on failure -- so the
// reference never leaks on the raise path.
arena = mrb_gc_arena_save(mrb);
status = mrubyInvokeArgs(context, resolved.as.fn, argv + 1, argc - 1, &out, message, sizeof(message));
calogValueFree(&resolved);
mrb_gc_arena_restore(mrb, arena);
if (status != calogOkE) {
mrb_raise(mrb, E_RUNTIME_ERROR, message);
}
return out;
}
static mrb_value mrubyP(mrb_state *mrb, mrb_value self) {
mrb_value *argv;
mrb_int argc;
mrb_int index;
(void)self;
mrb_get_args(mrb, "*", &argv, &argc);
for (index = 0; index < argc; index++) {
mrb_value text;
text = mrb_inspect(mrb, argv[index]);
fwrite(RSTRING_PTR(text), 1, (size_t)RSTRING_LEN(text), stdout);
fputc('\n', stdout);
}
if (argc == 1) {
return argv[0];
}
return mrb_nil_value();
}
static mrb_value mrubyPrint(mrb_state *mrb, mrb_value self) {
mrb_value *argv;
mrb_int argc;
mrb_int index;
(void)self;
mrb_get_args(mrb, "*", &argv, &argc);
for (index = 0; index < argc; index++) {
mrb_value text;
text = mrb_obj_as_string(mrb, argv[index]);
fwrite(RSTRING_PTR(text), 1, (size_t)RSTRING_LEN(text), stdout);
}
return mrb_nil_value();
}
static mrb_value mrubyPuts(mrb_state *mrb, mrb_value self) {
mrb_value *argv;
mrb_int argc;
mrb_int index;
(void)self;
mrb_get_args(mrb, "*", &argv, &argc);
if (argc == 0) {
fputc('\n', stdout);
return mrb_nil_value();
}
for (index = 0; index < argc; index++) {
mrb_value text;
text = mrb_obj_as_string(mrb, argv[index]);
fwrite(RSTRING_PTR(text), 1, (size_t)RSTRING_LEN(text), stdout);
fputc('\n', stdout);
}
return mrb_nil_value();
}
int32_t calogMrubyRun(CalogMrubyT *context, const char *source) {
mrb_state *mrb;
int arena;
mrb = context->mrb;
arena = mrb_gc_arena_save(mrb);
mrb_load_string(mrb, source);
if (mrb->exc != NULL) {
mrb_value exc;
mrb_value text;
exc = mrb_obj_value(mrb->exc);
mrb->exc = NULL;
text = mrb_funcall_argv(mrb, exc, mrb_intern_lit(mrb, "inspect"), 0, NULL);
fprintf(stderr, "mruby error: %s\n", mrb_string_p(text) ? RSTRING_PTR(text) : "(unknown)");
mrb_gc_arena_restore(mrb, arena);
return calogErrArgE;
}
mrb_gc_arena_restore(mrb, arena);
return calogOkE;
}
static int32_t mrubyToValue(CalogMrubyT *context, mrb_value value, CalogValueT *out, int32_t depth) {
mrb_state *mrb;
mrb = context->mrb;
calogValueNil(out);
if (depth >= CALOG_MAX_DEPTH) {
return calogErrDepthE;
}
if (mrb_nil_p(value)) {
return calogOkE;
}
switch (mrb_type(value)) {
case MRB_TT_TRUE:
calogValueBool(out, true);
return calogOkE;
case MRB_TT_FALSE:
calogValueBool(out, false);
return calogOkE;
case MRB_TT_INTEGER:
calogValueInt(out, (int64_t)mrb_integer(value));
return calogOkE;
case MRB_TT_FLOAT:
calogValueReal(out, (double)mrb_float(value));
return calogOkE;
case MRB_TT_STRING:
return calogValueString(out, RSTRING_PTR(value), (int64_t)RSTRING_LEN(value));
case MRB_TT_ARRAY: {
CalogAggT *aggregate;
int32_t status;
mrb_int index;
mrb_int count;
status = calogAggCreate(&aggregate, calogListE);
if (status != calogOkE) {
return status;
}
count = RARRAY_LEN(value);
for (index = 0; index < count; index++) {
CalogValueT element;
status = mrubyToValue(context, mrb_ary_ref(mrb, value, index), &element, depth + 1);
if (status != calogOkE) {
calogAggFree(aggregate);
return status;
}
status = calogAggPush(aggregate, &element);
if (status != calogOkE) {
calogValueFree(&element);
calogAggFree(aggregate);
return status;
}
}
calogValueAgg(out, aggregate);
return calogOkE;
}
case MRB_TT_HASH: {
CalogAggT *aggregate;
mrb_value keys;
int32_t status;
mrb_int index;
mrb_int count;
status = calogAggCreate(&aggregate, calogMapE);
if (status != calogOkE) {
return status;
}
keys = mrb_hash_keys(mrb, value);
count = RARRAY_LEN(keys);
for (index = 0; index < count; index++) {
mrb_value rubyKey;
CalogValueT key;
CalogValueT element;
rubyKey = mrb_ary_ref(mrb, keys, index);
status = mrubyToValue(context, rubyKey, &key, depth + 1);
if (status != calogOkE) {
calogAggFree(aggregate);
return status;
}
status = mrubyToValue(context, mrb_hash_get(mrb, value, rubyKey), &element, depth + 1);
if (status != calogOkE) {
calogValueFree(&key);
calogAggFree(aggregate);
return status;
}
status = calogAggSet(aggregate, &key, &element);
if (status != calogOkE) {
calogValueFree(&key);
calogValueFree(&element);
calogAggFree(aggregate);
return status;
}
}
calogValueAgg(out, aggregate);
return calogOkE;
}
case MRB_TT_PROC: {
CalogFnT *callable;
int32_t status;
status = mrubyExportValue(context, value, &callable);
if (status != calogOkE) {
return status;
}
calogValueFn(out, callable);
return calogOkE;
}
default:
break;
}
// Other reference types (symbols, objects, ranges, ...) have no CalogValueT equivalent.
return calogErrUnsupportedE;
}
// Record a foreign function pushed into this VM so it is released at destroy (a cfunc proc's env
// has no per-value finalizer to release it when the proc is collected).
static int32_t mrubyTrackForeign(CalogMrubyT *context, CalogFnT *callable) {
if (context->foreignCount == context->foreignCap) {
int32_t newCap;
CalogFnT **resized;
newCap = (context->foreignCap == 0) ? MRUBY_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 mrb_value mrubyTrampoline(mrb_state *mrb, mrb_value self) {
CalogMrubyT *context;
const char *name;
mrb_value *argv;
mrb_int argc;
CalogValueT *args;
CalogValueT result;
mrb_value out;
int arena;
mrb_int index;
int32_t status;
(void)self;
context = (CalogMrubyT *)mrb->ud;
name = mrb_sym_name(mrb, mrb_get_mid(mrb)); // which exposed native was called
mrb_get_args(mrb, "*", &argv, &argc);
args = NULL;
if (argc > 0) {
args = (CalogValueT *)calloc((size_t)argc, sizeof(CalogValueT));
if (args == NULL) {
mrb_raise(mrb, E_RUNTIME_ERROR, "out of memory marshalling native arguments");
}
}
arena = mrb_gc_arena_save(mrb);
for (index = 0; index < argc; index++) {
status = mrubyToValue(context, argv[index], &args[index], 0);
if (status != calogOkE) {
mrb_int cleanup;
for (cleanup = 0; cleanup < index; cleanup++) {
calogValueFree(&args[cleanup]);
}
free(args);
mrb_raise(mrb, E_TYPE_ERROR, "failed to marshal a native argument");
}
}
status = calogCall(context->broker, name, args, (int32_t)argc, &result);
for (index = 0; index < argc; index++) {
calogValueFree(&args[index]);
}
free(args);
if (status != calogOkE) {
char message[CALOG_ERR_MSG_CAP];
snprintf(message, sizeof(message), "%s", (result.type == calogStringE) ? result.as.s.bytes : "native call failed");
calogValueFree(&result);
mrb_raise(mrb, E_RUNTIME_ERROR, message);
}
status = mrubyFromValue(context, &result, &out, 0);
calogValueFree(&result);
mrb_gc_arena_restore(mrb, arena);
if (status != calogOkE) {
mrb_raise(mrb, E_TYPE_ERROR, "failed to marshal the native result");
}
return out;
}