mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-09-01 10:11:10 +00:00
feat(records): a struct in the impl header is a real wire type
`any` (LogosMap/LogosList) was the only way to express a heterogeneous shape, so
~104 slots across openmetrics, package_manager, package_downloader, storage and
logoscore-cli describe their payloads with an untyped map. LIDL has carried
TypeDecl/FieldDecl all along — chat_module's hand-written .lidl declares records
and notes they "document the shape and light up if the generators gain struct
support". This is the impl-header path finally producing them.
parser - a plain `struct Foo { T a; U b; };` ahead of the impl class becomes a
TypeDecl. Field types go through the same cppTypeToLidl, so a record
field is subject to the same 64-bit / bstr / composition rules.
gate - a Named type is supported when the module DECLARES it. An undeclared
name is still a build error naming the type: records are opt-in, not
a reopening of the opaque fallback.
emitter - one Codec<Foo> specialisation per record. Fields are addressed
through decltype, so no C++ type name is spelled (same trick as
JsonArg), and because the specialisation plugs into
logos::detail::Codec, a record nested in [T] or {tstr: T} needs
nothing further emitted — the existing recursion handles it.
A missing field decodes as null and the leaf codec rejects it with the field's
path, so the diagnostics compose too.
Verified end to end, not just as emitted text — a module declaring
`struct Status { uint64_t port; std::string name; std::vector<uint8_t> blob; }`
used as param, return and inside a vector, over the real transport:
param {"port":9099,"name":"ok","blob":{"_bytes":"gAE"}} -> "9099|ok|2:128:1:129"
return {"blob":{"_bytes":"gAE"},"name":"ok","port":9099}
[record] [{...},{"blob":{"_bytes":""},"name":"b","port":1}]
missing field {"code":"dispatch_failed",
"message":"expected string at arg0.name, got null"}
Note the bytes field: tagged inside the record, and inside a record inside an
array, with no record-specific bytes handling anywhere.
Tests: 175/175, including that an undeclared Named type is still rejected.
Next for records: emit the `type Foo {...}` decls into the published .lidl so
consumers generate typed wrappers, then the Rust and Qt sides. Until then a
record is a provider-side contract that consumers still see as an object.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
77a7009742
commit
22aa17c129
@@ -365,6 +365,10 @@ ImplParseResult parseImplHeader(const QString& headerPath,
|
||||
QStringList pendingDoc;
|
||||
bool inBlockComment = false;
|
||||
|
||||
// Record being accumulated (see LookingForClass below).
|
||||
TypeDecl record;
|
||||
bool inRecord = false;
|
||||
|
||||
QRegularExpression classRe("\\bclass\\s+" + QRegularExpression::escape(className) + "\\b");
|
||||
QRegularExpression accessRe("^\\s*(public|private|protected)\\s*:");
|
||||
QRegularExpression eventsRe("^\\s*logos_events\\s*:");
|
||||
@@ -375,6 +379,41 @@ ImplParseResult parseImplHeader(const QString& headerPath,
|
||||
|
||||
switch (state) {
|
||||
case LookingForClass:
|
||||
// A plain `struct Foo { T a; U b; };` ahead of the impl class is a
|
||||
// RECORD: a named wire shape the module's methods can take and
|
||||
// return. LIDL has carried TypeDecl/FieldDecl all along (chat_module
|
||||
// declares records in a hand-written .lidl) — this is the
|
||||
// impl-header path finally producing them, so an author writes a
|
||||
// struct instead of an untyped LogosMap.
|
||||
if (inRecord) {
|
||||
if (line.startsWith("}")) {
|
||||
if (!record.fields.empty())
|
||||
result.module.types.push_back(record);
|
||||
inRecord = false;
|
||||
} else {
|
||||
static QRegularExpression fieldRe(
|
||||
"^([A-Za-z_][A-Za-z0-9_:<>,\\s\\*&]*?)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*;$");
|
||||
const QRegularExpressionMatch fm = fieldRe.match(line);
|
||||
if (fm.hasMatch()) {
|
||||
FieldDecl f;
|
||||
f.name = fm.captured(2).toStdString();
|
||||
f.type = cppTypeToLidl(fm.captured(1).trimmed());
|
||||
record.fields.push_back(f);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
{
|
||||
static QRegularExpression recordRe(
|
||||
"^struct\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{\\s*$");
|
||||
const QRegularExpressionMatch rm = recordRe.match(line);
|
||||
if (rm.hasMatch()) {
|
||||
record = TypeDecl();
|
||||
record.name = rm.captured(1).toStdString();
|
||||
inRecord = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (classRe.match(line).hasMatch()) {
|
||||
state = InClass;
|
||||
for (QChar c : line) {
|
||||
|
||||
Reference in New Issue
Block a user