diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8d1bd90..8feffe9 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(LogosSDK) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) @@ -51,6 +51,20 @@ set(SDK_SOURCES implementations/mock/mock_transport.h implementations/mock/mock_registry.h implementations/mock/logos_mock.h + native/logos_value.cpp + native/logos_value.h + native/logos_value_qt.cpp + native/logos_value_qt.h + native/logos_native_types.h + native/logos_native_provider.cpp + native/logos_native_provider.h + native/logos_native_client.cpp + native/logos_native_client.h + native/logos_native_api.cpp + native/logos_native_api.h + native/logos_native_adapter.cpp + native/logos_native_adapter.h + native/logos_macros.h ) # Create the SDK library as STATIC instead of SHARED @@ -62,6 +76,7 @@ target_link_libraries(logos_sdk PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSIO # Include directories target_include_directories(logos_sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/native ${CMAKE_CURRENT_SOURCE_DIR}/implementations/qt_local ${CMAKE_CURRENT_SOURCE_DIR}/implementations/qt_remote ${CMAKE_CURRENT_SOURCE_DIR}/implementations/mock @@ -119,3 +134,14 @@ install(FILES implementations/mock/logos_mock.h DESTINATION include/implementations/mock ) + +install(FILES + native/logos_value.h + native/logos_native_types.h + native/logos_native_provider.h + native/logos_native_client.h + native/logos_native_api.h + native/logos_native_adapter.h + native/logos_macros.h + DESTINATION include/native +) diff --git a/cpp/logos_api_client.h b/cpp/logos_api_client.h index d89732e..2a1f2e9 100644 --- a/cpp/logos_api_client.h +++ b/cpp/logos_api_client.h @@ -38,6 +38,8 @@ public: QString registryUrl() const; bool reconnect(); + // DEPRECATED: Use NativeLogosClient::invokeMethod() for native modules. + // These QVariant-based overloads remain for Q_INVOKABLE and LogosProviderBase modules. QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariantList& args = QVariantList(), Timeout timeout = Timeout()); @@ -58,21 +60,11 @@ public: const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, const QVariant& arg4, const QVariant& arg5, Timeout timeout = Timeout()); - /** - * @brief Register an event listener via LogosObject's callback mechanism - * @param originObject The LogosObject that will emit the event - * @param eventName The name of the event to listen for - * @param callback Function to call when the event is triggered - */ + // DEPRECATED: Use NativeLogosClient::onEvent() for native modules. void onEvent(LogosObject* originObject, const QString& eventName, std::function callback); - /** - * @brief Emit an event on a LogosObject (for plugins that act as event sources) - * @param object The LogosObject to emit the event on - * @param eventName The name of the event - * @param data The event data - */ + // DEPRECATED: Use NativeLogosClient::onEventResponse() for native modules. void onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data); /** diff --git a/cpp/logos_api_provider.cpp b/cpp/logos_api_provider.cpp index 883133a..38ca3e0 100644 --- a/cpp/logos_api_provider.cpp +++ b/cpp/logos_api_provider.cpp @@ -1,6 +1,8 @@ #include "logos_api_provider.h" #include "logos_object.h" #include "logos_provider_object.h" +#include "native/logos_native_provider.h" +#include "native/logos_native_adapter.h" #include "qt_provider_object.h" #include "module_proxy.h" #include "logos_api.h" @@ -43,19 +45,31 @@ bool LogosAPIProvider::registerObject(const QString& name, QObject* object) return false; } - // Check if this plugin implements LogosProviderPlugin (new API) + // Check for NativeProviderPlugin first (new native API) + NativeProviderPlugin* nativePlugin = qobject_cast(object); + if (nativePlugin) { + qDebug() << "[NATIVE API] Module" << name << "uses NativeProviderPlugin (Qt-free native types)"; + NativeProviderObject* native = nativePlugin->createNativeProviderObject(); + if (native) { + auto* adapter = new NativeProviderAdapter(native); + return registerObject(name, adapter); + } + qWarning() << "[NATIVE API] Module" << name << ": createNativeProviderObject() returned null"; + } + + // Check if this plugin implements LogosProviderPlugin (Qt-typed new API) LogosProviderPlugin* providerPlugin = qobject_cast(object); if (providerPlugin) { - qDebug() << "[LogosProviderObject] LogosAPIProvider: detected LogosProviderPlugin for" << name; + qDebug() << "[QT API] Module" << name << "uses LogosProviderPlugin (DEPRECATED — Qt-typed provider)"; LogosProviderObject* provider = providerPlugin->createProviderObject(); if (provider) { return registerObject(name, provider); } - qWarning() << "LogosAPIProvider: createProviderObject() returned null for" << name; + qWarning() << "[QT API] Module" << name << ": createProviderObject() returned null"; } // Legacy path: wrap QObject in QtProviderObject adapter - qDebug() << "[LogosProviderObject] LogosAPIProvider: wrapping QObject in QtProviderObject for" << name; + qDebug() << "[QT API] Module" << name << "uses Q_INVOKABLE (DEPRECATED — legacy QObject path)"; m_qtProviderObject = new QtProviderObject(object, this); m_qtProviderObject->init(qobject_cast(parent())); @@ -81,7 +95,7 @@ bool LogosAPIProvider::registerObject(const QString& name, LogosProviderObject* return false; } - qDebug() << "[LogosProviderObject] LogosAPIProvider: registering LogosProviderObject directly for" << name; + qDebug() << "LogosAPIProvider: registering LogosProviderObject directly for" << name; provider->init(qobject_cast(parent())); @@ -95,7 +109,7 @@ bool LogosAPIProvider::publishProvider(const QString& name, LogosProviderObject* bool success = m_transport->publishObject(name, m_moduleProxy); if (success) { m_registeredObjectName = name; - qDebug() << "[LogosProviderObject] LogosAPIProvider: successfully published" << name; + qDebug() << "LogosAPIProvider: successfully published" << name; } else { qCritical() << "LogosAPIProvider: Failed to publish" << name; } diff --git a/cpp/logos_provider_object.cpp b/cpp/logos_provider_object.cpp index d8b18b5..88008cc 100644 --- a/cpp/logos_provider_object.cpp +++ b/cpp/logos_provider_object.cpp @@ -6,24 +6,24 @@ void LogosProviderBase::init(void* apiInstance) { m_logosAPI = static_cast(apiInstance); - qDebug() << "[LogosProviderObject] LogosProviderBase::init called"; + qDebug() << "[QT API] LogosProviderBase::init — module initialized with Qt types (DEPRECATED — migrate to NativeProviderBase)"; onInit(m_logosAPI); } bool LogosProviderBase::informModuleToken(const QString& moduleName, const QString& token) { if (!m_logosAPI) { - qWarning() << "[LogosProviderObject] informModuleToken: LogosAPI not available"; + qWarning() << "[QT API] LogosProviderBase::informModuleToken: LogosAPI not available"; return false; } TokenManager* tokenManager = m_logosAPI->getTokenManager(); if (!tokenManager) { - qWarning() << "[LogosProviderObject] informModuleToken: TokenManager not available"; + qWarning() << "[QT API] LogosProviderBase::informModuleToken: TokenManager not available"; return false; } - qDebug() << "[LogosProviderObject] Saving token for module:" << moduleName; + qDebug() << "[QT API] LogosProviderBase: saving token for module:" << moduleName; tokenManager->saveToken(moduleName, token); return true; } @@ -31,9 +31,9 @@ bool LogosProviderBase::informModuleToken(const QString& moduleName, const QStri void LogosProviderBase::emitEvent(const QString& eventName, const QVariantList& data) { if (m_eventCallback) { - qDebug() << "[LogosProviderObject] emitEvent:" << eventName; + qDebug() << "[QT API] LogosProviderBase::emitEvent:" << eventName; m_eventCallback(eventName, data); } else { - qWarning() << "[LogosProviderObject] emitEvent: no listener set for" << eventName; + qWarning() << "[QT API] LogosProviderBase::emitEvent: no listener set for" << eventName; } } diff --git a/cpp/logos_provider_object.h b/cpp/logos_provider_object.h index 4a5f96f..541a6cf 100644 --- a/cpp/logos_provider_object.h +++ b/cpp/logos_provider_object.h @@ -32,13 +32,14 @@ public: }; // --------------------------------------------------------------------------- -// LogosProviderBase — convenience base class for new-API modules +// LogosProviderBase — convenience base class for Qt-typed LOGOS_METHOD modules // -// Handles framework plumbing so the developer only writes business logic. -// callMethod() and getMethods() are provided by generated code produced -// by logos-cpp-generator --provider-header (analogous to Qt MOC). +// DEPRECATED: Use NativeProviderBase (in native/logos_native_provider.h) for +// new modules. NativeProviderBase provides the same functionality with Qt-free +// types (std::string, LogosValue, NativeLogosResult). // --------------------------------------------------------------------------- -class LogosProviderBase : public LogosProviderObject { +class [[deprecated("Use NativeProviderBase for new modules — see native/logos_native_provider.h")]] +LogosProviderBase : public LogosProviderObject { public: // These two are implemented by generated code (logos_provider_dispatch.cpp): // QVariant callMethod(const QString& methodName, const QVariantList& args) override; @@ -78,7 +79,8 @@ Q_DECLARE_INTERFACE(LogosProviderPlugin, LogosProviderPlugin_iid) // --------------------------------------------------------------------------- // LOGOS_PROVIDER: declares providerName/providerVersion and a private typedef. -// Place at the top of the class body (like Q_OBJECT). +// DEPRECATED: Use NATIVE_LOGOS_PROVIDER (in native/logos_native_provider.h) for +// new modules to avoid Qt type dependencies. #define LOGOS_PROVIDER(ClassName, Name, Version) \ public: \ QString providerName() const override { return Name; } \ @@ -88,9 +90,7 @@ public: \ private: \ using _LogosProviderThisType = ClassName; -// LOGOS_METHOD: marks a method as callable by the framework. -// Expands to nothing — scanned by logos-cpp-generator to produce -// callMethod() dispatch and getMethods() metadata (like Q_INVOKABLE + MOC). -#define LOGOS_METHOD +// LOGOS_METHOD macro — shared definition in native/logos_macros.h +#include "native/logos_macros.h" #endif // LOGOS_PROVIDER_OBJECT_H diff --git a/cpp/logos_types.h b/cpp/logos_types.h index f89928f..197fced 100644 --- a/cpp/logos_types.h +++ b/cpp/logos_types.h @@ -11,6 +11,9 @@ public: using std::runtime_error::runtime_error; }; +// DEPRECATED: Use NativeLogosResult (in native/logos_native_types.h) for new +// modules using NativeProviderBase. This Qt-based LogosResult remains for the +// Q_INVOKABLE and LogosProviderBase paths. struct LogosResult { bool success; diff --git a/cpp/native/logos_macros.h b/cpp/native/logos_macros.h new file mode 100644 index 0000000..9d4d0be --- /dev/null +++ b/cpp/native/logos_macros.h @@ -0,0 +1,9 @@ +#ifndef LOGOS_MACROS_H +#define LOGOS_MACROS_H + +// LOGOS_METHOD: marks a method as callable by the framework. +// Expands to nothing — scanned by code generators to produce +// callMethod() dispatch and getMethods() metadata. +#define LOGOS_METHOD + +#endif // LOGOS_MACROS_H diff --git a/cpp/native/logos_native_adapter.cpp b/cpp/native/logos_native_adapter.cpp new file mode 100644 index 0000000..ec749a8 --- /dev/null +++ b/cpp/native/logos_native_adapter.cpp @@ -0,0 +1,58 @@ +#include "logos_native_adapter.h" +#include "logos_native_provider.h" +#include "logos_value_qt.h" + +#include +#include + +NativeProviderAdapter::NativeProviderAdapter(NativeProviderObject* native) + : m_native(native) +{ +} + +QVariant NativeProviderAdapter::callMethod(const QString& methodName, const QVariantList& args) +{ + qDebug() << "[NATIVE API] NativeProviderAdapter: dispatching" << methodName + << "with" << args.size() << "args (QVariant→LogosValue→native→LogosValue→QVariant)"; + std::string nativeName = methodName.toStdString(); + std::vector nativeArgs = logosValueListFromQVariantList(args); + LogosValue result = m_native->callMethod(nativeName, nativeArgs); + return logosValueToQVariant(result); +} + +QJsonArray NativeProviderAdapter::getMethods() +{ + std::string json = m_native->getMethodsJson(); + QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(json)); + if (doc.isArray()) + return doc.array(); + return QJsonArray(); +} + +void NativeProviderAdapter::setEventListener(EventCallback callback) +{ + m_native->setEventListener( + [callback](const std::string& eventName, const std::vector& data) { + callback(QString::fromStdString(eventName), logosValueListToQVariantList(data)); + }); +} + +void NativeProviderAdapter::init(void* apiInstance) +{ + m_native->init(apiInstance); +} + +QString NativeProviderAdapter::providerName() const +{ + return QString::fromStdString(m_native->providerName()); +} + +QString NativeProviderAdapter::providerVersion() const +{ + return QString::fromStdString(m_native->providerVersion()); +} + +bool NativeProviderAdapter::informModuleToken(const QString& moduleName, const QString& token) +{ + return m_native->informModuleToken(moduleName.toStdString(), token.toStdString()); +} diff --git a/cpp/native/logos_native_adapter.h b/cpp/native/logos_native_adapter.h new file mode 100644 index 0000000..4f25270 --- /dev/null +++ b/cpp/native/logos_native_adapter.h @@ -0,0 +1,32 @@ +#ifndef LOGOS_NATIVE_ADAPTER_H +#define LOGOS_NATIVE_ADAPTER_H + +#include "../logos_provider_object.h" +#include "logos_native_provider.h" + +Q_DECLARE_INTERFACE(NativeProviderPlugin, NativeProviderPlugin_iid) + +// --------------------------------------------------------------------------- +// NativeProviderAdapter — bridges NativeProviderObject to LogosProviderObject +// +// Wraps a NativeProviderObject and presents it as a LogosProviderObject so +// the existing Qt-based runtime (ModuleProxy, transport) can use it unchanged. +// All type conversions (LogosValue <-> QVariant) happen here. +// --------------------------------------------------------------------------- +class NativeProviderAdapter : public LogosProviderObject { +public: + explicit NativeProviderAdapter(NativeProviderObject* native); + + QVariant callMethod(const QString& methodName, const QVariantList& args) override; + QJsonArray getMethods() override; + void setEventListener(EventCallback callback) override; + void init(void* apiInstance) override; + QString providerName() const override; + QString providerVersion() const override; + bool informModuleToken(const QString& moduleName, const QString& token) override; + +private: + NativeProviderObject* m_native; +}; + +#endif // LOGOS_NATIVE_ADAPTER_H diff --git a/cpp/native/logos_native_api.cpp b/cpp/native/logos_native_api.cpp new file mode 100644 index 0000000..99c3484 --- /dev/null +++ b/cpp/native/logos_native_api.cpp @@ -0,0 +1,35 @@ +#include "logos_native_api.h" +#include "logos_native_client.h" +#include "../logos_api.h" + +#include +#include + +NativeLogosAPI::NativeLogosAPI(LogosAPI* qtApi) + : m_qtApi(qtApi) +{ +} + +NativeLogosAPI::~NativeLogosAPI() +{ + for (auto& [name, client] : m_clients) { + delete client; + } +} + +NativeLogosClient* NativeLogosAPI::getClient(const std::string& target_module) +{ + auto it = m_clients.find(target_module); + if (it != m_clients.end()) + return it->second; + + qDebug() << "[NATIVE API] NativeLogosAPI::getClient — creating NativeLogosClient for" + << QString::fromStdString(target_module); + LogosAPIClient* qtClient = m_qtApi->getClient(QString::fromStdString(target_module)); + if (!qtClient) + return nullptr; + + auto* client = new NativeLogosClient(qtClient); + m_clients[target_module] = client; + return client; +} diff --git a/cpp/native/logos_native_api.h b/cpp/native/logos_native_api.h new file mode 100644 index 0000000..89961d5 --- /dev/null +++ b/cpp/native/logos_native_api.h @@ -0,0 +1,30 @@ +#ifndef LOGOS_NATIVE_API_H +#define LOGOS_NATIVE_API_H + +#include +#include + +class LogosAPI; +class NativeLogosClient; + +// --------------------------------------------------------------------------- +// NativeLogosAPI — Qt-free wrapper around LogosAPI +// +// Provides native module developers with a Qt-free entry point to the SDK. +// The header has no Qt includes; conversions happen in the .cpp file. +// --------------------------------------------------------------------------- +class NativeLogosAPI { +public: + explicit NativeLogosAPI(LogosAPI* qtApi); + ~NativeLogosAPI(); + + NativeLogosClient* getClient(const std::string& target_module); + + LogosAPI* qtApi() const { return m_qtApi; } + +private: + LogosAPI* m_qtApi; + std::map m_clients; +}; + +#endif // LOGOS_NATIVE_API_H diff --git a/cpp/native/logos_native_client.cpp b/cpp/native/logos_native_client.cpp new file mode 100644 index 0000000..f1b835b --- /dev/null +++ b/cpp/native/logos_native_client.cpp @@ -0,0 +1,137 @@ +#include "logos_native_client.h" +#include "logos_value_qt.h" +#include "../logos_api_client.h" + +#include +#include +#include + +NativeLogosClient::NativeLogosClient(LogosAPIClient* qtClient) + : m_client(qtClient) +{ +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const std::vector& args) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with" << args.size() << "args (native types)"; + QVariantList qArgs = logosValueListToQVariantList(args); + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + qArgs); + return logosValueFromQVariant(result); +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const LogosValue& arg) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with 1 arg (native types)"; + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + logosValueToQVariant(arg)); + return logosValueFromQVariant(result); +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const LogosValue& arg1, + const LogosValue& arg2) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with 2 args (native types)"; + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + logosValueToQVariant(arg1), + logosValueToQVariant(arg2)); + return logosValueFromQVariant(result); +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const LogosValue& arg1, + const LogosValue& arg2, + const LogosValue& arg3) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with 3 args (native types)"; + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + logosValueToQVariant(arg1), + logosValueToQVariant(arg2), + logosValueToQVariant(arg3)); + return logosValueFromQVariant(result); +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const LogosValue& arg1, + const LogosValue& arg2, + const LogosValue& arg3, + const LogosValue& arg4) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with 4 args (native types)"; + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + logosValueToQVariant(arg1), + logosValueToQVariant(arg2), + logosValueToQVariant(arg3), + logosValueToQVariant(arg4)); + return logosValueFromQVariant(result); +} + +LogosValue NativeLogosClient::invokeMethod(const std::string& objectName, + const std::string& methodName, + const LogosValue& arg1, + const LogosValue& arg2, + const LogosValue& arg3, + const LogosValue& arg4, + const LogosValue& arg5) +{ + qDebug() << "[NATIVE API] NativeLogosClient: calling" << QString::fromStdString(objectName) + << "." << QString::fromStdString(methodName) << "with 5 args (native types)"; + QVariant result = m_client->invokeRemoteMethod( + QString::fromStdString(objectName), + QString::fromStdString(methodName), + logosValueToQVariant(arg1), + logosValueToQVariant(arg2), + logosValueToQVariant(arg3), + logosValueToQVariant(arg4), + logosValueToQVariant(arg5)); + return logosValueFromQVariant(result); +} + +LogosObject* NativeLogosClient::requestObject(const std::string& objectName) +{ + qDebug() << "[NATIVE API] NativeLogosClient: requestObject" << QString::fromStdString(objectName); + return m_client->requestObject(QString::fromStdString(objectName)); +} + +void NativeLogosClient::onEvent(LogosObject* origin, const std::string& eventName, + std::function&)> callback) +{ + qDebug() << "[NATIVE API] NativeLogosClient: subscribing to event" << QString::fromStdString(eventName) + << "(native callback)"; + m_client->onEvent(origin, QString::fromStdString(eventName), + [callback](const QString& name, const QVariantList& data) { + callback(name.toStdString(), logosValueListFromQVariantList(data)); + }); +} + +void NativeLogosClient::onEventResponse(LogosObject* object, const std::string& eventName, + const std::vector& data) +{ + qDebug() << "[NATIVE API] NativeLogosClient: emitting event response" << QString::fromStdString(eventName) + << "(native types)"; + m_client->onEventResponse(object, QString::fromStdString(eventName), + logosValueListToQVariantList(data)); +} diff --git a/cpp/native/logos_native_client.h b/cpp/native/logos_native_client.h new file mode 100644 index 0000000..ad4fe15 --- /dev/null +++ b/cpp/native/logos_native_client.h @@ -0,0 +1,58 @@ +#ifndef LOGOS_NATIVE_CLIENT_H +#define LOGOS_NATIVE_CLIENT_H + +#include "logos_value.h" +#include "logos_native_types.h" + +#include +#include +#include + +class LogosAPIClient; +class LogosObject; + +// --------------------------------------------------------------------------- +// NativeLogosClient — Qt-free wrapper around LogosAPIClient +// +// Module developers use this instead of LogosAPIClient directly. +// The header is Qt-free; conversions happen in the .cpp file. +// --------------------------------------------------------------------------- +class NativeLogosClient { +public: + explicit NativeLogosClient(LogosAPIClient* qtClient); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const std::vector& args = {}); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const LogosValue& arg); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const LogosValue& arg1, const LogosValue& arg2); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const LogosValue& arg1, const LogosValue& arg2, + const LogosValue& arg3); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const LogosValue& arg1, const LogosValue& arg2, + const LogosValue& arg3, const LogosValue& arg4); + + LogosValue invokeMethod(const std::string& objectName, const std::string& methodName, + const LogosValue& arg1, const LogosValue& arg2, + const LogosValue& arg3, const LogosValue& arg4, + const LogosValue& arg5); + + LogosObject* requestObject(const std::string& objectName); + + void onEvent(LogosObject* origin, const std::string& eventName, + std::function&)> callback); + + void onEventResponse(LogosObject* object, const std::string& eventName, + const std::vector& data); + +private: + LogosAPIClient* m_client; +}; + +#endif // LOGOS_NATIVE_CLIENT_H diff --git a/cpp/native/logos_native_provider.cpp b/cpp/native/logos_native_provider.cpp new file mode 100644 index 0000000..bedfa5b --- /dev/null +++ b/cpp/native/logos_native_provider.cpp @@ -0,0 +1,55 @@ +#include "logos_native_provider.h" +#include "logos_native_api.h" +#include "../logos_api.h" +#include "../token_manager.h" + +#include +#include + +void NativeProviderBase::setEventListener(EventCallback callback) +{ + m_eventCallback = callback; +} + +void NativeProviderBase::init(void* apiInstance) +{ + auto* qtApi = static_cast(apiInstance); + m_logosAPI = new NativeLogosAPI(qtApi); + qDebug() << "[NATIVE API] NativeProviderBase::init — module initialized with native types"; + onInit(m_logosAPI); +} + +bool NativeProviderBase::informModuleToken(const std::string& moduleName, const std::string& token) +{ + if (!m_logosAPI) { + qWarning() << "[NATIVE API] NativeProviderBase::informModuleToken: LogosAPI not available"; + return false; + } + + auto* qtApi = m_logosAPI->qtApi(); + if (!qtApi) { + qWarning() << "[NATIVE API] NativeProviderBase::informModuleToken: underlying LogosAPI not available"; + return false; + } + + TokenManager* tokenManager = qtApi->getTokenManager(); + if (!tokenManager) { + qWarning() << "[NATIVE API] NativeProviderBase::informModuleToken: TokenManager not available"; + return false; + } + + qDebug() << "[NATIVE API] NativeProviderBase: saving token for module:" << QString::fromStdString(moduleName); + tokenManager->saveToken(QString::fromStdString(moduleName), QString::fromStdString(token)); + return true; +} + +void NativeProviderBase::emitEvent(const std::string& eventName, const std::vector& data) +{ + if (m_eventCallback) { + qDebug() << "[NATIVE API] NativeProviderBase::emitEvent:" << QString::fromStdString(eventName); + m_eventCallback(eventName, data); + } else { + qWarning() << "[NATIVE API] NativeProviderBase::emitEvent: no listener set for" + << QString::fromStdString(eventName); + } +} diff --git a/cpp/native/logos_native_provider.h b/cpp/native/logos_native_provider.h new file mode 100644 index 0000000..94863c5 --- /dev/null +++ b/cpp/native/logos_native_provider.h @@ -0,0 +1,86 @@ +#ifndef LOGOS_NATIVE_PROVIDER_H +#define LOGOS_NATIVE_PROVIDER_H + +#include "logos_value.h" +#include "logos_native_types.h" +#include "logos_macros.h" + +#include +#include +#include + +class NativeLogosAPI; + +// --------------------------------------------------------------------------- +// NativeProviderObject — Qt-free abstract provider interface +// +// Parallel to LogosProviderObject but uses native types exclusively. +// The adapter layer (NativeProviderAdapter) bridges this to the Qt runtime. +// --------------------------------------------------------------------------- +class NativeProviderObject { +public: + virtual ~NativeProviderObject() = default; + + using EventCallback = std::function&)>; + + virtual LogosValue callMethod(const std::string& methodName, + const std::vector& args) = 0; + virtual std::string getMethodsJson() = 0; + virtual void setEventListener(EventCallback callback) = 0; + virtual void init(void* apiInstance) = 0; + virtual bool informModuleToken(const std::string& moduleName, const std::string& token) = 0; + virtual std::string providerName() const = 0; + virtual std::string providerVersion() const = 0; +}; + +// --------------------------------------------------------------------------- +// NativeProviderBase — convenience base class for native modules +// +// Handles framework plumbing. callMethod() and getMethodsJson() are +// provided by generated code from logos-native-generator --provider-dispatch. +// --------------------------------------------------------------------------- +class NativeProviderBase : public NativeProviderObject { +public: + void setEventListener(EventCallback callback) override; + bool informModuleToken(const std::string& moduleName, const std::string& token) override; + void init(void* apiInstance) override; + +protected: + void emitEvent(const std::string& eventName, const std::vector& data); + virtual void onInit(NativeLogosAPI* api) {} + NativeLogosAPI* logosAPI() const { return m_logosAPI; } + +private: + EventCallback m_eventCallback; + NativeLogosAPI* m_logosAPI = nullptr; +}; + +// --------------------------------------------------------------------------- +// NATIVE_LOGOS_PROVIDER — macro for native module classes +// --------------------------------------------------------------------------- +#define NATIVE_LOGOS_PROVIDER(ClassName, Name, Version) \ +public: \ + std::string providerName() const override { return Name; } \ + std::string providerVersion() const override { return Version; } \ + LogosValue callMethod(const std::string& methodName, \ + const std::vector& args) override; \ + std::string getMethodsJson() override; \ +private: \ + using _LogosProviderThisType = ClassName; + +// --------------------------------------------------------------------------- +// NativeProviderPlugin — interface for Qt plugin detection +// +// The module's thin QObject loader implements this so the runtime can +// detect it via qobject_cast and call createNativeProviderObject(). +// The Q_DECLARE_INTERFACE macro goes in the loader header, not here. +// --------------------------------------------------------------------------- +class NativeProviderPlugin { +public: + virtual ~NativeProviderPlugin() = default; + virtual NativeProviderObject* createNativeProviderObject() = 0; +}; + +#define NativeProviderPlugin_iid "org.logos.NativeProviderPlugin" + +#endif // LOGOS_NATIVE_PROVIDER_H diff --git a/cpp/native/logos_native_types.h b/cpp/native/logos_native_types.h new file mode 100644 index 0000000..d565563 --- /dev/null +++ b/cpp/native/logos_native_types.h @@ -0,0 +1,94 @@ +#ifndef LOGOS_NATIVE_TYPES_H +#define LOGOS_NATIVE_TYPES_H + +#include "logos_value.h" + +#include +#include + +class NativeLogosResultException : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + +struct NativeLogosResult +{ + bool success = false; + LogosValue value; + std::string error; + + std::string getString() const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + return value.toString(); + } + + bool getBool() const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + return value.toBool(); + } + + int getInt() const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + return static_cast(value.toInt()); + } + + LogosValue::List getList() const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + return value.toList(); + } + + LogosValue::Map getMap() const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + return value.toMap(); + } + + std::string getString(const std::string& key, const std::string& def = "") const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + auto map = value.toMap(); + auto it = map.find(key); + if (it == map.end()) return def; + return it->second.toString(def); + } + + bool getBool(const std::string& key, bool def = false) const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + auto map = value.toMap(); + auto it = map.find(key); + if (it == map.end()) return def; + return it->second.toBool(def); + } + + int getInt(const std::string& key, int def = 0) const + { + if (!success) + throw NativeLogosResultException("Attempted to get value from a failed NativeLogosResult: " + error); + auto map = value.toMap(); + auto it = map.find(key); + if (it == map.end()) return def; + return static_cast(it->second.toInt(def)); + } + + std::string getError() const + { + if (success) + throw NativeLogosResultException("Attempted to get error from a successful NativeLogosResult"); + return error; + } +}; + +#endif // LOGOS_NATIVE_TYPES_H diff --git a/cpp/native/logos_value.cpp b/cpp/native/logos_value.cpp new file mode 100644 index 0000000..288e7ef --- /dev/null +++ b/cpp/native/logos_value.cpp @@ -0,0 +1,357 @@ +#include "logos_value.h" + +#include +#include +#include + +LogosValue::LogosValue() : m_data(std::monostate{}) {} + +LogosValue::LogosValue(bool v) : m_data(v) {} + +LogosValue::LogosValue(int v) : m_data(static_cast(v)) {} + +LogosValue::LogosValue(int64_t v) : m_data(v) {} + +LogosValue::LogosValue(double v) : m_data(v) {} + +LogosValue::LogosValue(const std::string& v) : m_data(v) {} + +LogosValue::LogosValue(const char* v) : m_data(std::string(v ? v : "")) {} + +LogosValue::LogosValue(const List& v) : m_data(v) {} + +LogosValue::LogosValue(const Map& v) : m_data(v) {} + +LogosValue::LogosValue(const std::vector& v) +{ + List list; + list.reserve(v.size()); + for (const auto& s : v) { + list.emplace_back(s); + } + m_data = std::move(list); +} + +bool LogosValue::isNull() const { return std::holds_alternative(m_data); } +bool LogosValue::isBool() const { return std::holds_alternative(m_data); } +bool LogosValue::isInt() const { return std::holds_alternative(m_data); } +bool LogosValue::isDouble() const { return std::holds_alternative(m_data); } +bool LogosValue::isString() const { return std::holds_alternative(m_data); } +bool LogosValue::isList() const { return std::holds_alternative(m_data); } +bool LogosValue::isMap() const { return std::holds_alternative(m_data); } + +bool LogosValue::toBool(bool def) const +{ + if (auto* v = std::get_if(&m_data)) return *v; + if (auto* v = std::get_if(&m_data)) return *v != 0; + if (auto* v = std::get_if(&m_data)) return *v != 0.0; + if (auto* v = std::get_if(&m_data)) { + if (*v == "true" || *v == "1") return true; + if (*v == "false" || *v == "0") return false; + } + return def; +} + +int64_t LogosValue::toInt(int64_t def) const +{ + if (auto* v = std::get_if(&m_data)) return *v; + if (auto* v = std::get_if(&m_data)) return *v ? 1 : 0; + if (auto* v = std::get_if(&m_data)) return static_cast(*v); + if (auto* v = std::get_if(&m_data)) { + try { return std::stoll(*v); } catch (...) {} + } + return def; +} + +double LogosValue::toDouble(double def) const +{ + if (auto* v = std::get_if(&m_data)) return *v; + if (auto* v = std::get_if(&m_data)) return static_cast(*v); + if (auto* v = std::get_if(&m_data)) return *v ? 1.0 : 0.0; + if (auto* v = std::get_if(&m_data)) { + try { return std::stod(*v); } catch (...) {} + } + return def; +} + +std::string LogosValue::toString(const std::string& def) const +{ + if (auto* v = std::get_if(&m_data)) return *v; + if (auto* v = std::get_if(&m_data)) return *v ? "true" : "false"; + if (auto* v = std::get_if(&m_data)) return std::to_string(*v); + if (auto* v = std::get_if(&m_data)) return std::to_string(*v); + if (isNull()) return def; + return toJson(); +} + +LogosValue::List LogosValue::toList() const +{ + if (auto* v = std::get_if(&m_data)) return *v; + return {}; +} + +LogosValue::Map LogosValue::toMap() const +{ + if (auto* v = std::get_if(&m_data)) return *v; + return {}; +} + +std::vector LogosValue::toStringList() const +{ + std::vector result; + if (auto* v = std::get_if(&m_data)) { + result.reserve(v->size()); + for (const auto& item : *v) { + result.push_back(item.toString()); + } + } + return result; +} + +LogosValue::operator bool() const { return !isNull(); } + +// --- JSON serialization --- + +static void escapeJsonString(std::ostringstream& out, const std::string& s) +{ + out << '"'; + for (char c : s) { + switch (c) { + case '"': out << "\\\""; break; + case '\\': out << "\\\\"; break; + case '\b': out << "\\b"; break; + case '\f': out << "\\f"; break; + case '\n': out << "\\n"; break; + case '\r': out << "\\r"; break; + case '\t': out << "\\t"; break; + default: + if (static_cast(c) < 0x20) { + char buf[8]; + snprintf(buf, sizeof(buf), "\\u%04x", static_cast(c)); + out << buf; + } else { + out << c; + } + } + } + out << '"'; +} + +static void valueToJson(std::ostringstream& out, const LogosValue& val) +{ + if (val.isNull()) { + out << "null"; + } else if (val.isBool()) { + out << (val.toBool() ? "true" : "false"); + } else if (val.isInt()) { + out << val.toInt(); + } else if (val.isDouble()) { + out << val.toDouble(); + } else if (val.isString()) { + escapeJsonString(out, val.toString()); + } else if (val.isList()) { + out << '['; + const auto& list = val.toList(); + for (size_t i = 0; i < list.size(); ++i) { + if (i > 0) out << ','; + valueToJson(out, list[i]); + } + out << ']'; + } else if (val.isMap()) { + out << '{'; + const auto& map = val.toMap(); + bool first = true; + for (const auto& [k, v] : map) { + if (!first) out << ','; + first = false; + escapeJsonString(out, k); + out << ':'; + valueToJson(out, v); + } + out << '}'; + } +} + +std::string LogosValue::toJson() const +{ + std::ostringstream out; + valueToJson(out, *this); + return out.str(); +} + +// --- JSON parsing --- + +namespace { + +struct JsonParser { + const std::string& src; + size_t pos = 0; + + char peek() const { return pos < src.size() ? src[pos] : '\0'; } + char advance() { return pos < src.size() ? src[pos++] : '\0'; } + + void skipWhitespace() + { + while (pos < src.size() && std::isspace(static_cast(src[pos]))) + ++pos; + } + + LogosValue parseValue() + { + skipWhitespace(); + char c = peek(); + if (c == '"') return parseString(); + if (c == '{') return parseObject(); + if (c == '[') return parseArray(); + if (c == 't' || c == 'f') return parseBool(); + if (c == 'n') return parseNull(); + return parseNumber(); + } + + LogosValue parseNull() + { + if (src.compare(pos, 4, "null") == 0) { + pos += 4; + return LogosValue(); + } + throw std::runtime_error("Invalid JSON: expected null"); + } + + LogosValue parseBool() + { + if (src.compare(pos, 4, "true") == 0) { + pos += 4; + return LogosValue(true); + } + if (src.compare(pos, 5, "false") == 0) { + pos += 5; + return LogosValue(false); + } + throw std::runtime_error("Invalid JSON: expected true/false"); + } + + LogosValue parseNumber() + { + size_t start = pos; + bool isFloat = false; + if (peek() == '-') advance(); + while (pos < src.size() && std::isdigit(static_cast(src[pos]))) + advance(); + if (peek() == '.') { + isFloat = true; + advance(); + while (pos < src.size() && std::isdigit(static_cast(src[pos]))) + advance(); + } + if (peek() == 'e' || peek() == 'E') { + isFloat = true; + advance(); + if (peek() == '+' || peek() == '-') advance(); + while (pos < src.size() && std::isdigit(static_cast(src[pos]))) + advance(); + } + std::string numStr = src.substr(start, pos - start); + if (isFloat) + return LogosValue(std::stod(numStr)); + return LogosValue(static_cast(std::stoll(numStr))); + } + + std::string parseRawString() + { + advance(); // opening '"' + std::string result; + while (pos < src.size()) { + char c = advance(); + if (c == '"') return result; + if (c == '\\') { + char esc = advance(); + switch (esc) { + case '"': result += '"'; break; + case '\\': result += '\\'; break; + case '/': result += '/'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'u': { + if (pos + 4 > src.size()) + throw std::runtime_error("Invalid JSON: incomplete \\u escape"); + std::string hex = src.substr(pos, 4); + pos += 4; + unsigned int cp = static_cast(std::stoul(hex, nullptr, 16)); + if (cp < 0x80) { + result += static_cast(cp); + } else if (cp < 0x800) { + result += static_cast(0xC0 | (cp >> 6)); + result += static_cast(0x80 | (cp & 0x3F)); + } else { + result += static_cast(0xE0 | (cp >> 12)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } + break; + } + default: result += esc; + } + } else { + result += c; + } + } + throw std::runtime_error("Invalid JSON: unterminated string"); + } + + LogosValue parseString() + { + return LogosValue(parseRawString()); + } + + LogosValue parseArray() + { + advance(); // '[' + LogosValue::List list; + skipWhitespace(); + if (peek() == ']') { advance(); return LogosValue(list); } + while (true) { + list.push_back(parseValue()); + skipWhitespace(); + if (peek() == ']') { advance(); return LogosValue(list); } + if (peek() != ',') + throw std::runtime_error("Invalid JSON: expected ',' or ']' in array"); + advance(); + } + } + + LogosValue parseObject() + { + advance(); // '{' + LogosValue::Map map; + skipWhitespace(); + if (peek() == '}') { advance(); return LogosValue(map); } + while (true) { + skipWhitespace(); + if (peek() != '"') + throw std::runtime_error("Invalid JSON: expected string key in object"); + std::string key = parseRawString(); + skipWhitespace(); + if (peek() != ':') + throw std::runtime_error("Invalid JSON: expected ':' after key"); + advance(); + map[key] = parseValue(); + skipWhitespace(); + if (peek() == '}') { advance(); return LogosValue(map); } + if (peek() != ',') + throw std::runtime_error("Invalid JSON: expected ',' or '}' in object"); + advance(); + } + } +}; + +} // anonymous namespace + +LogosValue LogosValue::fromJson(const std::string& json) +{ + JsonParser parser{json, 0}; + LogosValue val = parser.parseValue(); + return val; +} diff --git a/cpp/native/logos_value.h b/cpp/native/logos_value.h new file mode 100644 index 0000000..591b51a --- /dev/null +++ b/cpp/native/logos_value.h @@ -0,0 +1,52 @@ +#ifndef LOGOS_VALUE_H +#define LOGOS_VALUE_H + +#include +#include +#include +#include +#include + +class LogosValue { +public: + using List = std::vector; + using Map = std::map; + +private: + std::variant m_data; + +public: + LogosValue(); + LogosValue(bool v); + LogosValue(int v); + LogosValue(int64_t v); + LogosValue(double v); + LogosValue(const std::string& v); + LogosValue(const char* v); + LogosValue(const List& v); + LogosValue(const Map& v); + LogosValue(const std::vector& v); + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isDouble() const; + bool isString() const; + bool isList() const; + bool isMap() const; + + bool toBool(bool def = false) const; + int64_t toInt(int64_t def = 0) const; + double toDouble(double def = 0.0) const; + std::string toString(const std::string& def = "") const; + List toList() const; + Map toMap() const; + std::vector toStringList() const; + + std::string toJson() const; + static LogosValue fromJson(const std::string& json); + + explicit operator bool() const; +}; + +#endif // LOGOS_VALUE_H diff --git a/cpp/native/logos_value_qt.cpp b/cpp/native/logos_value_qt.cpp new file mode 100644 index 0000000..0ab42cf --- /dev/null +++ b/cpp/native/logos_value_qt.cpp @@ -0,0 +1,139 @@ +#include "logos_value_qt.h" +#include "../logos_types.h" + +#include +#include +#include +#include +#include +#include + +LogosValue logosValueFromQVariant(const QVariant& v) +{ + if (!v.isValid() || v.isNull()) + return LogosValue(); + + switch (v.typeId()) { + case QMetaType::Bool: + return LogosValue(v.toBool()); + case QMetaType::Int: + case QMetaType::LongLong: + return LogosValue(static_cast(v.toLongLong())); + case QMetaType::UInt: + case QMetaType::ULongLong: + return LogosValue(static_cast(v.toLongLong())); + case QMetaType::Double: + case QMetaType::Float: + return LogosValue(v.toDouble()); + case QMetaType::QString: + return LogosValue(v.toString().toStdString()); + case QMetaType::QStringList: { + LogosValue::List list; + for (const auto& s : v.toStringList()) + list.emplace_back(s.toStdString()); + return LogosValue(list); + } + case QMetaType::QVariantList: { + return LogosValue(logosValueListFromQVariantList(v.toList())); + } + case QMetaType::QVariantMap: { + LogosValue::Map map; + QVariantMap qm = v.toMap(); + for (auto it = qm.constBegin(); it != qm.constEnd(); ++it) { + map[it.key().toStdString()] = logosValueFromQVariant(it.value()); + } + return LogosValue(map); + } + case QMetaType::QJsonArray: { + LogosValue::List list; + QJsonArray arr = v.toJsonArray(); + for (const auto& item : arr) { + list.push_back(logosValueFromQVariant(item.toVariant())); + } + return LogosValue(list); + } + case QMetaType::QJsonObject: { + LogosValue::Map map; + QJsonObject obj = v.toJsonObject(); + for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) { + map[it.key().toStdString()] = logosValueFromQVariant(it.value().toVariant()); + } + return LogosValue(map); + } + default: { + const char* tn = v.typeName(); + if (tn && strcmp(tn, "LogosResult") == 0) { + const LogosResult* lr = static_cast(v.constData()); + LogosValue::Map m; + m["success"] = LogosValue(lr->success); + m["value"] = logosValueFromQVariant(lr->value); + m["error"] = logosValueFromQVariant(lr->error); + return LogosValue(m); + } + if (v.canConvert()) + return LogosValue(v.toString().toStdString()); + return LogosValue(); + } + } +} + +QVariant logosValueToQVariant(const LogosValue& v) +{ + if (v.isNull()) return QVariant(); + if (v.isBool()) return QVariant(v.toBool()); + if (v.isInt()) { + int64_t val = v.toInt(); + if (val >= std::numeric_limits::min() && val <= std::numeric_limits::max()) + return QVariant(static_cast(val)); + return QVariant(static_cast(val)); + } + if (v.isDouble()) return QVariant(v.toDouble()); + if (v.isString()) return QVariant(QString::fromStdString(v.toString())); + if (v.isList()) return QVariant(logosValueListToQVariantList(v.toList())); + if (v.isMap()) { + QVariantMap qm; + for (const auto& [k, val] : v.toMap()) { + qm[QString::fromStdString(k)] = logosValueToQVariant(val); + } + return QVariant(qm); + } + return QVariant(); +} + +std::vector logosValueListFromQVariantList(const QVariantList& list) +{ + std::vector result; + result.reserve(static_cast(list.size())); + for (const auto& item : list) { + result.push_back(logosValueFromQVariant(item)); + } + return result; +} + +QVariantList logosValueListToQVariantList(const std::vector& list) +{ + QVariantList result; + result.reserve(static_cast(list.size())); + for (const auto& item : list) { + result.append(logosValueToQVariant(item)); + } + return result; +} + +NativeLogosResult nativeResultFromQt(const LogosResult& qtResult) +{ + NativeLogosResult result; + result.success = qtResult.success; + result.value = logosValueFromQVariant(qtResult.value); + result.error = qtResult.error.toString().toStdString(); + return result; +} + +LogosResult qtResultFromNative(const NativeLogosResult& result) +{ + LogosResult qtResult; + qtResult.success = result.success; + qtResult.value = logosValueToQVariant(result.value); + qtResult.error = QVariant(QString::fromStdString(result.error)); + return qtResult; +} diff --git a/cpp/native/logos_value_qt.h b/cpp/native/logos_value_qt.h new file mode 100644 index 0000000..3e20c35 --- /dev/null +++ b/cpp/native/logos_value_qt.h @@ -0,0 +1,22 @@ +#ifndef LOGOS_VALUE_QT_H +#define LOGOS_VALUE_QT_H + +#include "logos_value.h" +#include "logos_native_types.h" + +#include +#include +#include + +struct LogosResult; + +LogosValue logosValueFromQVariant(const QVariant& v); +QVariant logosValueToQVariant(const LogosValue& v); + +std::vector logosValueListFromQVariantList(const QVariantList& list); +QVariantList logosValueListToQVariantList(const std::vector& list); + +NativeLogosResult nativeResultFromQt(const LogosResult& qtResult); +LogosResult qtResultFromNative(const NativeLogosResult& result); + +#endif // LOGOS_VALUE_QT_H diff --git a/cpp/qt_provider_object.cpp b/cpp/qt_provider_object.cpp index 37f7b70..f8527cf 100644 --- a/cpp/qt_provider_object.cpp +++ b/cpp/qt_provider_object.cpp @@ -170,13 +170,12 @@ QtProviderObject::QtProviderObject(QObject* module, QObject* parent) if (m_module) { connect(m_module, SIGNAL(eventResponse(QString, QVariantList)), this, SLOT(onWrappedEventResponse(QString, QVariantList))); - qDebug() << "[LogosProviderObject] QtProviderObject: connected to QObject eventResponse signal"; + qDebug() << "[QT API] QtProviderObject: wrapping Q_INVOKABLE QObject (DEPRECATED — legacy path)"; } } QtProviderObject::~QtProviderObject() { - qDebug() << "[LogosProviderObject] QtProviderObject: destroyed"; } void QtProviderObject::onWrappedEventResponse(const QString& eventName, const QVariantList& data) @@ -194,12 +193,12 @@ void QtProviderObject::init(void* apiInstance) int methodIndex = m_module->metaObject()->indexOfMethod("initLogos(LogosAPI*)"); if (methodIndex != -1) { - qDebug() << "[LogosProviderObject] QtProviderObject: calling initLogos on wrapped QObject"; + qDebug() << "[QT API] QtProviderObject::init — calling initLogos(LogosAPI*) on wrapped QObject"; QMetaObject::invokeMethod(m_module, "initLogos", Qt::DirectConnection, Q_ARG(LogosAPI*, api)); } else { - qDebug() << "[LogosProviderObject] QtProviderObject: wrapped QObject has no initLogos, skipping"; + qDebug() << "[QT API] QtProviderObject::init — wrapped QObject has no initLogos, skipping"; } } @@ -223,15 +222,18 @@ void QtProviderObject::setEventListener(EventCallback callback) QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantList& args) { if (!m_module) { - qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: null module"; + qWarning() << "[QT API] QtProviderObject::callMethod: null module"; return QVariant(); } if (methodName.isEmpty()) { - qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: empty method name"; + qWarning() << "[QT API] QtProviderObject::callMethod: empty method name"; return QVariant(); } + qDebug() << "[QT API] QtProviderObject: dispatching" << methodName + << "with" << args.size() << "args via QMetaObject::invokeMethod (Q_INVOKABLE path)"; + // Special-case getPluginMethods (framework-level, not on the wrapped plugin) if (methodName == "getPluginMethods" && args.isEmpty()) { return QVariant(getMethods()); @@ -240,13 +242,13 @@ QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantL // Auth-token validation (mirrors the old ModuleProxy logic) PluginInterface* pluginInterface = qobject_cast(m_module); if (!pluginInterface) { - qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: module is not a PluginInterface"; + qWarning() << "[QT API] QtProviderObject::callMethod: module is not a PluginInterface"; return QVariant(); } LogosAPI* api = pluginInterface->logosAPI; if (!api) { - qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: LogosAPI not available"; + qWarning() << "[QT API] QtProviderObject::callMethod: LogosAPI not available"; return QVariant(); } @@ -262,7 +264,7 @@ QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantL } if (methodIndex == -1) { - qWarning() << "[LogosProviderObject] QtProviderObject: method not found:" << methodName + qWarning() << "[QT API] QtProviderObject: method not found:" << methodName << "with" << args.size() << "arguments"; return QVariant(); } @@ -305,13 +307,13 @@ QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantL success = invokeMethodByArgCount(m_module, methodName, args, &v, "QStringList"); if (success) result = QVariant(v); } else { - qWarning() << "[LogosProviderObject] QtProviderObject: unsupported return type:" + qWarning() << "[QT API] QtProviderObject: unsupported return type:" << returnType.name() << "for method:" << methodName; return QVariant(); } if (!success) { - qWarning() << "[LogosProviderObject] QtProviderObject: failed to invoke" << methodName; + qWarning() << "[QT API] QtProviderObject: failed to invoke" << methodName; } return result; } @@ -320,23 +322,23 @@ bool QtProviderObject::informModuleToken(const QString& moduleName, const QStrin { PluginInterface* pluginInterface = qobject_cast(m_module); if (!pluginInterface) { - qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: not a PluginInterface"; + qWarning() << "[QT API] QtProviderObject::informModuleToken: not a PluginInterface"; return false; } LogosAPI* api = pluginInterface->logosAPI; if (!api) { - qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: LogosAPI not available"; + qWarning() << "[QT API] QtProviderObject::informModuleToken: LogosAPI not available"; return false; } TokenManager* tokenManager = api->getTokenManager(); if (!tokenManager) { - qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: TokenManager not available"; + qWarning() << "[QT API] QtProviderObject::informModuleToken: TokenManager not available"; return false; } - qDebug() << "[LogosProviderObject] QtProviderObject: saving token for module:" << moduleName; + qDebug() << "[QT API] QtProviderObject: saving token for module:" << moduleName; tokenManager->saveToken(moduleName, token); return true; } diff --git a/flake.nix b/flake.nix index e6b86b1..5c1d037 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,7 @@ # Combined outputs (for backward compatibility) logos-cpp-sdk = sdk; cpp-generator = bin; # Alias for backward compatibility + native-generator = bin; # Also includes logos-native-generator # Default package default = sdk; diff --git a/native-generator/CMakeLists.txt b/native-generator/CMakeLists.txt new file mode 100644 index 0000000..013f13f --- /dev/null +++ b/native-generator/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.16) +project(logos-native-generator LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(logos-native-generator main.cpp) diff --git a/native-generator/main.cpp b/native-generator/main.cpp new file mode 100644 index 0000000..5860b5e --- /dev/null +++ b/native-generator/main.cpp @@ -0,0 +1,661 @@ +// logos-native-generator — Qt-free code generator for LOGOS_METHOD modules +// +// Parses C++ headers for LOGOS_METHOD markers and emits native-typed code: +// --provider-dispatch: generates callMethod()/getMethodsJson() dispatch +// --consumer-wrappers: generates Qt-free consumer wrapper class +// --umbrella: generates logos_sdk.h/cpp aggregating all native wrappers + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +struct ParsedMethod { + std::string returnType; + std::string name; + std::vector> params; // (type, name) +}; + +static std::string trim(const std::string& s) +{ + auto start = s.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) return ""; + auto end = s.find_last_not_of(" \t\r\n"); + return s.substr(start, end - start + 1); +} + +static std::string normalizeType(std::string t) +{ + t = trim(t); + if (t.substr(0, 6) == "const ") t = t.substr(6); + t = trim(t); + if (!t.empty() && (t.back() == '&' || t.back() == '*')) t.pop_back(); + t = trim(t); + return t; +} + +static std::string toPascalCase(const std::string& name) +{ + std::string out; + bool cap = true; + for (char c : name) { + if (!std::isalnum(static_cast(c))) { cap = true; continue; } + if (cap) { out += static_cast(std::toupper(static_cast(c))); cap = false; } + else { out += static_cast(std::tolower(static_cast(c))); } + } + if (out.empty()) return "Module"; + return out; +} + +// Maps any Qt or native type to the canonical native type for code generation +static std::string mapToNativeType(const std::string& type) +{ + std::string base = normalizeType(type); + if (base == "void" || base.empty()) return "void"; + if (base == "bool") return "bool"; + if (base == "int") return "int"; + if (base == "int64_t") return "int64_t"; + if (base == "double") return "double"; + if (base == "float") return "float"; + if (base == "QString" || base == "std::string") return "std::string"; + if (base == "QStringList" || base == "std::vector") return "std::vector"; + if (base == "QJsonArray" || base == "std::vector") return "std::vector"; + if (base == "QVariant" || base == "LogosValue") return "LogosValue"; + if (base == "LogosResult" || base == "NativeLogosResult") return "NativeLogosResult"; + if (base == "QVariantList") return "std::vector"; + if (base == "QVariantMap") return "LogosValue::Map"; + return "LogosValue"; +} + +// Returns the LogosValue accessor expression for extracting a native type from args +static std::string argConversion(const std::string& nativeType, const std::string& argExpr) +{ + if (nativeType == "bool") return argExpr + ".toBool()"; + if (nativeType == "int") return "static_cast(" + argExpr + ".toInt())"; + if (nativeType == "int64_t") return argExpr + ".toInt()"; + if (nativeType == "double") return argExpr + ".toDouble()"; + if (nativeType == "float") return "static_cast(" + argExpr + ".toDouble())"; + if (nativeType == "std::string") return argExpr + ".toString()"; + if (nativeType == "std::vector") return argExpr + ".toStringList()"; + if (nativeType == "std::vector") return argExpr + ".toList()"; + if (nativeType == "LogosValue::Map") return argExpr + ".toMap()"; + if (nativeType == "LogosValue") return argExpr; + return argExpr + ".toString()"; +} + +// Returns the expression to wrap a return value in LogosValue +static std::string returnWrap(const std::string& nativeType, const std::string& expr) +{ + if (nativeType == "void") return ""; + if (nativeType == "LogosValue") return expr; + return "LogosValue(" + expr + ")"; +} + +// Returns the expression to unwrap a LogosValue to the native return type +static std::string returnUnwrap(const std::string& nativeType, const std::string& expr) +{ + if (nativeType == "bool") return expr + ".toBool()"; + if (nativeType == "int") return "static_cast(" + expr + ".toInt())"; + if (nativeType == "int64_t") return expr + ".toInt()"; + if (nativeType == "double") return expr + ".toDouble()"; + if (nativeType == "float") return "static_cast(" + expr + ".toDouble())"; + if (nativeType == "std::string") return expr + ".toString()"; + if (nativeType == "std::vector") return expr + ".toStringList()"; + if (nativeType == "std::vector") return expr + ".toList()"; + if (nativeType == "LogosValue::Map") return expr + ".toMap()"; + if (nativeType == "LogosValue") return expr; + return expr + ".toString()"; +} + +// Whether a param type should be passed by const ref +static bool shouldPassByConstRef(const std::string& nativeType) +{ + return nativeType == "std::string" || + nativeType == "std::vector" || + nativeType == "std::vector" || + nativeType == "LogosValue" || + nativeType == "LogosValue::Map" || + nativeType == "NativeLogosResult"; +} + +static std::vector parseHeader(const std::string& headerPath) +{ + std::vector methods; + std::ifstream file(headerPath); + if (!file.is_open()) { + std::cerr << "Cannot open header file: " << headerPath << "\n"; + return methods; + } + + std::regex re(R"(^\s*LOGOS_METHOD\s+(.+?)\s+(\w+)\s*\(([^)]*)\)\s*;)"); + std::string line; + while (std::getline(file, line)) { + std::smatch match; + if (!std::regex_search(line, match, re)) continue; + + ParsedMethod m; + m.returnType = normalizeType(match[1].str()); + m.name = match[2].str(); + + std::string paramStr = trim(match[3].str()); + if (!paramStr.empty()) { + std::istringstream pstream(paramStr); + std::string part; + while (std::getline(pstream, part, ',')) { + std::string trimmed = trim(part); + // Strip default value + auto eqPos = trimmed.find('='); + if (eqPos != std::string::npos) + trimmed = trim(trimmed.substr(0, eqPos)); + // Split type and name: find last space or & + auto lastSpace = trimmed.rfind(' '); + auto lastAmp = trimmed.rfind('&'); + size_t splitAt = std::string::npos; + if (lastSpace != std::string::npos && lastAmp != std::string::npos) + splitAt = std::max(lastSpace, lastAmp); + else if (lastSpace != std::string::npos) + splitAt = lastSpace; + else if (lastAmp != std::string::npos) + splitAt = lastAmp; + if (splitAt != std::string::npos && splitAt > 0) { + std::string type = normalizeType(trimmed.substr(0, splitAt + 1)); + std::string pname = trim(trimmed.substr(splitAt + 1)); + m.params.push_back({type, pname}); + } else { + m.params.push_back({normalizeType(trimmed), + "arg" + std::to_string(m.params.size())}); + } + } + } + methods.push_back(m); + } + return methods; +} + +static std::string findClassName(const std::string& headerPath) +{ + std::ifstream file(headerPath); + if (!file.is_open()) return ""; + + std::regex re(R"(class\s+(\w+)\s*:\s*public\s+NativeProviderBase)"); + std::string line; + while (std::getline(file, line)) { + std::smatch match; + if (std::regex_search(line, match, re)) + return match[1].str(); + } + + // Fallback: also support LogosProviderBase for migration + file.clear(); + file.seekg(0); + std::regex re2(R"(class\s+(\w+)\s*:\s*public\s+LogosProviderBase)"); + while (std::getline(file, line)) { + std::smatch match; + if (std::regex_search(line, match, re2)) + return match[1].str(); + } + return ""; +} + +// --provider-dispatch: generates native callMethod/getMethodsJson +static int generateProviderDispatch(const std::string& headerPath, const std::string& outputDir) +{ + auto methods = parseHeader(headerPath); + if (methods.empty()) { + std::cerr << "No LOGOS_METHOD markers found in: " << headerPath << "\n"; + return 3; + } + + std::string className = findClassName(headerPath); + if (className.empty()) { + std::cerr << "Could not find class inheriting NativeProviderBase in: " << headerPath << "\n"; + return 4; + } + + std::string headerBaseName = fs::path(headerPath).filename().string(); + std::string genDir = outputDir.empty() ? fs::path(headerPath).parent_path().string() : outputDir; + fs::create_directories(genDir); + + std::ostringstream s; + s << "// AUTO-GENERATED by logos-native-generator -- do not edit\n"; + s << "#include \"" << headerBaseName << "\"\n"; + s << "#include \"logos_value.h\"\n"; + s << "#include \"logos_native_types.h\"\n\n"; + + // Group by name for overloads + std::map> byName; + for (const auto& m : methods) + byName[m.name].push_back(&m); + + s << "LogosValue " << className << "::callMethod(\n"; + s << " const std::string& methodName, const std::vector& args)\n"; + s << "{\n"; + for (const auto& [name, overloads] : byName) { + s << " if (methodName == \"" << name << "\") {\n"; + bool multi = overloads.size() > 1; + for (const auto* m : overloads) { + std::string nativeRet = mapToNativeType(m->returnType); + if (multi) { + s << " if (args.size() == " << m->params.size() << ") {\n "; + } + if (nativeRet == "void") { + s << " " << m->name << "("; + for (size_t i = 0; i < m->params.size(); ++i) { + std::string nt = mapToNativeType(m->params[i].first); + s << argConversion(nt, "args.at(" + std::to_string(i) + ")"); + if (i + 1 < m->params.size()) s << ", "; + } + s << ");\n"; + if (multi) s << " "; + s << " return LogosValue(true);\n"; + } else if (nativeRet == "NativeLogosResult") { + s << " {\n"; + if (multi) s << " "; + s << " NativeLogosResult _r = " << m->name << "("; + for (size_t i = 0; i < m->params.size(); ++i) { + std::string nt = mapToNativeType(m->params[i].first); + s << argConversion(nt, "args.at(" + std::to_string(i) + ")"); + if (i + 1 < m->params.size()) s << ", "; + } + s << ");\n"; + if (multi) s << " "; + s << " LogosValue::Map _m;\n"; + if (multi) s << " "; + s << " _m[\"success\"] = LogosValue(_r.success);\n"; + if (multi) s << " "; + s << " _m[\"value\"] = _r.value;\n"; + if (multi) s << " "; + s << " _m[\"error\"] = LogosValue(_r.error);\n"; + if (multi) s << " "; + s << " return LogosValue(_m);\n"; + if (multi) s << " "; + s << " }\n"; + } else { + std::string call = m->name + "("; + for (size_t i = 0; i < m->params.size(); ++i) { + std::string nt = mapToNativeType(m->params[i].first); + call += argConversion(nt, "args.at(" + std::to_string(i) + ")"); + if (i + 1 < m->params.size()) call += ", "; + } + call += ")"; + s << " return " << returnWrap(nativeRet, call) << ";\n"; + } + if (multi) s << " }\n"; + } + s << " }\n"; + } + s << " return LogosValue();\n"; + s << "}\n\n"; + + // getMethodsJson() + s << "std::string " << className << "::getMethodsJson()\n"; + s << "{\n"; + s << " return R\"LOGOS_JSON(["; + for (size_t i = 0; i < methods.size(); ++i) { + const auto& m = methods[i]; + std::string nativeRet = mapToNativeType(m.returnType); + s << "{\"name\":\"" << m.name << "\",\"returnType\":\"" << nativeRet + << "\",\"isInvokable\":true,\"signature\":\"" << m.name << "("; + for (size_t p = 0; p < m.params.size(); ++p) { + s << mapToNativeType(m.params[p].first); + if (p + 1 < m.params.size()) s << ","; + } + s << ")\""; + if (!m.params.empty()) { + s << ",\"parameters\":["; + for (size_t p = 0; p < m.params.size(); ++p) { + s << "{\"type\":\"" << mapToNativeType(m.params[p].first) + << "\",\"name\":\"" << m.params[p].second << "\"}"; + if (p + 1 < m.params.size()) s << ","; + } + s << "]"; + } + s << "}"; + if (i + 1 < methods.size()) s << ","; + } + s << "])LOGOS_JSON\";\n"; + s << "}\n"; + + std::string outPath = (fs::path(genDir) / "logos_provider_dispatch.cpp").string(); + std::ofstream out(outPath); + if (!out.is_open()) { + std::cerr << "Failed to write dispatch file: " << outPath << "\n"; + return 5; + } + out << s.str(); + out.close(); + + std::cout << "Generated provider dispatch: " << outPath + << " (" << methods.size() << " methods from " << className << ")\n"; + return 0; +} + +// --consumer-wrappers: generates Qt-free consumer wrapper +static int generateConsumerWrappers(const std::string& headerPath, + const std::string& moduleName, + const std::string& outputDir) +{ + auto methods = parseHeader(headerPath); + if (methods.empty()) { + std::cerr << "No LOGOS_METHOD markers found in: " << headerPath << "\n"; + return 3; + } + + std::string className = toPascalCase(moduleName); + std::string genDir = outputDir.empty() ? fs::path(headerPath).parent_path().string() : outputDir; + fs::create_directories(genDir); + + std::string headerRel = moduleName + "_api.h"; + std::string sourceRel = moduleName + "_api.cpp"; + + // Generate header + { + std::ostringstream s; + s << "#pragma once\n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \"logos_value.h\"\n"; + s << "#include \"logos_native_types.h\"\n\n"; + s << "class NativeLogosClient;\n"; + s << "class NativeLogosAPI;\n"; + s << "class LogosAPI;\n"; + s << "class LogosObject;\n\n"; + s << "class " << className << " {\n"; + s << "public:\n"; + s << " explicit " << className << "(NativeLogosAPI* api);\n"; + s << " explicit " << className << "(LogosAPI* api);\n\n"; + + for (const auto& m : methods) { + std::string nativeRet = mapToNativeType(m.returnType); + s << " " << nativeRet << " " << m.name << "("; + for (size_t i = 0; i < m.params.size(); ++i) { + std::string nt = mapToNativeType(m.params[i].first); + if (shouldPassByConstRef(nt)) + s << "const " << nt << "& " << m.params[i].second; + else + s << nt << " " << m.params[i].second; + if (i + 1 < m.params.size()) s << ", "; + } + s << ");\n"; + } + + s << "\n using EventCallback = std::function&)>;\n"; + s << " bool on(const std::string& eventName, EventCallback callback);\n"; + s << " void trigger(const std::string& eventName, const std::vector& data = {});\n\n"; + + s << "private:\n"; + s << " NativeLogosAPI* m_api;\n"; + s << " NativeLogosAPI* m_ownedApi = nullptr;\n"; + s << " NativeLogosClient* m_client;\n"; + s << " std::string m_moduleName;\n"; + s << " LogosObject* m_eventReplica = nullptr;\n"; + s << "};\n"; + + std::string headerPath = (fs::path(genDir) / headerRel).string(); + std::ofstream out(headerPath); + if (!out.is_open()) { + std::cerr << "Failed to write header: " << headerPath << "\n"; + return 5; + } + out << s.str(); + } + + // Generate source + { + std::ostringstream s; + s << "#include \"" << headerRel << "\"\n"; + s << "#include \"logos_native_api.h\"\n"; + s << "#include \"logos_native_client.h\"\n\n"; + + s << className << "::" << className << "(NativeLogosAPI* api)\n"; + s << " : m_api(api), m_client(api->getClient(\"" << moduleName << "\")),\n"; + s << " m_moduleName(\"" << moduleName << "\") {}\n\n"; + + s << className << "::" << className << "(LogosAPI* api)\n"; + s << " : m_ownedApi(new NativeLogosAPI(api)), m_api(m_ownedApi),\n"; + s << " m_client(m_api->getClient(\"" << moduleName << "\")),\n"; + s << " m_moduleName(\"" << moduleName << "\") {}\n\n"; + + for (const auto& m : methods) { + std::string nativeRet = mapToNativeType(m.returnType); + s << nativeRet << " " << className << "::" << m.name << "("; + for (size_t i = 0; i < m.params.size(); ++i) { + std::string nt = mapToNativeType(m.params[i].first); + if (shouldPassByConstRef(nt)) + s << "const " << nt << "& " << m.params[i].second; + else + s << nt << " " << m.params[i].second; + if (i + 1 < m.params.size()) s << ", "; + } + s << ") {\n"; + + // Build invocation + if (nativeRet != "void") { + s << " LogosValue _result = "; + } else { + s << " "; + } + + if (m.params.empty()) { + s << "m_client->invokeMethod(m_moduleName, \"" << m.name << "\");\n"; + } else if (m.params.size() <= 5) { + s << "m_client->invokeMethod(m_moduleName, \"" << m.name << "\""; + for (const auto& [ptype, pname] : m.params) { + std::string nt = mapToNativeType(ptype); + if (nt == "LogosValue") + s << ", " << pname; + else + s << ", LogosValue(" << pname << ")"; + } + s << ");\n"; + } else { + s << "m_client->invokeMethod(m_moduleName, \"" << m.name << "\", std::vector{"; + for (size_t i = 0; i < m.params.size(); ++i) { + std::string nt = mapToNativeType(m.params[i].first); + if (nt == "LogosValue") + s << m.params[i].second; + else + s << "LogosValue(" << m.params[i].second << ")"; + if (i + 1 < m.params.size()) s << ", "; + } + s << "});\n"; + } + + // Return conversion + if (nativeRet == "void") { + // nothing + } else if (nativeRet == "NativeLogosResult") { + s << " NativeLogosResult _nr;\n"; + s << " if (_result.isMap()) {\n"; + s << " auto _m = _result.toMap();\n"; + s << " auto _sIt = _m.find(\"success\");\n"; + s << " _nr.success = (_sIt != _m.end()) ? _sIt->second.toBool() : false;\n"; + s << " auto _vIt = _m.find(\"value\");\n"; + s << " _nr.value = (_vIt != _m.end()) ? _vIt->second : LogosValue();\n"; + s << " auto _eIt = _m.find(\"error\");\n"; + s << " _nr.error = (_eIt != _m.end()) ? _eIt->second.toString() : \"\";\n"; + s << " }\n"; + s << " return _nr;\n"; + } else { + s << " return " << returnUnwrap(nativeRet, "_result") << ";\n"; + } + + s << "}\n\n"; + } + + // Event methods + s << "bool " << className << "::on(const std::string& eventName, EventCallback callback) {\n"; + s << " if (!callback) return false;\n"; + s << " if (!m_eventReplica) {\n"; + s << " m_eventReplica = m_client->requestObject(m_moduleName);\n"; + s << " if (!m_eventReplica) return false;\n"; + s << " }\n"; + s << " m_client->onEvent(m_eventReplica, eventName, callback);\n"; + s << " return true;\n"; + s << "}\n\n"; + + s << "void " << className << "::trigger(const std::string& eventName, const std::vector& data) {\n"; + s << " if (!m_eventReplica) {\n"; + s << " m_eventReplica = m_client->requestObject(m_moduleName);\n"; + s << " if (!m_eventReplica) return;\n"; + s << " }\n"; + s << " m_client->onEventResponse(m_eventReplica, eventName, data);\n"; + s << "}\n"; + + std::string sourcePath = (fs::path(genDir) / sourceRel).string(); + std::ofstream out(sourcePath); + if (!out.is_open()) { + std::cerr << "Failed to write source: " << sourcePath << "\n"; + return 6; + } + out << s.str(); + } + + std::cout << "Generated consumer wrappers: " << (fs::path(genDir) / headerRel).string() + << " and " << (fs::path(genDir) / sourceRel).string() << "\n"; + return 0; +} + +// --umbrella: generates logos_sdk.h/cpp for native modules +static int generateUmbrella(const std::string& outputDir, const std::vector& deps) +{ + if (outputDir.empty()) { + std::cerr << "--umbrella requires --output-dir\n"; + return 1; + } + fs::create_directories(outputDir); + + // Generate logos_sdk.h + { + std::ostringstream s; + s << "#pragma once\n"; + s << "#include \"logos_native_api.h\"\n\n"; + + for (const auto& dep : deps) { + s << "#include \"" << dep << "_api.h\"\n"; + } + s << "\n"; + + s << "struct LogosModules {\n"; + s << " explicit LogosModules(NativeLogosAPI* api) : api(api)"; + for (const auto& dep : deps) { + s << ",\n " << dep << "(api)"; + } + s << " {}\n"; + s << " NativeLogosAPI* api;\n"; + for (const auto& dep : deps) { + std::string className = toPascalCase(dep); + s << " " << className << " " << dep << ";\n"; + } + s << "};\n"; + + std::string path = (fs::path(outputDir) / "logos_sdk.h").string(); + std::ofstream out(path); + if (!out.is_open()) { + std::cerr << "Failed to write umbrella header: " << path << "\n"; + return 5; + } + out << s.str(); + } + + // Generate logos_sdk.cpp + { + std::ostringstream s; + s << "#include \"logos_sdk.h\"\n\n"; + for (const auto& dep : deps) { + s << "#include \"" << dep << "_api.cpp\"\n"; + } + s << "\n"; + + std::string path = (fs::path(outputDir) / "logos_sdk.cpp").string(); + std::ofstream out(path); + if (!out.is_open()) { + std::cerr << "Failed to write umbrella source: " << path << "\n"; + return 6; + } + out << s.str(); + } + + std::cout << "Generated umbrella: logos_sdk.h and logos_sdk.cpp in " << outputDir << "\n"; + return 0; +} + +static void printUsage(const char* progName) +{ + std::cerr << "Usage:\n" + << " " << progName << " --provider-dispatch
[--output-dir ]\n" + << " " << progName << " --consumer-wrappers
--module-name [--output-dir ]\n" + << " " << progName << " --umbrella --deps --output-dir \n"; +} + +static std::string getArg(const std::vector& args, const std::string& flag) +{ + for (size_t i = 0; i < args.size(); ++i) { + if (args[i] == flag && i + 1 < args.size()) + return args[i + 1]; + } + return ""; +} + +static bool hasFlag(const std::vector& args, const std::string& flag) +{ + for (const auto& a : args) + if (a == flag) return true; + return false; +} + +int main(int argc, char* argv[]) +{ + std::vector args; + for (int i = 0; i < argc; ++i) + args.emplace_back(argv[i]); + + std::string outputDir = getArg(args, "--output-dir"); + + if (hasFlag(args, "--provider-dispatch")) { + std::string header = getArg(args, "--provider-dispatch"); + if (header.empty()) { + printUsage(argv[0]); + return 1; + } + return generateProviderDispatch(header, outputDir); + } + + if (hasFlag(args, "--consumer-wrappers")) { + std::string header = getArg(args, "--consumer-wrappers"); + std::string moduleName = getArg(args, "--module-name"); + if (header.empty() || moduleName.empty()) { + printUsage(argv[0]); + return 1; + } + return generateConsumerWrappers(header, moduleName, outputDir); + } + + if (hasFlag(args, "--umbrella")) { + std::string depsStr = getArg(args, "--deps"); + if (depsStr.empty() || outputDir.empty()) { + printUsage(argv[0]); + return 1; + } + std::vector deps; + std::istringstream dstream(depsStr); + std::string dep; + while (std::getline(dstream, dep, ',')) { + dep = trim(dep); + if (!dep.empty()) deps.push_back(dep); + } + return generateUmbrella(outputDir, deps); + } + + printUsage(argv[0]); + return 1; +} diff --git a/nix/bin.nix b/nix/bin.nix index 674b567..c5c9fe5 100644 --- a/nix/bin.nix +++ b/nix/bin.nix @@ -1,4 +1,4 @@ -# Builds the logos-cpp-generator binary +# Builds the logos-cpp-generator and logos-native-generator binaries { pkgs, common, src }: pkgs.stdenv.mkDerivation { @@ -14,12 +14,19 @@ pkgs.stdenv.mkDerivation { buildPhase = '' runHook preBuild - # Build generator + # Build Qt-based generator mkdir -p build-generator cd build-generator cmake ../cpp-generator -GNinja $cmakeFlags ninja cd .. + + # Build native generator (no Qt dependency) + mkdir -p build-native-generator + cd build-native-generator + cmake ../native-generator -GNinja + ninja + cd .. runHook postBuild ''; @@ -27,11 +34,14 @@ pkgs.stdenv.mkDerivation { installPhase = '' runHook preInstall - # Install generator binary + # Install generator binaries mkdir -p $out/bin if [ -f build-generator/bin/logos-cpp-generator ]; then cp build-generator/bin/logos-cpp-generator $out/bin/ fi + if [ -f build-native-generator/logos-native-generator ]; then + cp build-native-generator/logos-native-generator $out/bin/ + fi runHook postInstall ''; diff --git a/nix/include.nix b/nix/include.nix index 91dd285..ca62355 100644 --- a/nix/include.nix +++ b/nix/include.nix @@ -18,6 +18,7 @@ pkgs.stdenv.mkDerivation { # Install headers with proper structure mkdir -p $out/include/core mkdir -p $out/include/cpp + mkdir -p $out/include/cpp/native mkdir -p $out/include/cpp/implementations/qt_local mkdir -p $out/include/cpp/implementations/qt_remote mkdir -p $out/include/cpp/implementations/mock @@ -61,6 +62,20 @@ pkgs.stdenv.mkDerivation { fi done + # Install native headers and sources + for file in logos_value.h logos_value.cpp \ + logos_value_qt.h logos_value_qt.cpp \ + logos_native_types.h \ + logos_macros.h \ + logos_native_provider.h logos_native_provider.cpp \ + logos_native_client.h logos_native_client.cpp \ + logos_native_api.h logos_native_api.cpp \ + logos_native_adapter.h logos_native_adapter.cpp; do + if [ -f cpp/native/$file ]; then + cp cpp/native/$file $out/include/cpp/native/ + fi + done + if [ -f cpp/logos_mode.h ]; then cp cpp/logos_mode.h $out/include/ fi