Native plugins › 06 Exceptions and callbacks

Exceptions and callbacks

Throw language exceptions from C, and call language methods from C, with the exception rules in both directions.

1. Throwing

vm_throw(VM* vm, Value exc);          /* any object implementing _LANG.Exception */
vm_throw_iobe(VM* vm, int64_t idx, int64_t len);
vm_throw_dzbe(VM* vm);                /* DivideByZeroException */
static void nd_divide(NativeCtx* ctx) {
    int32_t a = n_as_int(n_arg(ctx, 0), n_arg_type(ctx, 0));
    int32_t b = n_as_int(n_arg(ctx, 1), n_arg_type(ctx, 1));
    if (b == 0) {
        vm_throw_dzbe(ctx->vm);      /* language code catches DivideByZeroException */
        return;
    }
    ctx->out.i = a / b;
}

2. Calling back into the language

int vm_call(VM* vm, Value recv, const char* method,
            Value* args, uint32_t argc, const TypeDesc* const* arg_types,
            const TypeDesc* ret_type, Value* out);
Value r = NIL_VAL;
int rc = vm_call(ctx->vm, ctx->args[0], "___equals", &other, 1, NULL, NULL, &r);
if (rc == 0 && r.i) {
    /* the language method returned true */
} else if (rc == 1) {
    /* the method threw; r holds the exception object */
}
ReturnMeaning
0normal completion, result in *out
1the callee threw; the exception object is in *out
-1no such method, or a null / non-object receiver

3. Three ways to handle a callee exception

/* 1. Propagate it to your caller's language code: */
vm_throw(ctx->vm, exc);

/* 2. Handle it: inspect the object (its class, its fields) and continue. */

/* 3. Swallow it: ignore the return code. */

Nothing is automatic: the VM never unwinds through your C frames, so a native is always in control of what happens next.

4. A realistic callback: content equality

Generic containers need to compare elements whose type is only known through a slot. The pattern is to call ___equals for references and compare bits for primitives:

static int elem_equal(NativeCtx* ctx, Value a, Value b) {
    const TypeDesc* t = ctx->nslots > 0 ? ctx->slots[0] : NULL;
    if (t && n_is_ref(t)) {
        if (a.o == b.o) return 1;
        Value r = NIL_VAL;
        int rc = vm_call(ctx->vm, a, "___equals", &b, 1, NULL, NULL, &r);
        return (rc == 0 && r.i) ? 1 : 0;
    }
    return a.i == b.i;
}

5. Exceptions from constructors

If a native constructor throws, the object is not cached (for singletons) and the exception propagates like any other. Keep constructors short and avoid allocation-heavy work in them.