// mybasicAdapter.c -- my-basic engine adapter for the broker.
//
// A bank of slot-specific trampolines works around my-basic's native signature
// (no per-call userData; one interpreter userdata slot): each trampoline forwards
// to mbDispatch with its compile-time slot, which recovers the broker binding
// from the context's bank. The context pointer rides the interpreter userdata
// slot; the live argument cursor of the current native call is stashed in
// currentL so an exported BASIC routine can be evaluated through mb_eval_routine.

#include "mybasicAdapter.h"
#include "myBasic.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MB_BANK_SIZE     256
#define MB_INITIAL_ARGS  8
#define MB_NAME_MAX      128
#define MB_NATIVE_ERROR  SE_RN_FAILED_TO_OPERATE

typedef struct BindingT {
    const char *name;   // registry name; dispatched through calogCall so the actor
                        // layer marshals the native to the host thread
} BindingT;

typedef struct MyBasicRoutineT {
    CalogMyBasicT *context;
    mb_value_t       routine;
} MyBasicRoutineT;

struct CalogMyBasicT {
    struct mb_interpreter_t *bas;
    CalogT                 *broker;
    uint64_t                 ctxId;
    void                   **currentL;
    BindingT                 bank[MB_BANK_SIZE];
    int32_t                  bankCount;
};

static int32_t mbContextCount = 0;

static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const CalogAggT *aggregate, mb_value_t *out, int32_t depth);
static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void    mbCallableRelease(CalogFnT *callable);
static int32_t mbDictToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int     mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l);
static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogValueT *value, mb_value_t *out, int32_t depth);
static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth);
static int     mbPrinter(struct mb_interpreter_t *s, const char *format, ...);
static void    mbReleaseSetValue(CalogMyBasicT *context, mb_value_t value);
static void    mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value);
static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth);
static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFnT **out);

// Generated trampoline bank (see file header). Each forwards its compile-time
// slot to mbDispatch. This block is intentionally exempt from the alphabetical
// ordering of the hand-written functions below.
#define MB_TRAMPOLINE(slot) static int mbTramp##slot(struct mb_interpreter_t *s, void **l) { return mbDispatch(slot, s, l); }
MB_TRAMPOLINE(0)
MB_TRAMPOLINE(1)
MB_TRAMPOLINE(2)
MB_TRAMPOLINE(3)
MB_TRAMPOLINE(4)
MB_TRAMPOLINE(5)
MB_TRAMPOLINE(6)
MB_TRAMPOLINE(7)
MB_TRAMPOLINE(8)
MB_TRAMPOLINE(9)
MB_TRAMPOLINE(10)
MB_TRAMPOLINE(11)
MB_TRAMPOLINE(12)
MB_TRAMPOLINE(13)
MB_TRAMPOLINE(14)
MB_TRAMPOLINE(15)
MB_TRAMPOLINE(16)
MB_TRAMPOLINE(17)
MB_TRAMPOLINE(18)
MB_TRAMPOLINE(19)
MB_TRAMPOLINE(20)
MB_TRAMPOLINE(21)
MB_TRAMPOLINE(22)
MB_TRAMPOLINE(23)
MB_TRAMPOLINE(24)
MB_TRAMPOLINE(25)
MB_TRAMPOLINE(26)
MB_TRAMPOLINE(27)
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,
    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
};


static int32_t mbAggregateToCollDepth(CalogMyBasicT *context, void **l, const CalogAggT *aggregate, mb_value_t *out, int32_t depth) {
    mb_value_t coll;
    int32_t    status;
    int64_t    index;
    bool       asList;

    mb_make_nil(coll);
    if (depth > CALOG_MAX_DEPTH) {
        return calogErrDepthE;
    }
    asList = (aggregate->pairCount == 0 && aggregate->kind != calogMapE);
    coll.type = asList ? MB_DT_LIST : MB_DT_DICT;
    if (mb_init_coll(context->bas, l, &coll) != MB_FUNC_OK) {
        return calogErrOomE;
    }
    // mb_set_coll deep-copies a scalar or string key/value (so the copy we built
    // must be released afterwards) but stores a LIST/DICT by pointer WITHOUT
    // adding a reference (so that single reference now belongs to the parent and
    // must NOT be released here). mbReleaseSetValue encodes that distinction.
    for (index = 0; index < aggregate->arrayCount; index++) {
        mb_value_t element;
        mb_value_t key;
        mb_make_nil(element);
        status = mbFromValueDepth(context, l, &aggregate->array[index], &element, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, coll);
            return status;
        }
        mb_make_int(key, (int_t)index);
        mbRetainBeforeSet(context, l, element);
        mb_set_coll(context->bas, l, coll, key, element);
        mbReleaseSetValue(context, element);
    }
    for (index = 0; index < aggregate->pairCount; index++) {
        mb_value_t key;
        mb_value_t element;
        mb_make_nil(key);
        mb_make_nil(element);
        status = mbFromValueDepth(context, l, &aggregate->pairs[index].key, &key, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, coll);
            return status;
        }
        status = mbFromValueDepth(context, l, &aggregate->pairs[index].value, &element, depth + 1);
        if (status != calogOkE) {
            mb_dispose_value(context->bas, key);
            mb_dispose_value(context->bas, coll);
            return status;
        }
        mbRetainBeforeSet(context, l, element);
        mb_set_coll(context->bas, l, coll, key, element);
        mbReleaseSetValue(context, key);
        mbReleaseSetValue(context, element);
    }
    *out = coll;
    return calogOkE;
}


static int32_t mbCallableInvoke(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    MyBasicRoutineT *routine;
    CalogMyBasicT *context;
    mb_value_t      *mbArgs;
    mb_value_t       ret;
    int32_t          index;
    int32_t          status;
    int              code;

    routine = (MyBasicRoutineT *)userData;
    context = routine->context;
    calogValueNil(result);
    if (context->currentL == NULL) {
        return calogFail(result, calogErrUnsupportedE, "BASIC routine invoked outside a serving frame");
    }
    mbArgs = NULL;
    if (argCount > 0) {
        mbArgs = (mb_value_t *)calloc((size_t)argCount, sizeof(mb_value_t));
        if (mbArgs == NULL) {
            return calogFail(result, calogErrOomE, "out of memory marshalling routine args");
        }
    }
    for (index = 0; index < argCount; index++) {
        mb_make_nil(mbArgs[index]);
        status = mbFromValueDepth(context, context->currentL, &args[index], &mbArgs[index], 0);
        if (status != calogOkE) {
            int32_t built;
            for (built = 0; built < index; built++) {
                mb_dispose_value(context->bas, mbArgs[built]);
            }
            free(mbArgs);
            return calogFail(result, status, "failed to marshal routine argument");
        }
    }
    mb_make_nil(ret);
    code = mb_eval_routine(context->bas, context->currentL, routine->routine, mbArgs, (unsigned)argCount, &ret);
    if (code == MB_FUNC_OK) {
        // Marshal the return value BEFORE releasing the arguments: a routine may
        // return one of its (borrowed) arguments, so result must be deep-copied
        // out first or it would alias a buffer we are about to free.
        status = mbToValueDepth(context, context->currentL, &ret, result, 0);
    } else {
        status = calogFail(result, calogErrArgE, "BASIC routine evaluation failed");
    }
    // mb_eval_routine BORROWS its arguments (the routine's scope marks them
    // is_ref and never frees them), so every marshalled arg is ours to release.
    for (index = 0; index < argCount; index++) {
        mb_dispose_value(context->bas, mbArgs[index]);
    }
    free(mbArgs);
    return status;
}


static void mbCallableRelease(CalogFnT *callable) {
    MyBasicRoutineT *routine;

    routine = (MyBasicRoutineT *)calogFnUserData(callable);
    free(routine);
}


static int32_t mbDictToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth) {
    CalogAggT *aggregate;
    mb_value_t *keys;
    int32_t     status;
    int32_t     index;
    int         count;

    calogValueNil(out);
    count = 0;
    mb_count_coll(context->bas, l, coll, &count);
    status = calogAggCreate(&aggregate, calogMapE);
    if (status != calogOkE) {
        return status;
    }
    keys = NULL;
    if (count > 0) {
        keys = (mb_value_t *)calloc((size_t)count, sizeof(mb_value_t));
        if (keys == NULL) {
            calogAggFree(aggregate);
            return calogErrOomE;
        }
    }
    mb_keys_of_coll(context->bas, l, coll, keys, count);
    for (index = 0; index < count; index++) {
        mb_value_t mbValue;
        CalogValueT     keyValue;
        CalogValueT     valValue;
        mb_make_nil(mbValue);
        mb_get_coll(context->bas, l, coll, keys[index], &mbValue);
        status = mbToValueDepth(context, l, &keys[index], &keyValue, depth + 1);
        if (status != calogOkE) {
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
        status = mbToValueDepth(context, l, &mbValue, &valValue, depth + 1);
        if (status != calogOkE) {
            calogValueFree(&keyValue);
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
        status = calogAggSet(aggregate, &keyValue, &valValue);
        if (status != calogOkE) {
            calogValueFree(&keyValue);
            calogValueFree(&valValue);
            free(keys);
            calogAggFree(aggregate);
            return status;
        }
    }
    free(keys);
    calogValueAgg(out, aggregate);
    return calogOkE;
}


static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l) {
    CalogMyBasicT *context;
    BindingT        *binding;
    void            *userData;
    CalogValueT          *args;
    CalogValueT           result;
    mb_value_t       pushValue;
    void           **savedL;
    int32_t          argCount;
    int32_t          capacity;
    int32_t          index;
    int32_t          status;
    int              code;

    userData = NULL;
    mb_get_userdata(s, &userData);
    context = (CalogMyBasicT *)userData;
    binding = &context->bank[slot];

    code = mb_attempt_open_bracket(s, l);
    if (code != MB_FUNC_OK) {
        return code;
    }
    args = NULL;
    argCount = 0;
    capacity = 0;
    while (mb_has_arg(s, l)) {
        mb_value_t popped;
        mb_make_nil(popped);
        code = mb_pop_value(s, l, &popped);
        if (code != MB_FUNC_OK) {
            goto cleanupArgs;
        }
        if (argCount == capacity) {
            int32_t newCap;
            CalogValueT *grown;
            newCap = (capacity == 0) ? MB_INITIAL_ARGS : capacity * CALOG_GROWTH_FACTOR;
            grown = (CalogValueT *)realloc(args, (size_t)newCap * sizeof(CalogValueT));
            if (grown == NULL) {
                if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
                    mb_dispose_value(context->bas, popped);
                }
                code = mb_raise_error(s, l, SE_RN_OUT_OF_MEMORY, MB_FUNC_ERR);
                goto cleanupArgs;
            }
            args = grown;
            capacity = newCap;
        }
        status = mbToValueDepth(context, l, &popped, &args[argCount], 0);
        // A popped collection is owned by us (the consumer) and must be released
        // once marshalled; popped strings are borrowed interior pointers and
        // routine values are owned by their scope, so neither is disposed here.
        if (popped.type == MB_DT_LIST || popped.type == MB_DT_DICT) {
            mb_dispose_value(context->bas, popped);
        }
        if (status != calogOkE) {
            code = mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
            goto cleanupArgs;
        }
        argCount++;
    }
    code = mb_attempt_close_bracket(s, l);
    if (code != MB_FUNC_OK) {
        goto cleanupArgs;
    }

    // Route through calogCall so the actor layer marshals the native to the host thread
    // (like the other engines); currentL is published first so a host native that calls
    // back into a BASIC routine finds this context's serving frame.
    savedL = context->currentL;
    context->currentL = l;
    status = calogCall(context->broker, binding->name, args, argCount, &result);
    context->currentL = savedL;

    for (index = 0; index < argCount; index++) {
        calogValueFree(&args[index]);
    }
    free(args);
    args = NULL;

    if (status != calogOkE) {
        calogValueFree(&result);
        return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
    }
    mb_make_nil(pushValue);
    status = mbFromValueDepth(context, l, &result, &pushValue, 0);
    calogValueFree(&result);
    if (status != calogOkE) {
        return mb_raise_error(s, l, MB_NATIVE_ERROR, MB_FUNC_ERR);
    }
    // mb_push_value borrows a string (the interpreter never frees it); mb_push_string
    // marks the buffer for lazy destruction. Collections are owned by mb_push_value.
    if (pushValue.type == MB_DT_STRING) {
        return mb_push_string(s, l, pushValue.value.string);
    }
    return mb_push_value(s, l, pushValue);

cleanupArgs:
    for (index = 0; index < argCount; index++) {
        calogValueFree(&args[index]);
    }
    free(args);
    return code;
}


static int32_t mbFromValueDepth(CalogMyBasicT *context, void **l, const CalogValueT *value, mb_value_t *out, int32_t depth) {
    char *duplicate;

    mb_make_nil(*out);
    switch (value->type) {
        case calogNilE:
            return calogOkE;
        case calogBoolE:
            mb_make_int(*out, value->as.b ? 1 : 0);
            return calogOkE;
        case calogIntE:
            if (value->as.i < INT32_MIN || value->as.i > INT32_MAX) {
                return calogErrRangeE;
            }
            mb_make_int(*out, (int_t)value->as.i);
            return calogOkE;
        case calogRealE:
            mb_make_real(*out, (real_t)value->as.r);
            return calogOkE;
        case calogStringE:
            duplicate = mb_memdup(value->as.s.bytes, (unsigned)(value->as.s.length + 1));
            if (duplicate == NULL) {
                return calogErrOomE;
            }
            mb_make_string(*out, duplicate);
            return calogOkE;
        case calogAggE:
            return mbAggregateToCollDepth(context, l, value->as.agg, out, depth);
        case calogFnE:
            return calogErrUnsupportedE;
    }
    return calogErrTypeE;
}


static int32_t mbListToAggregate(CalogMyBasicT *context, void **l, mb_value_t coll, CalogValueT *out, int32_t depth) {
    CalogAggT *aggregate;
    int32_t     status;
    int32_t     index;
    int         count;

    calogValueNil(out);
    count = 0;
    mb_count_coll(context->bas, l, coll, &count);
    status = calogAggCreate(&aggregate, calogListE);
    if (status != calogOkE) {
        return status;
    }
    for (index = 0; index < count; index++) {
        mb_value_t idx;
        mb_value_t element;
        CalogValueT     elementValue;
        mb_make_int(idx, (int_t)index);
        mb_make_nil(element);
        mb_get_coll(context->bas, l, coll, idx, &element);
        status = mbToValueDepth(context, l, &element, &elementValue, depth + 1);
        if (status != calogOkE) {
            calogAggFree(aggregate);
            return status;
        }
        status = calogAggPush(aggregate, &elementValue);
        if (status != calogOkE) {
            calogValueFree(&elementValue);
            calogAggFree(aggregate);
            return status;
        }
    }
    calogValueAgg(out, aggregate);
    return calogOkE;
}


static int mbPrinter(struct mb_interpreter_t *s, const char *format, ...) {
    va_list args;
    int     written;

    (void)s;
    va_start(args, format);
    written = vprintf(format, args);
    va_end(args);
    return written;
}


static void mbReleaseSetValue(CalogMyBasicT *context, mb_value_t value) {
    // Release a value we handed to mb_set_coll: scalars/strings were copied, so
    // dispose ours; a LIST/DICT was stored by pointer without a reference, so the
    // parent now owns it and we must not touch it.
    if (value.type == MB_DT_LIST || value.type == MB_DT_DICT) {
        return;
    }
    mb_dispose_value(context->bas, value);
}


static void mbRetainBeforeSet(CalogMyBasicT *context, void **l, mb_value_t value) {
    // mb_set_coll stores a LIST/DICT by pointer WITHOUT adding a reference, so the
    // parent would not own it and would leak it on teardown. Add the reference
    // here; mbReleaseSetValue then deliberately does not dispose collections, so
    // the parent holds the single owning reference. Scalars/strings are copied by
    // mb_set_coll and need no reference.
    if (value.type == MB_DT_LIST || value.type == MB_DT_DICT) {
        mb_ref_value(context->bas, l, value);
    }
}


static int32_t mbToValueDepth(CalogMyBasicT *context, void **l, const mb_value_t *value, CalogValueT *out, int32_t depth) {
    CalogFnT *callable;
    int32_t    status;

    calogValueNil(out);
    if (depth > CALOG_MAX_DEPTH) {
        return calogErrDepthE;
    }
    switch (value->type) {
        case MB_DT_NIL:
            return calogOkE;
        case MB_DT_INT:
            calogValueInt(out, (int64_t)value->value.integer);
            return calogOkE;
        case MB_DT_REAL:
            calogValueReal(out, (double)value->value.float_point);
            return calogOkE;
        case MB_DT_STRING:
            return calogValueString(out, value->value.string, value->value.string != NULL ? (int64_t)strlen(value->value.string) : 0);
        case MB_DT_LIST:
            return mbListToAggregate(context, l, *value, out, depth);
        case MB_DT_DICT:
            return mbDictToAggregate(context, l, *value, out, depth);
        case MB_DT_ROUTINE:
            status = mbWrapRoutine(context, *value, &callable);
            if (status != calogOkE) {
                return status;
            }
            calogValueFn(out, callable);
            return calogOkE;
        default:
            return calogErrUnsupportedE;
    }
}


static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFnT **out) {
    MyBasicRoutineT *holder;
    int32_t          status;

    *out = NULL;
    holder = (MyBasicRoutineT *)malloc(sizeof(*holder));
    if (holder == NULL) {
        return calogErrOomE;
    }
    holder->context = context;
    holder->routine = routine;
    // A BASIC routine value is owned by its defining scope and is NOT separately
    // ref-counted here: mb_ref_value/mb_unref_value mishandle routine values and
    // corrupt the scope. The handle is therefore valid only while that scope is
    // alive -- i.e. while the context is serving (a native call is on the stack,
    // which the actor layer's parked serve frame guarantees).
    status = calogFnCreate(out, context->broker, mbCallableInvoke, holder, mbCallableRelease, context->ctxId);
    if (status != calogOkE) {
        free(holder);
        return status;
    }
    return calogOkE;
}


int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId) {
    CalogMyBasicT         *context;
    struct mb_interpreter_t *bas;

    *out = NULL;
    context = (CalogMyBasicT *)calloc(1, sizeof(*context));
    if (context == NULL) {
        return calogErrOomE;
    }
    if (mbContextCount == 0) {
        mb_init();
    }
    bas = NULL;
    if (mb_open(&bas) != MB_FUNC_OK || bas == NULL) {
        if (mbContextCount == 0) {
            mb_dispose();
        }
        free(context);
        return calogErrOomE;
    }
    context->bas      = bas;
    context->broker   = broker;
    context->ctxId    = ctxId;
    context->currentL = NULL;
    mbContextCount++;
    mb_set_userdata(bas, context);
    mb_set_printer(bas, mbPrinter);
    *out = context;
    return calogOkE;
}


void calogMyBasicDestroy(CalogMyBasicT *context) {
    if (context == NULL) {
        return;
    }
    if (context->bas != NULL) {
        mb_close(&context->bas);
    }
    free(context);
    mbContextCount--;
    if (mbContextCount == 0) {
        mb_dispose();
    }
}


int32_t calogMyBasicExportRoutine(CalogMyBasicT *context, const char *routineName, CalogFnT **out) {
    mb_value_t routine;
    char       upper[MB_NAME_MAX];
    size_t     index;

    *out = NULL;
    if (context->currentL == NULL) {
        return calogErrUnsupportedE;
    }
    // BASIC uppercases every identifier at parse time, but mb_get_routine matches
    // the raw name, so the lookup name must be uppercased to find "def d" as "D".
    for (index = 0; routineName[index] != '\0'; index++) {
        char ch;
        if (index + 1 >= sizeof(upper)) {
            return calogErrArgE;
        }
        ch = routineName[index];
        upper[index] = (ch >= 'a' && ch <= 'z') ? (char)(ch - 'a' + 'A') : ch;
    }
    upper[index] = '\0';
    mb_make_nil(routine);
    if (mb_get_routine(context->bas, context->currentL, upper, &routine) != MB_FUNC_OK) {
        return calogErrNotFoundE;
    }
    if (routine.type != MB_DT_ROUTINE) {
        return calogErrNotFoundE;
    }
    return mbWrapRoutine(context, routine, out);
}


int32_t calogMyBasicExpose(CalogMyBasicT *context, const char *name) {
    CalogEntryT *entry;
    int32_t       slot;

    entry = calogLookup(context->broker, name);
    if (entry == NULL) {
        return calogErrNotFoundE;
    }
    if (context->bankCount >= MB_BANK_SIZE) {
        return calogErrRangeE;
    }
    slot = context->bankCount;
    context->bank[slot].name = entry->name;
    if (mb_register_func(context->bas, name, mbTrampTable[slot]) == 0) {
        return calogErrArgE;
    }
    context->bankCount++;
    return calogOkE;
}


int32_t calogMyBasicRun(CalogMyBasicT *context, const char *source) {
    int code;

    code = mb_load_string(context->bas, source, true);
    if (code != MB_FUNC_OK) {
        fprintf(stderr, "my-basic load error: %d\n", code);
        return calogErrArgE;
    }
    code = mb_run(context->bas, true);
    if (code != MB_FUNC_OK) {
        fprintf(stderr, "my-basic run error: %d\n", code);
        return calogErrArgE;
    }
    return calogOkE;
}
