mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-24 18:30:08 +00:00
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:
parent
725a7b6551
commit
2302e5fb7d
@ -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`.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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: ""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user