feat(codegen): native C++ generator handles seq / Option / nested

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>
This commit is contained in:
Ivan FB 2026-05-31 18:20:38 +02:00
parent 725a7b6551
commit 2302e5fb7d
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
4 changed files with 378 additions and 119 deletions

View File

@ -28,10 +28,12 @@ make run
## Status
First cut. Methods whose params/returns use only scalar / string / bool /
nested-struct fields are generated (create, version, echo). Methods using
**sequences or optionals** are emitted as `// SKIPPED` for now (complex,
schedule) — those plus **native typed events** are the next increments. The
native-bare / `_cbor` filename reconciliation (matching the C headers) is also a
follow-up; today this emits `my_timer_native.hpp` so it coexists with the CBOR
`my_timer.hpp`.
Requests are fully supported: scalar / string / bool / nested struct **and now
sequences (`std::vector`) and optionals (`std::optional`)** — create, version,
echo, complex, schedule all generate and round-trip typed values (ASAN-clean).
`toC` uses a holder that owns the C-array backing while string pointers borrow
the C++ argument (valid for the call's duration; the library deep-copies).
Still to come: **native typed events** (`On<Event>` handlers) and the
native-bare / `_cbor` filename reconciliation (matching the C headers). Today
this emits `my_timer_native.hpp` so it coexists with the CBOR `my_timer.hpp`.

View File

@ -1,22 +1,34 @@
// Driver for the GENERATED native C++ bindings (my_timer_native.hpp).
// Uses the native (zero-serialization) ABI: typed structs in, typed structs out.
#include "my_timer_native.hpp"
#include <iostream>
int main() {
try {
my_timer::My_timerNode node(my_timer::TimerConfig{"cpp-native-gen"});
std::cout << "version: " << node.Version() << "\n";
auto r = node.Echo(my_timer::EchoRequest{"hello from generated C++", 5});
std::cout << "echo: echoed=" << r.echoed << " timerName=" << r.timerName
<< "\n";
std::cout << "echo: echoed=" << r.echoed << " timerName=" << r.timerName << "\n";
// seq + Option params (ComplexRequest), typed ComplexResponse return.
my_timer::ComplexRequest creq;
creq.messages = {{"one", 0}, {"two", 0}};
creq.tags = {"a", "b"};
creq.note = "a note";
creq.retries = 3;
auto c = node.Complex(creq);
std::cout << "complex: itemCount=" << c.itemCount << " hasNote=" << c.hasNote
<< " summary=\"" << c.summary << "\"\n";
// Multiple struct params (JobSpec, RetryPolicy, ScheduleConfig).
my_timer::JobSpec job; job.name = "nightly"; job.payload = {"x"}; job.priority = 5;
my_timer::RetryPolicy retry; retry.maxAttempts = 3; retry.backoffMs = 100; retry.retryOn = {"timeout"};
my_timer::ScheduleConfig sched; sched.startAtMs = 1000; sched.intervalMs = 5000; sched.jitter = 50;
auto s = node.Schedule(job, retry, sched);
std::cout << "schedule: jobId=\"" << s.jobId << "\" willRunCount=" << s.willRunCount << "\n";
std::cout << "done.\n";
return 0;
} catch (const std::exception &e) {
std::cerr << "error: " << e.what() << "\n";
return 1;
std::cerr << "error: " << e.what() << "\n"; return 1;
}
}

View File

@ -8,18 +8,23 @@
#include "my_timer.h"
#include <cstdint>
#include <future>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
namespace my_timer {
struct TimerConfig {
std::string name{};
};
inline ::TimerConfig toC(const TimerConfig& v) {
struct TimerConfigC {
::TimerConfig c{};
c.name = v.name.c_str();
return c;
};
inline TimerConfigC toC(const TimerConfig& v) {
TimerConfigC h;
h.c.name = v.name.c_str();
return h;
}
inline TimerConfig fromC(const ::TimerConfig& c) {
TimerConfig v{};
@ -31,11 +36,14 @@ struct EchoRequest {
std::string message{};
int64_t delayMs{};
};
inline ::EchoRequest toC(const EchoRequest& v) {
struct EchoRequestC {
::EchoRequest c{};
c.message = v.message.c_str();
c.delayMs = v.delayMs;
return c;
};
inline EchoRequestC toC(const EchoRequest& v) {
EchoRequestC h;
h.c.message = v.message.c_str();
h.c.delayMs = v.delayMs;
return h;
}
inline EchoRequest fromC(const ::EchoRequest& c) {
EchoRequest v{};
@ -48,11 +56,14 @@ struct EchoResponse {
std::string echoed{};
std::string timerName{};
};
inline ::EchoResponse toC(const EchoResponse& v) {
struct EchoResponseC {
::EchoResponse c{};
c.echoed = v.echoed.c_str();
c.timerName = v.timerName.c_str();
return c;
};
inline EchoResponseC toC(const EchoResponse& v) {
EchoResponseC h;
h.c.echoed = v.echoed.c_str();
h.c.timerName = v.timerName.c_str();
return h;
}
inline EchoResponse fromC(const ::EchoResponse& c) {
EchoResponse v{};
@ -61,17 +72,68 @@ inline EchoResponse fromC(const ::EchoResponse& c) {
return v;
}
struct ComplexRequest {
std::vector<EchoRequest> messages{};
std::vector<std::string> tags{};
std::optional<std::string> note{};
std::optional<int64_t> retries{};
};
struct ComplexRequestC {
::ComplexRequest c{};
std::vector<::EchoRequest> _messages;
std::vector<EchoRequestC> _messagesH;
std::vector<const char*> _tags;
};
inline ComplexRequestC toC(const ComplexRequest& v) {
ComplexRequestC h;
for (const auto& it : v.messages) {
h._messagesH.push_back(toC(it));
}
for (const auto& hh : h._messagesH) h._messages.push_back(hh.c);
h.c.messages = h._messages.empty() ? nullptr : h._messages.data();
h.c.messages_len = h._messages.size();
for (const auto& it : v.tags) {
h._tags.push_back(it.c_str());
}
h.c.tags = h._tags.empty() ? nullptr : h._tags.data();
h.c.tags_len = h._tags.size();
if (v.note.has_value()) {
h.c.note_present = 1;
h.c.note = (*v.note).c_str();
}
if (v.retries.has_value()) {
h.c.retries_present = 1;
h.c.retries = (*v.retries);
}
return h;
}
inline ComplexRequest fromC(const ::ComplexRequest& c) {
ComplexRequest v{};
for (std::size_t i = 0; i < c.messages_len; i++)
v.messages.push_back(fromC(c.messages[i]));
for (std::size_t i = 0; i < c.tags_len; i++)
v.tags.push_back((c.tags[i] ? std::string(c.tags[i]) : std::string()));
if (c.note_present)
v.note = (c.note ? std::string(c.note) : std::string());
if (c.retries_present)
v.retries = c.retries;
return v;
}
struct ComplexResponse {
std::string summary{};
int64_t itemCount{};
bool hasNote{};
};
inline ::ComplexResponse toC(const ComplexResponse& v) {
struct ComplexResponseC {
::ComplexResponse c{};
c.summary = v.summary.c_str();
c.itemCount = v.itemCount;
c.hasNote = (v.hasNote ? 1 : 0);
return c;
};
inline ComplexResponseC toC(const ComplexResponse& v) {
ComplexResponseC h;
h.c.summary = v.summary.c_str();
h.c.itemCount = v.itemCount;
h.c.hasNote = v.hasNote ? 1 : 0;
return h;
}
inline ComplexResponse fromC(const ::ComplexResponse& c) {
ComplexResponse v{};
@ -85,11 +147,14 @@ struct EchoEvent {
std::string message{};
int64_t echoCount{};
};
inline ::EchoEvent toC(const EchoEvent& v) {
struct EchoEventC {
::EchoEvent c{};
c.message = v.message.c_str();
c.echoCount = v.echoCount;
return c;
};
inline EchoEventC toC(const EchoEvent& v) {
EchoEventC h;
h.c.message = v.message.c_str();
h.c.echoCount = v.echoCount;
return h;
}
inline EchoEvent fromC(const ::EchoEvent& c) {
EchoEvent v{};
@ -98,19 +163,107 @@ inline EchoEvent fromC(const ::EchoEvent& c) {
return v;
}
struct JobSpec {
std::string name{};
std::vector<std::string> payload{};
int64_t priority{};
};
struct JobSpecC {
::JobSpec c{};
std::vector<const char*> _payload;
};
inline JobSpecC toC(const JobSpec& v) {
JobSpecC h;
h.c.name = v.name.c_str();
for (const auto& it : v.payload) {
h._payload.push_back(it.c_str());
}
h.c.payload = h._payload.empty() ? nullptr : h._payload.data();
h.c.payload_len = h._payload.size();
h.c.priority = v.priority;
return h;
}
inline JobSpec fromC(const ::JobSpec& c) {
JobSpec v{};
v.name = c.name ? std::string(c.name) : std::string();
for (std::size_t i = 0; i < c.payload_len; i++)
v.payload.push_back((c.payload[i] ? std::string(c.payload[i]) : std::string()));
v.priority = c.priority;
return v;
}
struct RetryPolicy {
int64_t maxAttempts{};
int64_t backoffMs{};
std::vector<std::string> retryOn{};
};
struct RetryPolicyC {
::RetryPolicy c{};
std::vector<const char*> _retryOn;
};
inline RetryPolicyC toC(const RetryPolicy& v) {
RetryPolicyC h;
h.c.maxAttempts = v.maxAttempts;
h.c.backoffMs = v.backoffMs;
for (const auto& it : v.retryOn) {
h._retryOn.push_back(it.c_str());
}
h.c.retryOn = h._retryOn.empty() ? nullptr : h._retryOn.data();
h.c.retryOn_len = h._retryOn.size();
return h;
}
inline RetryPolicy fromC(const ::RetryPolicy& c) {
RetryPolicy v{};
v.maxAttempts = c.maxAttempts;
v.backoffMs = c.backoffMs;
for (std::size_t i = 0; i < c.retryOn_len; i++)
v.retryOn.push_back((c.retryOn[i] ? std::string(c.retryOn[i]) : std::string()));
return v;
}
struct ScheduleConfig {
int64_t startAtMs{};
int64_t intervalMs{};
std::optional<int64_t> jitter{};
};
struct ScheduleConfigC {
::ScheduleConfig c{};
};
inline ScheduleConfigC toC(const ScheduleConfig& v) {
ScheduleConfigC h;
h.c.startAtMs = v.startAtMs;
h.c.intervalMs = v.intervalMs;
if (v.jitter.has_value()) {
h.c.jitter_present = 1;
h.c.jitter = (*v.jitter);
}
return h;
}
inline ScheduleConfig fromC(const ::ScheduleConfig& c) {
ScheduleConfig v{};
v.startAtMs = c.startAtMs;
v.intervalMs = c.intervalMs;
if (c.jitter_present)
v.jitter = c.jitter;
return v;
}
struct ScheduleResult {
std::string jobId{};
int64_t willRunCount{};
int64_t firstRunAtMs{};
int64_t effectiveBackoffMs{};
};
inline ::ScheduleResult toC(const ScheduleResult& v) {
struct ScheduleResultC {
::ScheduleResult c{};
c.jobId = v.jobId.c_str();
c.willRunCount = v.willRunCount;
c.firstRunAtMs = v.firstRunAtMs;
c.effectiveBackoffMs = v.effectiveBackoffMs;
return c;
};
inline ScheduleResultC toC(const ScheduleResult& v) {
ScheduleResultC h;
h.c.jobId = v.jobId.c_str();
h.c.willRunCount = v.willRunCount;
h.c.firstRunAtMs = v.firstRunAtMs;
h.c.effectiveBackoffMs = v.effectiveBackoffMs;
return h;
}
inline ScheduleResult fromC(const ::ScheduleResult& c) {
ScheduleResult v{};
@ -159,6 +312,20 @@ inline void my_timer_native_my_timer_echo(int ret, const char* msg, std::size_t
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_my_timer_complex(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<ComplexResponse>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = fromC(*reinterpret_cast<const ::ComplexResponse*>(msg));
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
inline void my_timer_native_my_timer_schedule(int ret, const char* msg, std::size_t len, void* ud) {
auto* c = static_cast<detail::Capture<ScheduleResult>*>(ud);
c->ret = ret;
if (ret == RET_OK) c->value = fromC(*reinterpret_cast<const ::ScheduleResult*>(msg));
else c->err = detail::rawText(msg, len);
c->done.set_value();
}
} // extern "C"
class My_timerNode {
@ -167,7 +334,7 @@ class My_timerNode {
detail::AckCapture cap;
auto fut = cap.done.get_future();
auto c_config = toC(config);
ctx_ = my_timer_create(c_config, my_timer_native_ack, &cap);
ctx_ = my_timer_create(c_config.c, my_timer_native_ack, &cap);
if (!ctx_) throw std::runtime_error("my_timer_create returned null");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
@ -177,7 +344,7 @@ class My_timerNode {
detail::Capture<EchoResponse> cap;
auto fut = cap.done.get_future();
auto c_req = toC(req);
if (my_timer_echo(ctx_, my_timer_native_my_timer_echo, &cap, c_req) != RET_OK)
if (my_timer_echo(ctx_, my_timer_native_my_timer_echo, &cap, c_req.c) != RET_OK)
throw std::runtime_error("my_timer_echo dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
@ -194,8 +361,30 @@ class My_timerNode {
return cap.value;
}
// SKIPPED my_timer_complex: seq/Option/multi-struct params not yet supported by native C++ codegen
// SKIPPED my_timer_schedule: seq/Option/multi-struct params not yet supported by native C++ codegen
ComplexResponse Complex(const ComplexRequest& req) {
detail::Capture<ComplexResponse> cap;
auto fut = cap.done.get_future();
auto c_req = toC(req);
if (my_timer_complex(ctx_, my_timer_native_my_timer_complex, &cap, c_req.c) != RET_OK)
throw std::runtime_error("my_timer_complex dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
ScheduleResult Schedule(const JobSpec& job, const RetryPolicy& retry, const ScheduleConfig& schedule) {
detail::Capture<ScheduleResult> cap;
auto fut = cap.done.get_future();
auto c_job = toC(job);
auto c_retry = toC(retry);
auto c_schedule = toC(schedule);
if (my_timer_schedule(ctx_, my_timer_native_my_timer_schedule, &cap, c_job.c, c_retry.c, c_schedule.c) != RET_OK)
throw std::runtime_error("my_timer_schedule dispatch failed");
fut.wait();
if (cap.ret != RET_OK) throw std::runtime_error(cap.err);
return cap.value;
}
~My_timerNode() { if (ctx_) my_timer_destroy(ctx_); }
My_timerNode(const My_timerNode&) = delete;
My_timerNode& operator=(const My_timerNode&) = delete;

View File

@ -14,10 +14,32 @@ import std/[os, strutils]
import ./meta, ./string_helpers
import ./c as cgen
proc cppType(t: string): string =
## Idiomatic C++ type for an `{.ffi.}` field / scalar.
proc isSeqT(t: string): bool =
t.strip().startsWith("seq[") and t.strip().endsWith("]")
proc isOptT(t: string): bool =
let s = t.strip()
(s.startsWith("Option[") or s.startsWith("Maybe[")) and s.endsWith("]")
proc seqElem(t: string): string =
t.strip()["seq[".len .. ^2].strip()
proc optElem(t: string): string =
let s = t.strip()
let p = if s.startsWith("Maybe["): "Maybe[".len else: "Option[".len
s[p .. ^2].strip()
proc isStringT(t: string): bool =
t.strip() in ["string", "cstring"]
proc isStructT(t: string, types: seq[FFITypeMeta]): bool =
for ty in types:
if ty.name == t.strip():
return true
false
proc scalarCpp(t: string): string =
case t.strip()
of "string", "cstring": "std::string"
of "int", "int64", "clong": "int64_t"
of "int32", "cint": "int32_t"
of "int16": "int16_t"
@ -29,94 +51,122 @@ proc cppType(t: string): string =
of "bool": "bool"
of "float", "float32": "float"
of "float64": "double"
else: t.strip() # nested {.ffi.} struct -> its C++ name
else: t.strip() # nested struct -> its C++ name
proc isSeqT(t: string): bool =
t.strip().startsWith("seq[") and t.strip().endsWith("]")
proc isOptT(t: string): bool =
proc cppType(t: string): string =
## Idiomatic C++ type for an `{.ffi.}` field / param.
let s = t.strip()
(s.startsWith("Option[") or s.startsWith("Maybe[")) and s.endsWith("]")
if isSeqT(s): "std::vector<" & cppType(seqElem(s)) & ">"
elif isOptT(s): "std::optional<" & cppType(optElem(s)) & ">"
elif isStringT(s): "std::string"
else: scalarCpp(s)
proc isStringT(t: string): bool =
t.strip() in ["string", "cstring"]
proc cElemCpp(t: string, types: seq[FFITypeMeta]): string =
## The C type used for one seq element / option payload (matches emitCStructs).
let s = t.strip()
if isStringT(s): "const char*"
elif isStructT(s, types): "::" & s
elif s == "bool": "int"
else: scalarCpp(s)
proc isStructT(t: string, types: seq[FFITypeMeta]): bool =
for ty in types:
if ty.name == t.strip():
return true
false
# Element-granular converters. `src` yields one element on the source side.
proc toCElem(t, src: string, types: seq[FFITypeMeta]): string =
let s = t.strip()
if isStringT(s): src & ".c_str()"
elif s == "bool": "(" & src & " ? 1 : 0)"
elif isStructT(s, types): "toC(" & src & ").c"
else: src
proc isSimpleType(t: FFITypeMeta, types: seq[FFITypeMeta]): bool =
## True if every field is scalar / string / bool / nested *simple* struct
## (no sequences or optionals yet).
for f in t.fields:
let ft = f.typeName.strip()
if isSeqT(ft) or isOptT(ft):
return false
if isStructT(ft, types):
for inner in types:
if inner.name == ft and not isSimpleType(inner, types):
return false
true
proc paramSimple(typeName: string, types: seq[FFITypeMeta]): bool =
let t = typeName.strip()
if isStringT(t) or t == "bool":
return true
if cppType(t) != t: # mapped scalar
return true
if isStructT(t, types):
for ty in types:
if ty.name == t:
return isSimpleType(ty, types)
false
# --- toC / fromC field converters -------------------------------------------
proc toCField(f: FFIFieldMeta, types: seq[FFITypeMeta]): string =
let t = f.typeName.strip()
if isStringT(t):
"v." & f.name & ".c_str()"
elif t == "bool":
"(v." & f.name & " ? 1 : 0)"
elif isStructT(t, types):
"toC(v." & f.name & ")"
else:
"v." & f.name
proc fromCField(f: FFIFieldMeta, types: seq[FFITypeMeta]): string =
let t = f.typeName.strip()
if isStringT(t):
"c." & f.name & " ? std::string(c." & f.name & ") : std::string()"
elif t == "bool":
"c." & f.name & " != 0"
elif isStructT(t, types):
"fromC(c." & f.name & ")"
else:
"c." & f.name
proc fromCElem(t, src: string, types: seq[FFITypeMeta]): string =
let s = t.strip()
if isStringT(s): "(" & src & " ? std::string(" & src & ") : std::string())"
elif s == "bool": "(" & src & " != 0)"
elif isStructT(s, types): "fromC(" & src & ")"
else: src
proc emitTypes(types: seq[FFITypeMeta]): seq[string] =
var L: seq[string] = @[]
for t in types:
if not isSimpleType(t, types):
continue
# C++ struct.
L.add("struct " & t.name & " {")
for f in t.fields:
L.add(" " & cppType(f.typeName) & " " & f.name & "{};")
L.add("};")
# toC: build the C-POD struct (strings borrow the C++ object's storage,
# valid for the duration of the call).
L.add("inline ::" & t.name & " toC(const " & t.name & "& v) {")
# Holder: owns the backing storage for any seq fields and exposes the C-POD
# struct. Returned by value from `toC`; the std::vector buffers survive the
# move/NRVO, and string pointers borrow the source `v` (kept alive by the
# caller for the duration of the call).
L.add("struct " & t.name & "C {")
L.add(" ::" & t.name & " c{};")
for f in t.fields:
L.add(" c." & f.name & " = " & toCField(f, types) & ";")
L.add(" return c;")
if isSeqT(f.typeName):
L.add(" std::vector<" & cElemCpp(seqElem(f.typeName), types) & "> _" &
f.name & ";")
if isStructT(seqElem(f.typeName), types):
L.add(" std::vector<" & cppType(seqElem(f.typeName)) & "C> _" &
f.name & "H;")
L.add("};")
L.add("inline " & t.name & "C toC(const " & t.name & "& v) {")
L.add(" " & t.name & "C h;")
for f in t.fields:
let ft = f.typeName.strip()
if isSeqT(ft):
let e = seqElem(ft)
L.add(" for (const auto& it : v." & f.name & ") {")
if isStructT(e, types):
# Keep element holders alive, then collect their C structs.
L.add(" h._" & f.name & "H.push_back(toC(it));")
else:
L.add(" h._" & f.name & ".push_back(" & toCElem(e, "it", types) & ");")
L.add(" }")
if isStructT(e, types):
L.add(" for (const auto& hh : h._" & f.name & "H) h._" & f.name &
".push_back(hh.c);")
L.add(" h.c." & f.name & " = h._" & f.name & ".empty() ? nullptr : h._" &
f.name & ".data();")
L.add(" h.c." & f.name & "_len = h._" & f.name & ".size();")
elif isOptT(ft):
let e = optElem(ft)
L.add(" if (v." & f.name & ".has_value()) {")
L.add(" h.c." & f.name & "_present = 1;")
L.add(" h.c." & f.name & " = " & toCElem(e, "(*v." & f.name & ")", types) & ";")
L.add(" }")
elif isStringT(ft):
L.add(" h.c." & f.name & " = v." & f.name & ".c_str();")
elif ft == "bool":
L.add(" h.c." & f.name & " = v." & f.name & " ? 1 : 0;")
elif isStructT(ft, types):
L.add(" h.c." & f.name & " = toC(v." & f.name & ").c;")
else:
L.add(" h.c." & f.name & " = v." & f.name & ";")
L.add(" return h;")
L.add("}")
# fromC: copy out of a C-POD struct into C++-owned values.
L.add("inline " & t.name & " fromC(const ::" & t.name & "& c) {")
L.add(" " & t.name & " v{};")
for f in t.fields:
L.add(" v." & f.name & " = " & fromCField(f, types) & ";")
let ft = f.typeName.strip()
if isSeqT(ft):
let e = seqElem(ft)
L.add(" for (std::size_t i = 0; i < c." & f.name & "_len; i++)")
L.add(" v." & f.name & ".push_back(" &
fromCElem(e, "c." & f.name & "[i]", types) & ");")
elif isOptT(ft):
let e = optElem(ft)
L.add(" if (c." & f.name & "_present)")
L.add(" v." & f.name & " = " &
fromCElem(e, "c." & f.name, types) & ";")
elif isStringT(ft):
L.add(" v." & f.name & " = c." & f.name & " ? std::string(c." & f.name &
") : std::string();")
elif ft == "bool":
L.add(" v." & f.name & " = c." & f.name & " != 0;")
elif isStructT(ft, types):
L.add(" v." & f.name & " = fromC(c." & f.name & ");")
else:
L.add(" v." & f.name & " = c." & f.name & ";")
L.add(" return v;")
L.add("}")
L.add("")
@ -132,8 +182,12 @@ proc methodName(procName, libName: string): string =
capitalizeFirstLetter(snakeToPascalCase(bare))
proc procSupported(p: FFIProcMeta, types: seq[FFITypeMeta]): bool =
## Scalar / string / `{.ffi.}`-struct params are supported (structs may carry
## seq/Option fields now). Bare seq/Option top-level params and raw pointers
## are not (the native ABI doesn't expose them either).
for ep in p.extraParams:
if ep.isPtr or not paramSimple(ep.typeName, types):
let t = ep.typeName.strip()
if ep.isPtr or isSeqT(t) or isOptT(t):
return false
true
@ -157,8 +211,10 @@ proc generateCppNativeHeader*(
L.add("#include \"" & libName & ".h\"")
L.add("#include <cstdint>")
L.add("#include <future>")
L.add("#include <optional>")
L.add("#include <stdexcept>")
L.add("#include <string>")
L.add("#include <vector>")
L.add("")
L.add("namespace " & libName & " {")
L.add("")
@ -240,7 +296,7 @@ proc generateCppNativeHeader*(
params.add("const " & cppType(ep.typeName) & "& " & ep.name)
if isStructT(ep.typeName, types):
conv.add(" auto c_" & ep.name & " = toC(" & ep.name & ");")
args.add("c_" & ep.name)
args.add("c_" & ep.name & ".c")
else:
args.add(ep.name)
let argsStr = if args.len > 0: args.join(", ") & ", " else: ""
@ -273,7 +329,7 @@ proc generateCppNativeHeader*(
params.add("const " & cppType(ep.typeName) & "& " & ep.name)
if isStructT(ep.typeName, types):
conv.add(" auto c_" & ep.name & " = toC(" & ep.name & ");")
args.add("c_" & ep.name)
args.add("c_" & ep.name & ".c")
else:
args.add(ep.name)
let argsStr = if args.len > 0: ", " & args.join(", ") else: ""