diff --git a/examples/ffi-06-callback.sx b/examples/ffi-06-callback.sx new file mode 100644 index 0000000..712fa28 --- /dev/null +++ b/examples/ffi-06-callback.sx @@ -0,0 +1,59 @@ +// Phase 0 baseline (PLAN-FFI.md step 0.6): sx function passed to C +// as a function pointer; C invokes it; sx-side observable effect. +// Mirrors the `app->onInputEvent` install pattern in +// library/modules/platform/android.sx. +// +// Two arities covered: +// 1. (s32) -> s32 — single-arg callback +// 2. (*void, s32) -> s32 — pointer + value (onInputEvent shape) +// +// Plus a side-effect via a global so we can confirm the callback +// actually fired (return value + state mutation both observable). + +#import "modules/std.sx"; + +#import c { + #source "vendors/ffi_callback/ffi_callback.c"; +}; + +ffi_apply_callback :: (cb: (s32) -> s32, value: s32) -> s32 #foreign; +ffi_apply_callback2 :: (cb: (*void, s32) -> s32, ctx: *void, v: s32) -> s32 #foreign; + +g_callback_hits : s32 = 0; +g_callback_sum : s32 = 0; + +double_it :: (x: s32) -> s32 { + g_callback_hits += 1; + g_callback_sum += x; + x * 2; +} + +add_with_ctx :: (ctx: *void, v: s32) -> s32 { + g_callback_hits += 1; + // Pass a sentinel via ctx to prove the pointer arg also survives the + // round-trip — read it back as an s32 through *s32. + p : *s32 = xx ctx; + p.* + v; +} + +main :: () -> s32 { + // ── Single-arg callback ──────────────────────────────────────── + r1 := ffi_apply_callback(double_it, 21); + print("callback returned = {}\n", r1); + print("hits after first call = {}\n", g_callback_hits); + print("sum after first call = {}\n", g_callback_sum); + + // Two more calls confirm the same fn pointer keeps working. + ffi_apply_callback(double_it, 7); + ffi_apply_callback(double_it, 11); + print("hits after three calls = {}\n", g_callback_hits); + print("sum after three calls = {}\n", g_callback_sum); + + // ── Two-arg callback with opaque ctx pointer ─────────────────── + ctx_val : s32 = 100; + r2 := ffi_apply_callback2(add_with_ctx, xx @ctx_val, 42); + print("ctx + value = {}\n", r2); + print("hits after ctx callback = {}\n", g_callback_hits); + + 0; +} diff --git a/tests/expected/ffi-06-callback.exit b/tests/expected/ffi-06-callback.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/ffi-06-callback.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/ffi-06-callback.txt b/tests/expected/ffi-06-callback.txt new file mode 100644 index 0000000..a9b8285 --- /dev/null +++ b/tests/expected/ffi-06-callback.txt @@ -0,0 +1,7 @@ +callback returned = 42 +hits after first call = 1 +sum after first call = 21 +hits after three calls = 3 +sum after three calls = 39 +ctx + value = 142 +hits after ctx callback = 4 diff --git a/vendors/ffi_callback/ffi_callback.c b/vendors/ffi_callback/ffi_callback.c new file mode 100644 index 0000000..605b43f --- /dev/null +++ b/vendors/ffi_callback/ffi_callback.c @@ -0,0 +1,9 @@ +#include "ffi_callback.h" + +int ffi_apply_callback(int (*cb)(int), int value) { + return cb(value); +} + +int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v) { + return cb(ctx, v); +} diff --git a/vendors/ffi_callback/ffi_callback.h b/vendors/ffi_callback/ffi_callback.h new file mode 100644 index 0000000..5441289 --- /dev/null +++ b/vendors/ffi_callback/ffi_callback.h @@ -0,0 +1,13 @@ +// C-to-sx callback FFI baseline. C takes a function pointer + a value, +// invokes the callback with the value, and returns whatever the callback +// returned. Mirrors the `app->onInputEvent` pattern in +// library/modules/platform/android.sx where sx installs a handler that +// native_app_glue invokes from its input-event loop. + +int ffi_apply_callback(int (*cb)(int), int value); + +// Two-arg variant — the actual chess-on-Android shape: +// the callback receives a pointer + a value (mirrors +// onInputEvent(app, event) where both are opaque pointers from +// the C caller's point of view). +int ffi_apply_callback2(int (*cb)(void *ctx, int v), void *ctx, int v);