mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
* fix(generator): one Qt type mapper, and it knows about void; LpClient takes a timeout
Two near-duplicate LIDL->Qt type mappers existed — legacy/main.cpp's
lidlTypeExprToQtTypeName and experimental/lidl_emit_common.cpp's lidlTypeToQt —
and they disagreed. The legacy one had no `void` case, so a `-> void` method
arriving as Primitive("void") from the impl-header parser fell through to
QVariant. (The .lidl parser spells it Named("void"), which survived only by
accident, through mapReturnType's `base == "void"` early-out.)
That was not a Qt-consumer bug: the std/lp tables are DERIVED from this name, so
the same method generated `LogosMap doVoid(...)` on the Qt-free surface too.
Measured, from `void doVoid();` in a .h interface:
QVariant doVoid(...) --api-style qt before
void doVoid(...) --api-style qt after
LogosMap doVoid(...) --api-style lp before
void doVoid(...) --api-style lp after
lidlTypeExprToQtTypeName is now a delegation, so there is one table to disagree
with. This changes the generated signature for any module consuming a `-> void`
method through a .h interface; the two in-tree call sites discard the value and
are unaffected.
logos::LpClient::invoke/invokeAsync gain a timeout_ms parameter, defaulted to
the C ABI's "use the default" (0) so no existing caller changes. The Qt-typed
consumer surface takes a Timeout on every async overload and had nowhere to put
it — a wrapper delegating to the lp path silently dropped it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(generator): drop the dead event-source surface from consumer wrappers
Generated consumer wrappers carried setEventSource / eventSource / trigger — an
author-facing way to SOURCE events through a wrapper whose job is to CONSUME
them. Both emitters (legacy and experimental) shipped it.
Nothing used it. Zero call sites across every repo in the workspace including
the vendored SDK copies; the only `trigger(` in the tree is a QML Action's own
method. The generated code did not use it internally either — m_eventSource was
written only by its own setter and read only by trigger, so calling trigger()
without a prior setEventSource() warned and returned.
It was not free. `trigger` routes through m_client->onEventResponse, which has
no lp equivalent — lp_* offers only lp_provider_emit_event, on a handle a
consumer wrapper does not own. That single call was the reason a Qt wrapper had
to keep a LogosAPIClient alongside its lp client, carrying two clients and two
lots of token state per wrapper. Removing an unused surface removes a real
constraint on the veneer.
Worth noting what it would have taken otherwise: either widening the C ABI with
a consumer-side emit (softening a provider/consumer split the ABI currently
enforces), or rerouting through the module's own provider handle. Neither is
needed if nobody is asking.
Pinned by a test rather than left to convention — the emitters are the kind of
code where a convenience accessor grows back.
181/181.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
498 lines
23 KiB
C++
498 lines
23 KiB
C++
#include "lidl_gen_client.h"
|
|
#include "lidl_emit_common.h"
|
|
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QTextStream>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
static bool isRefType(const QString& qt)
|
|
{
|
|
if (qt == "QString" || qt == "QStringList" || qt == "QJsonArray"
|
|
|| qt == "QVariantList" || qt == "QVariantMap" || qt == "QByteArray")
|
|
return true;
|
|
// A record is a struct: pass it by const& too. Anything that is not a known
|
|
// Qt scalar/handle spelling is a generated record type.
|
|
return !(qt == "bool" || qt == "int" || qt == "double" || qt == "float"
|
|
|| qt == "void" || qt == "qlonglong" || qt == "qulonglong"
|
|
|| qt == "QVariant" || qt == "LogosResult");
|
|
}
|
|
|
|
static void emitParam(QTextStream& s, const QString& qtType, const std::string& name)
|
|
{
|
|
if (isRefType(qtType))
|
|
s << "const " << qtType << "& " << name;
|
|
else
|
|
s << qtType << " " << name;
|
|
}
|
|
|
|
static bool lidlIsRecord(const TypeExpr& te);
|
|
static QString qtToVariantExpr(const TypeExpr& te, const QString& expr);
|
|
static QString qtFromVariantExpr(const TypeExpr& te, const QString& expr);
|
|
static QString returnConversionFor(const TypeExpr& te, const QString& qt);
|
|
|
|
static QString returnConversion(const QString& qt)
|
|
{
|
|
if (qt == "bool") return "return _result.toBool();";
|
|
// 64-bit, matching lidlTypeToQt: toInt() truncated a LIDL int/uint, and for
|
|
// uint it also read the value as signed.
|
|
if (qt == "qlonglong") return "return _result.toLongLong();";
|
|
if (qt == "qulonglong") return "return _result.toULongLong();";
|
|
if (qt == "double") return "return _result.toDouble();";
|
|
if (qt == "float") return "return _result.toFloat();";
|
|
if (qt == "QString") return "return _result.toString();";
|
|
if (qt == "QStringList") return "return _result.toStringList();";
|
|
if (qt == "QJsonArray") return "return qvariant_cast<QJsonArray>(_result);";
|
|
if (qt == "QVariantList") return "return _result.toList();";
|
|
if (qt == "QVariantMap") return "return _result.toMap();";
|
|
if (qt == "LogosResult") return "return _result.value<LogosResult>();";
|
|
return "return _result;";
|
|
}
|
|
|
|
// Records (and containers holding them) decode through the generated
|
|
// conversions; everything else keeps the historical QVariant accessor.
|
|
static QString returnConversionFor(const TypeExpr& te, const QString& qt)
|
|
{
|
|
const bool holdsRecord =
|
|
lidlIsRecord(te)
|
|
|| (te.kind == TypeExpr::Array && te.elements.size() == 1 && lidlIsRecord(te.elements[0]))
|
|
|| (te.kind == TypeExpr::Map && te.elements.size() == 2 && lidlIsRecord(te.elements[1]));
|
|
if (holdsRecord)
|
|
return "return " + qtFromVariantExpr(te, "_result") + ";";
|
|
return returnConversion(qt);
|
|
}
|
|
|
|
// The async twin of returnConversionFor: `v` is the wire QVariant.
|
|
//
|
|
// A record-bearing return MUST decode field by field here too. The wire carries
|
|
// a QVariantMap and no Q_DECLARE_METATYPE is emitted for the struct, so
|
|
// `qvariant_cast<Status>(v)` does not fail — it silently returns a
|
|
// DEFAULT-CONSTRUCTED Status, and the caller sees empty fields with no
|
|
// diagnostic. That is the worst failure mode available: the sync path is
|
|
// correct, so the same call is right or wrong depending only on which overload
|
|
// the caller reached for.
|
|
static QString asyncReturnConversionFor(const TypeExpr& te, const QString& qt)
|
|
{
|
|
const bool holdsRecord =
|
|
lidlIsRecord(te)
|
|
|| (te.kind == TypeExpr::Array && te.elements.size() == 1 && lidlIsRecord(te.elements[0]))
|
|
|| (te.kind == TypeExpr::Map && te.elements.size() == 2 && lidlIsRecord(te.elements[1]));
|
|
if (holdsRecord)
|
|
return qtFromVariantExpr(te, "v");
|
|
return "qvariant_cast<" + qt + ">(v)";
|
|
}
|
|
|
|
static QString asyncDefaultVal(const QString& qt)
|
|
{
|
|
if (qt == "bool") return "false";
|
|
if (qt == "int" || qt == "double" || qt == "float") return "0";
|
|
if (qt == "QString") return "QString()";
|
|
if (qt == "QStringList") return "QStringList()";
|
|
if (qt == "QJsonArray") return "QJsonArray()";
|
|
if (qt == "QVariantList") return "QVariantList()";
|
|
if (qt == "QVariantMap") return "QVariantMap()";
|
|
return qt + "{}";
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Records
|
|
//
|
|
// A `type Foo { … }` in the contract becomes a real C++ struct plus two inline
|
|
// conversions, so a Qt consumer says `Status s = client.makeStatus();` instead
|
|
// of digging fields out of a QVariantMap. One LIDL type, one type per language.
|
|
//
|
|
// bstr fields are QByteArray on purpose: logos-protocol's QVariant<->JSON
|
|
// conversion already materialises the canonical {"_bytes": base64url} form as a
|
|
// QByteArray and back (logos_json_convert.cpp), so the record conversions stay
|
|
// pure field mapping and binary survives at any depth for free.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static bool lidlIsRecord(const TypeExpr& te)
|
|
{
|
|
return te.kind == TypeExpr::Named && !te.name.empty();
|
|
}
|
|
|
|
// value expression of the Qt type -> QVariant
|
|
static QString qtToVariantExpr(const TypeExpr& te, const QString& expr)
|
|
{
|
|
if (lidlIsRecord(te))
|
|
return qs(te.name) + "ToVariant(" + expr + ")";
|
|
if (te.kind == TypeExpr::Array && te.elements.size() == 1
|
|
&& (lidlIsRecord(te.elements[0]) || te.elements[0].kind != TypeExpr::Primitive)) {
|
|
return "[&]{ QVariantList __l; for (const auto& __e : " + expr + ") __l.append("
|
|
+ qtToVariantExpr(te.elements[0], "__e") + "); return QVariant(__l); }()";
|
|
}
|
|
if (te.kind == TypeExpr::Map && te.elements.size() == 2
|
|
&& (lidlIsRecord(te.elements[1]) || te.elements[1].kind != TypeExpr::Primitive)) {
|
|
return "[&]{ QVariantMap __m; for (auto __it = " + expr + ".begin(); __it != " + expr
|
|
+ ".end(); ++__it) __m.insert(__it.key(), "
|
|
+ qtToVariantExpr(te.elements[1], "__it.value()") + "); return QVariant(__m); }()";
|
|
}
|
|
return "QVariant::fromValue(" + expr + ")";
|
|
}
|
|
|
|
// QVariant expression -> value of the Qt type
|
|
static QString qtFromVariantExpr(const TypeExpr& te, const QString& expr)
|
|
{
|
|
if (lidlIsRecord(te))
|
|
return qs(te.name) + "FromVariant(" + expr + ")";
|
|
if (te.kind == TypeExpr::Primitive) {
|
|
const QString n = qs(te.name);
|
|
if (n == "tstr") return expr + ".toString()";
|
|
if (n == "bstr") return expr + ".toByteArray()";
|
|
if (n == "int") return expr + ".toLongLong()";
|
|
if (n == "uint") return expr + ".toULongLong()";
|
|
if (n == "float64") return expr + ".toDouble()";
|
|
if (n == "bool") return expr + ".toBool()";
|
|
}
|
|
if (te.kind == TypeExpr::Array && te.elements.size() == 1) {
|
|
const TypeExpr& e = te.elements[0];
|
|
return "[&]{ " + lidlTypeToQt(te) + " __acc; for (const QVariant& __e : " + expr
|
|
+ ".toList()) __acc.append(" + qtFromVariantExpr(e, "__e") + "); return __acc; }()";
|
|
}
|
|
if (te.kind == TypeExpr::Map && te.elements.size() == 2) {
|
|
const TypeExpr& v = te.elements[1];
|
|
return "[&]{ " + lidlTypeToQt(te) + " __acc; const QVariantMap __mm = " + expr
|
|
+ ".toMap(); for (auto __it = __mm.begin(); __it != __mm.end(); ++__it) __acc.insert("
|
|
+ "__it.key(), " + qtFromVariantExpr(v, "__it.value()") + "); return __acc; }()";
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
// A method argument as passed to packVariantList: records convert, everything
|
|
// else goes through unchanged (packVariantList wraps with QVariant::fromValue).
|
|
static QString qtArgExpr(const TypeExpr& te, const QString& name)
|
|
{
|
|
const bool holdsRecord =
|
|
lidlIsRecord(te)
|
|
|| (te.kind == TypeExpr::Array && te.elements.size() == 1 && lidlIsRecord(te.elements[0]))
|
|
|| (te.kind == TypeExpr::Map && te.elements.size() == 2 && lidlIsRecord(te.elements[1]));
|
|
return holdsRecord ? qtToVariantExpr(te, name) : name;
|
|
}
|
|
|
|
static void emitRecords(QTextStream& s, const ModuleDecl& module)
|
|
{
|
|
if (module.types.empty()) return;
|
|
for (const TypeDecl& t : module.types) {
|
|
const QString n = qs(t.name);
|
|
s << "/// `" << n << "` — a record declared by the `" << qs(module.name) << "` contract.\n";
|
|
s << "struct " << n << " {\n";
|
|
for (const FieldDecl& f : t.fields)
|
|
s << " " << lidlTypeToQt(f.type) << " " << qs(f.name) << "{};\n";
|
|
s << "};\n\n";
|
|
}
|
|
// Conversions come after ALL structs so records may reference each other.
|
|
for (const TypeDecl& t : module.types) {
|
|
const QString n = qs(t.name);
|
|
s << "inline QVariant " << n << "ToVariant(const " << n << "& v)\n{\n";
|
|
s << " QVariantMap __m;\n";
|
|
for (const FieldDecl& f : t.fields)
|
|
s << " __m.insert(\"" << qs(f.name) << "\", "
|
|
<< qtToVariantExpr(f.type, "v." + qs(f.name)) << ");\n";
|
|
s << " return QVariant(__m);\n}\n\n";
|
|
|
|
s << "inline " << n << " " << n << "FromVariant(const QVariant& value)\n{\n";
|
|
s << " const QVariantMap __m = value.toMap();\n";
|
|
s << " " << n << " __out;\n";
|
|
for (const FieldDecl& f : t.fields)
|
|
s << " __out." << qs(f.name) << " = "
|
|
<< qtFromVariantExpr(f.type, "__m.value(\"" + qs(f.name) + "\")") << ";\n";
|
|
s << " return __out;\n}\n\n";
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Header generation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode)
|
|
{
|
|
QString className = lidlToPascalCase(qs(module.name));
|
|
QString h;
|
|
QTextStream s(&h);
|
|
|
|
s << "#pragma once\n";
|
|
s << "#include <QString>\n";
|
|
s << "#include <QVariant>\n";
|
|
s << "#include <QStringList>\n";
|
|
s << "#include <QJsonArray>\n";
|
|
s << "#include <QVariantList>\n";
|
|
s << "#include <QVariantMap>\n";
|
|
s << "#include <functional>\n";
|
|
s << "#include <utility>\n";
|
|
s << "#include \"logos_types.h\"\n";
|
|
s << "#include \"logos_api.h\"\n";
|
|
s << "#include \"logos_api_client.h\"\n";
|
|
s << "#include \"logos_call_error.h\"\n";
|
|
s << "#include \"logos_object.h\"\n\n";
|
|
|
|
emitRecords(s, module);
|
|
|
|
s << "class " << className << " {\n";
|
|
s << "public:\n";
|
|
if (bindMode == BindMode::Bound)
|
|
s << " explicit " << className << "(LogosAPI* api, const QString& moduleName);\n\n";
|
|
else
|
|
s << " explicit " << className << "(LogosAPI* api);\n\n";
|
|
|
|
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
|
|
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
|
|
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
|
|
s << " bool on(const QString& eventName, EventCallback callback);\n";
|
|
|
|
for (const MethodDecl& md : module.methods) {
|
|
QString ret = lidlTypeToQt(md.returnType);
|
|
s << " " << ret << " " << md.name << "(";
|
|
for (int i = 0; i < md.params.size(); ++i) {
|
|
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
|
|
if (i + 1 < md.params.size()) s << ", ";
|
|
}
|
|
// Optional error out-channel: pass a logos::CallError* to distinguish
|
|
// a failed remote call from a legitimately default-valued result.
|
|
if (!md.params.empty()) s << ", ";
|
|
s << "logos::CallError* err = nullptr);\n";
|
|
QString asyncCb = (ret == "void")
|
|
? QString("std::function<void()>")
|
|
: QString("std::function<void(") + ret + ")>";
|
|
s << " void " << md.name << "Async(";
|
|
for (int i = 0; i < md.params.size(); ++i) {
|
|
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
|
|
if (i + 1 < md.params.size()) s << ", ";
|
|
}
|
|
if (!md.params.empty()) s << ", ";
|
|
s << asyncCb << " callback, Timeout timeout = Timeout());\n";
|
|
}
|
|
|
|
s << "\nprivate:\n";
|
|
s << " LogosObject* ensureReplica();\n";
|
|
s << " template<typename... Args>\n";
|
|
s << " static QVariantList packVariantList(Args&&... args) {\n";
|
|
s << " QVariantList list;\n";
|
|
s << " list.reserve(sizeof...(Args));\n";
|
|
s << " using Expander = int[];\n";
|
|
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
|
|
s << " return list;\n";
|
|
s << " }\n";
|
|
s << " LogosAPI* m_api;\n";
|
|
s << " LogosAPIClient* m_client;\n";
|
|
s << " QString m_moduleName;\n";
|
|
s << " LogosObject* m_eventReplica = nullptr;\n";
|
|
s << "};\n";
|
|
|
|
return h;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Source generation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode)
|
|
{
|
|
QString className = lidlToPascalCase(qs(module.name));
|
|
QString headerRel = qs(module.name) + "_api.h";
|
|
QString c;
|
|
QTextStream s(&c);
|
|
|
|
s << "#include \"" << headerRel << "\"\n\n";
|
|
s << "#include <QDebug>\n\n";
|
|
|
|
// Target expression for every remote call: a baked literal in Static
|
|
// mode, the runtime m_moduleName member in Bound (interface) mode.
|
|
const QString targetExpr = (bindMode == BindMode::Bound)
|
|
? QStringLiteral("m_moduleName")
|
|
: (QStringLiteral("\"") + qs(module.name) + QStringLiteral("\""));
|
|
if (bindMode == BindMode::Bound)
|
|
s << className << "::" << className << "(LogosAPI* api, const QString& moduleName) : m_api(api), m_client(api->getClient(moduleName)), m_moduleName(moduleName) {}\n\n";
|
|
else
|
|
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\""
|
|
<< module.name << "\")), m_moduleName(QStringLiteral(\"" << module.name << "\")) {}\n\n";
|
|
|
|
s << "LogosObject* " << className << "::ensureReplica() {\n";
|
|
s << " if (!m_eventReplica) {\n";
|
|
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
|
|
s << " if (!replica) {\n";
|
|
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
|
|
s << " return nullptr;\n";
|
|
s << " }\n";
|
|
s << " m_eventReplica = replica;\n";
|
|
s << " }\n";
|
|
s << " return m_eventReplica;\n";
|
|
s << "}\n\n";
|
|
|
|
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
|
|
s << " if (!callback) { qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName; return false; }\n";
|
|
s << " LogosObject* origin = ensureReplica();\n";
|
|
s << " if (!origin) return false;\n";
|
|
s << " m_client->onEvent(origin, eventName, callback);\n";
|
|
s << " return true;\n";
|
|
s << "}\n\n";
|
|
|
|
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
|
|
s << " if (!callback) { qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName; return false; }\n";
|
|
s << " return on(eventName, [callback](const QString&, const QVariantList& data) { callback(data); });\n";
|
|
s << "}\n\n";
|
|
|
|
|
|
|
|
for (const MethodDecl& md : module.methods) {
|
|
QString ret = lidlTypeToQt(md.returnType);
|
|
int nParams = md.params.size();
|
|
|
|
s << ret << " " << className << "::" << md.name << "(";
|
|
for (int i = 0; i < nParams; ++i) {
|
|
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
|
|
if (i + 1 < nParams) s << ", ";
|
|
}
|
|
if (nParams > 0) s << ", ";
|
|
s << "logos::CallError* err) {\n";
|
|
|
|
// Call through the err-out overload: with a logos::CallError* the
|
|
// caller can distinguish a failed remote call from a legitimately
|
|
// default-valued result; without it the historical default-on-failure
|
|
// behavior is kept, plus a warning in the module log.
|
|
s << " logos::CallError _err;\n";
|
|
if (ret != "void") s << " QVariant _result = ";
|
|
else s << " ";
|
|
|
|
// Pack each argument as ONE element via packVariantList (which wraps
|
|
// with QVariant::fromValue). A braced `QVariantList{v}` or `<< v` would
|
|
// CONCATENATE a QVariantList-typed arg (any `[T]` list) into the args
|
|
// list, sending a 3-element [1,2,3] as three positional args instead of
|
|
// one — the historical "typed arrays empty over the Qt path" bug.
|
|
s << "m_client->invokeRemoteMethod(" << targetExpr << ", \"" << md.name << "\", packVariantList(";
|
|
for (int i = 0; i < nParams; ++i) {
|
|
s << qtArgExpr(md.params[i].type, qs(md.params[i].name));
|
|
if (i + 1 < nParams) s << ", ";
|
|
}
|
|
s << "), Timeout(), &_err);\n";
|
|
s << " if (err) *err = _err;\n";
|
|
s << " else if (!_err.ok()) qWarning() << \"" << className << "::" << md.name
|
|
<< ": remote call failed:\" << QString::fromStdString(_err.message);\n";
|
|
|
|
if (ret != "void")
|
|
s << " " << returnConversionFor(md.returnType, ret) << "\n";
|
|
s << "}\n\n";
|
|
|
|
s << "void " << className << "::" << md.name << "Async(";
|
|
for (int i = 0; i < nParams; ++i) {
|
|
emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name);
|
|
if (i + 1 < nParams) s << ", ";
|
|
}
|
|
if (nParams > 0) s << ", ";
|
|
s << "std::function<void(" << (ret == "void" ? "void" : ret) << ")> callback, Timeout timeout) {\n";
|
|
s << " if (!callback) return;\n";
|
|
// Same one-element-per-arg packing as the sync path (see above): a
|
|
// QVariantList-typed arg must not be spread across the args list.
|
|
s << " m_client->invokeRemoteMethodAsync(" << targetExpr << ", \"" << md.name << "\", packVariantList(";
|
|
for (int i = 0; i < nParams; ++i) {
|
|
s << qtArgExpr(md.params[i].type, qs(md.params[i].name));
|
|
if (i + 1 < nParams) s << ", ";
|
|
}
|
|
s << ")";
|
|
s << ", [callback](QVariant v) {\n";
|
|
if (ret == "void") {
|
|
s << " callback();\n";
|
|
} else if (ret == "QVariant") {
|
|
s << " callback(v);\n";
|
|
} else {
|
|
s << " callback(v.isValid() ? " << asyncReturnConversionFor(md.returnType, ret)
|
|
<< " : " << asyncDefaultVal(ret) << ");\n";
|
|
}
|
|
s << " }, timeout);\n";
|
|
s << "}\n\n";
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// metadata.json
|
|
// ---------------------------------------------------------------------------
|
|
|
|
QString lidlGenerateMetadataJson(const ModuleDecl& module)
|
|
{
|
|
QJsonObject obj;
|
|
obj["name"] = qs(module.name);
|
|
obj["version"] = module.version.empty() ? QStringLiteral("0.0.0") : qs(module.version);
|
|
obj["type"] = "core";
|
|
obj["category"] = module.category.empty() ? QStringLiteral("general") : qs(module.category);
|
|
obj["description"] = qs(module.description);
|
|
obj["main"] = qs(module.name) + "_plugin";
|
|
QJsonArray deps;
|
|
for (const std::string& d : module.depends)
|
|
deps.append(qs(d));
|
|
obj["dependencies"] = deps;
|
|
QJsonDocument doc(obj);
|
|
return doc.toJson(QJsonDocument::Indented);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Full pipeline (from .lidl file)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
int lidlGenerateClientStubs(const QString& lidlPath, const QString& outputDir,
|
|
bool moduleOnly, QTextStream& out, QTextStream& err)
|
|
{
|
|
QFileInfo fi(lidlPath);
|
|
if (!fi.exists()) { err << "LIDL file does not exist: " << lidlPath << "\n"; return 2; }
|
|
QFile file(fi.canonicalFilePath().isEmpty() ? fi.absoluteFilePath() : fi.canonicalFilePath());
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { err << "Failed to open LIDL file: " << lidlPath << "\n"; return 3; }
|
|
QString source = QString::fromUtf8(file.readAll());
|
|
file.close();
|
|
|
|
LidlParseResult pr = lidlParse(source);
|
|
if (pr.hasError()) { err << lidlPath << ":" << pr.errorLine << ":" << pr.errorColumn << ": " << pr.error << "\n"; return 4; }
|
|
|
|
LidlValidationResult vr = lidlValidate(pr.module);
|
|
if (vr.hasErrors()) { for (const std::string& e : vr.errors) err << lidlPath << ": " << e << "\n"; return 5; }
|
|
{
|
|
QString recErr;
|
|
if (!lidlCheckRecords(pr.module, &recErr)) { err << lidlPath << ": " << recErr << "\n"; return 5; }
|
|
}
|
|
|
|
const ModuleDecl& mod = pr.module;
|
|
QString genDirPath = outputDir.isEmpty() ? QDir::current().filePath("logos-cpp-sdk/cpp/generated") : outputDir;
|
|
QDir().mkpath(genDirPath);
|
|
|
|
QString headerAbs = QDir(genDirPath).filePath(qs(mod.name) + "_api.h");
|
|
QString sourceAbs = QDir(genDirPath).filePath(qs(mod.name) + "_api.cpp");
|
|
|
|
{ QFile f(headerAbs); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { err << "Failed to write: " << headerAbs << "\n"; return 6; } f.write(lidlMakeHeader(mod).toUtf8()); }
|
|
{ QFile f(sourceAbs); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { err << "Failed to write: " << sourceAbs << "\n"; return 7; } f.write(lidlMakeSource(mod).toUtf8()); }
|
|
{ QString metaPath = QDir(genDirPath).filePath("metadata.json"); QFile f(metaPath); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { err << "Failed to write: " << metaPath << "\n"; return 8; } f.write(lidlGenerateMetadataJson(mod).toUtf8()); }
|
|
|
|
out << "Generated: " << headerAbs << " and " << sourceAbs << "\n";
|
|
|
|
if (!moduleOnly) {
|
|
QDir genDir(genDirPath);
|
|
QStringList headers = genDir.entryList(QStringList() << "*_api.h", QDir::Files | QDir::Readable);
|
|
{ QString content; QTextStream ss(&content);
|
|
ss << "#pragma once\n#include \"logos_api.h\"\n#include \"logos_api_client.h\"\n\n";
|
|
for (const QString& h : headers) ss << "#include \"" << h << "\"\n";
|
|
ss << "\nstruct LogosModules {\n explicit LogosModules(LogosAPI* api) : api(api)";
|
|
for (const QString& h : headers) { QString base = h; base.chop(6); ss << ", \n " << base << "(api)"; }
|
|
ss << " {}\n LogosAPI* api;\n";
|
|
for (const QString& h : headers) { QString base = h; base.chop(6); ss << " " << lidlToPascalCase(base) << " " << base << ";\n"; }
|
|
ss << "};\n";
|
|
QFile f(genDir.filePath("logos_sdk.h")); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { err << "Failed to write umbrella\n"; return 9; } f.write(content.toUtf8()); }
|
|
{ QStringList sources = genDir.entryList(QStringList() << "*_api.cpp", QDir::Files | QDir::Readable);
|
|
QString content; QTextStream ss(&content); ss << "#include \"logos_sdk.h\"\n\n";
|
|
for (const QString& c : sources) ss << "#include \"" << c << "\"\n"; ss << "\n";
|
|
QFile f(genDir.filePath("logos_sdk.cpp")); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { err << "Failed to write umbrella\n"; return 10; } f.write(content.toUtf8()); }
|
|
out << "Generated: logos_sdk.h and logos_sdk.cpp\n";
|
|
}
|
|
out << "Generated: metadata.json\n";
|
|
out.flush();
|
|
return 0;
|
|
}
|