Aligns the C++ generators with the C generator and the symbol naming: the
native (zero-serialization, same-process) wrapper is the bare `<lib>.hpp` and
the CBOR (inter-process) wrapper carries the `_cbor` suffix — mirroring the C
headers (`<lib>.h` / `<lib>_cbor.h`) and the `<name>` / `<name>_cbor` exports.
Previously native was `<lib>_native.hpp` and CBOR was the bare `<lib>.hpp`,
which is backwards from the symbol convention and would collide on the native
`<lib>.h` when both ABIs emit into one dir (ffiMode=both). With the flip, a
single `genbindings_cpp` run now drops `<lib>.hpp` + `<lib>_cbor.hpp` side by
side, exactly like c_bindings holds both `.h` headers.
Consumers updated to match: the CBOR cpp_bindings driver and the C++ e2e suite
include `*_cbor.hpp`; the native example includes the bare `<lib>.hpp`.
Validated: native example runs on `my_timer.hpp`; C++ e2e suite 19/19 on the
`_cbor.hpp` headers; check_bindings_cpp regen is deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the ergonomic native event surface to the C++ generator:
`node.On<Event>(std::function<void(const <Payload>&)>)` registers a native
listener; a per-event extern "C" trampoline reads the typed POD
(`fromC(*reinterpret_cast<const ::<Payload>*>(msg))`) and invokes the handler —
no CBOR. The handler is owned by the node (a `std::map` of `ListenerBase`) so
its address stays valid until `removeEventListener`.
The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo
fires it. Verified end-to-end and ASAN-clean.
With this the native C++ generator covers the full surface: requests
(scalar/string/bool/seq/Option/nested), typed struct returns, and typed events.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the native C++ marshalling to sequences and optionals: a C++ field maps
to std::vector<T> / std::optional<T>, and `toC` returns a holder that owns the
C-array backing (move/NRVO-safe std::vectors) while string pointers borrow the
C++ argument — valid for the call, which the library deep-copies. `fromC` reads
seq/Option back out of the C-POD.
Unblocks the timer's complex (seq-of-structs / seq-of-strings / two optionals)
and schedule (three struct params) methods — they now generate and round-trip
typed results. Verified end-to-end and ASAN-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A native C++ binding generator (`cpp_native.nim`), the C++ counterpart of the C
and Go native paths and companion to the CBOR `cpp.nim`. It emits
`<lib>_native.hpp`: an idiomatic C++ struct + `toC`/`fromC` per `{.ffi.}` type,
and a `<Lib>Node` class whose methods marshal typed args into / read typed
struct returns out of the native ABI (`<name>` entry points + flat C structs in
`<lib>.h`) — zero serialization. Wired into genBindings under
`targetLang=cpp` + `-d:ffiMode=native`; emits the native C header alongside so
the binding is self-contained. Task: `genbindings_cpp_native`.
First cut covers scalar/string/bool/nested-struct fields (create/version/echo);
seq/Option params are `// SKIPPED`, and native typed events are next. Filename
is `_native.hpp` for now to coexist with the CBOR `.hpp` (rename is a follow-up).
Verified end-to-end: the generated example builds and round-trips a typed
EchoResponse (`make run`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add the `-d:ffiMode=native|cbor|both` strdefine (default both) with
`ffiEmitNative`/`ffiEmitCbor` helpers; the C generator now emits only the
selected header(s) (`<lib>.h` and/or `<lib>_cbor.h`).
- Native C events: the native header documents each event's payload type
(`"on_echo_fired" -> const EchoEvent *`) so consumers cast the callback's msg
to the typed struct — the bare native listener already delivers it.
- nimble tasks: `genbindings_c` (both), `genbindings_c_native`,
`genbindings_c_cbor`.
Verified: native mode emits only my_timer.h, cbor only my_timer_cbor.h, both
emits both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the ergonomic native event surface for Go: `node.On<Event>(func(<Payload>))`
registers a native listener for that event and the library delivers the typed
`<Payload>` POD, which an exported Go callback reads into a Go struct (reusing
the generated `fromC`) and hands to the user's handler — no CBOR parsing. Sits
beside the existing CBOR `SetEventHandler` (wildcard / inter-process).
The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo
fires it. Verified end-to-end with `go run -race`.
C/C++/Rust get the same per-event typed handler + router next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Events now mirror the native/CBOR split already in place for requests, with the
same symbol-naming convention:
- `<lib>_add_event_listener` -> NATIVE listener (typed `<T>Pod` by pointer)
- `<lib>_add_event_listener_cbor` -> CBOR listener (EventEnvelope bytes)
Framework: `FFIEventListener` gains a `native` flag; `addEventListener` a
`native` param; a new `dispatchFFIEventDual` template builds the `<T>Pod` once
for native listeners (`nimToPod`/`freePod`) and the CBOR envelope once for the
rest, fanning each out — so a single `{.ffiEvent.}` dispatch serves both kinds.
`declareLibrary` exports both registration entry points.
Generators: the bare `<lib>_add_event_listener` is the native symbol; every
CBOR consumer (C/C++/Go/Rust) now targets `<lib>_add_event_listener_cbor`. The
rename and the generator updates ship together so the bare name is never briefly
broken. Bindings regenerated.
Validated: native-event unit test (typed POD to native + CBOR to cbor listener,
orc/refc/ASAN); full unit suite; C++ e2e 19/19; Go example; existing event
tests unchanged. The per-event *typed* native callback + wildcard router (the
ergonomic consumer surface) is a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every generated library exports both ABIs side by side; spell out the choice in
the example READMEs: the native (pure-C) ABI is the default for same-process /
local calls (flat C structs, zero serialization), while the CBOR ABI exists
solely for inter-process communication (different process or machine). In a
shared address space CBOR is pure overhead, so prefer native locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Echo/Complex/Schedule now return typed Go structs (EchoResponse,
ComplexResponse, ScheduleResult); print their fields instead of an "ok" line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Struct-returning methods now hand back a typed Go struct instead of the raw
CBOR/bytes. Since the native return POD is freed right after the callback, the
POD->Go conversion must happen in-callback: the generator emits a `fromC()`
reader per {.ffi.} type and, per struct-returning proc, an exported Go result
callback. The method calls the native entry point directly with that callback
and a `runtime/cgo.Handle` (boxed in a small C allocation so it travels through
the void* userData checkptr-safe), then blocks until the callback delivers the
typed value or error on the result slot.
String/raw-returning procs keep the existing C-bridge + condvar path. Validated
end-to-end (Echo/Complex/Schedule) including under `go run -race`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the native C example to cast each struct return's callback msg to its
`const <Type>*` and read it in-callback (EchoResponse, ComplexResponse), instead
of scanning opaque bytes. Regenerate the headers with the new return-shape note.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A runnable main.go that constructs the timer with a TimerConfig, then calls
Echo (struct param), Complex (slice-of-structs + slice + two optionals) and
Schedule (three struct params) with idiomatic Go values — the methods the Go
generator used to skip. The Makefile builds the dylib next to the package
(cgo's ${SRCDIR} rpath finds it at runtime); README documents the Nim->Go type
mapping. Verified end-to-end with `go run`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Go generator previously emitted a `// SKIPPED` stub for any proc with a
struct, sequence or optional parameter, leaving Echo/Complex/Schedule
uncallable. Now that the native ABI carries those as flat C-POD structs, the Go
side can marshal them: emit an idiomatic Go struct per {.ffi.} type plus a
`toC()` that builds the matching `C.<Type>` (C.CString for strings, a C array
for seqs, present-flags for options, recursively for nested structs) and
returns cleanup funcs run via defer once the call returns. The native path
deep-copies every argument, so releasing the C buffers immediately is safe.
The C bridge already accepted struct-by-value params via the pass-through type
mapping; only the Go-side conversion and the `allSupported` gate needed work.
Bare seq/Option *top-level* params (not wrapped in a struct) remain skipped, as
the native ABI does not expose them either.
The generated package is now self-contained: the native `<lib>.h` is emitted
beside the `.go`, and the cgo directives use ${SRCDIR} so the header and the
staged library resolve without extra env vars. genbindings_go runs gofmt to
finalize column alignment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native C ABI only works in-process. This example demonstrates the other
half — the CBOR ABI crossing a process (and machine) boundary — since the `ctx`
pointer is process-local and cannot travel over the wire.
A server links libmy_timer, owns one context, and serves method calls; a client
links nothing (it only needs the FfiCbor encoder/ffi_decode_text in
my_timer_cbor.h) and speaks the same CBOR over a socket. Both binaries accept
`--unix <path>` for two processes on one host and `--tcp <host> <port>` for two
machines — the only difference is the socket family, so one client/server pair
covers both scenarios. Framing is length-prefixed in network byte order so the
endpoints may differ in OS, arch, or endianness.
`proto.h` carries the shared framing, the CBOR request builders, and a small
CBOR map reader so the client can pull text fields out of a response without a
full CBOR library. Verified end-to-end over both AF_UNIX and TCP loopback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The C codegen already emitted `my_timer.h` / `my_timer_cbor.h`, but the example
had no runnable driver. Add `example.c` exercising the native ABI end-to-end
(ctor with a struct param, string-returning version, struct-param echo, and a
deeply nested ComplexRequest), plus a Makefile that builds the Nim dylib from
the repo root — where the vendored Nimble deps resolve — and links the driver.
Native is the same-process path; the companion CBOR headers are for crossing a
process/machine boundary (see the forthcoming ipc example).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Rust wrapper speaks CBOR, but after the native/CBOR split it still declared
and called the bare `<name>` request symbols — which are now the *native*
(typed-args) entry points, so every Rust request hit the wrong ABI (struct/ptr
mismatch). This is the Rust counterpart of the C++ fix (914c70a), which was
missed at the time. Point the ffi.rs externs and the api.rs ctor/method calls at
`<name>_cbor`; the destructor has no CBOR variant and the event registration is
unchanged here.
Verified at runtime: the rust_client now creates a context and round-trips
version / echo / schedule over CBOR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The C++ wrapper speaks CBOR, but after the native/CBOR split it still called the
bare `<name>` symbols — which are now the *native* (typed-args) entry points, so
every C++ call hit the wrong ABI and the C++ e2e failed 19/19 (also reddening
the ASan/TSan jobs, which run the same suite). Point the generated extern
declarations and call sites at `<name>_cbor` for `{.ffi.}` procs and the ctor;
the destructor has no CBOR variant and stays bare. Regenerated the timer and
echo C++ bindings. C++ e2e back to 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>