Files
sx/examples/ffi-06-callback.sx
agra 3855f2351e ffi: move test-companion .c/.h next to their .sx (drop vendors/ namespace)
vendors/ is a third-party namespace (stb_image, kb_text_shape, etc.);
test fixtures don't belong there. The .c/.h companion files for the
Phase-0 FFI baselines now sit alongside the .sx that drives them in
examples/, with matching basenames:

  examples/ffi-01-primitives.{sx,c,h}    <- was vendors/ffi_primitives/
  examples/ffi-02-small-struct.{sx,c,h}  <- was vendors/ffi_structs/
  examples/ffi-03-large-struct.{sx,c,h}  <- was vendors/ffi_large_struct/
  examples/ffi-04-fp-struct.{sx,c,h}     <- was vendors/ffi_fp_struct/
  examples/ffi-05-string-args.{sx,c,h}   <- was vendors/ffi_strings/
  examples/ffi-06-callback.{sx,c,h}      <- was vendors/ffi_callback/
  examples/101-ffi-medium-struct.{sx,c}  <- was vendors/ffi_medium_struct/

`#source` / `#include` paths in the .sx files become bare filenames
(no prefix) since imports.zig's base_dir resolution finds them
relative to the importing .sx file's directory.

`library/vendors/sx_ffi_resolve_test/` stays put — that one's the
whole point: regression coverage for the stdlib-search branch of
the resolution chain, so it must live where ONLY that branch can
find it.

94/94 regression tests pass.
2026-05-19 11:54:36 +03:00

60 lines
2.1 KiB
Plaintext

// 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 "ffi-06-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;
}