From 1bc101df1f368cce728dbbedf0a433266beb5129 Mon Sep 17 00:00:00 2001 From: Dario Lipicar Date: Tue, 16 Jun 2026 21:00:42 -0300 Subject: [PATCH] feat: cpp-generator consumes logos-lidl; delete embedded frontend (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: cpp-generator consumes logos-lidl; delete embedded frontend The canonical LIDL frontend now lives in logos-lidl. cpp-generator links it and keeps only the C++/Qt-specific parts (impl-header parsing, the gen_client/ gen_cdylib backends, the Qt type-name mapping). - Delete the embedded lidl_lexer/parser/serializer/validator/ast. - Add experimental/lidl_compat.h: brings logos-lidl's std AST into the global scope the backends use (via `using`), a qs() std::string→QString helper, a QTextStream< * fix: pin logos-lidl to the C-ABI commit + lock it The logos-lidl input was declared in flake.nix but missing from flake.lock, so override chains that don't reach the nested input (the doctest harness building a scaffolded module) couldn't resolve it. Pin the branch rev and lock it so the component is self-contained. Re-point at master once logos-lidl lands. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: re-point logos-lidl to merged master (#5) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- cpp-generator/CMakeLists.txt | 38 +- .../experimental/impl_header_parser.cpp | 43 +-- .../experimental/impl_header_parser.h | 2 +- cpp-generator/experimental/lidl_ast.h | 97 ----- cpp-generator/experimental/lidl_compat.h | 57 +++ cpp-generator/experimental/lidl_emit_common.h | 10 +- .../experimental/lidl_gen_cdylib.cpp | 28 +- cpp-generator/experimental/lidl_gen_cdylib.h | 2 +- .../experimental/lidl_gen_client.cpp | 36 +- cpp-generator/experimental/lidl_gen_client.h | 2 +- cpp-generator/experimental/lidl_lexer.cpp | 115 ------ cpp-generator/experimental/lidl_lexer.h | 37 -- cpp-generator/experimental/lidl_parser.cpp | 236 ------------ cpp-generator/experimental/lidl_parser.h | 17 - .../experimental/lidl_serializer.cpp | 49 --- cpp-generator/experimental/lidl_serializer.h | 9 - cpp-generator/experimental/lidl_validator.cpp | 62 ---- cpp-generator/experimental/lidl_validator.h | 16 - cpp-generator/legacy/main.cpp | 14 +- cpp-generator/main.cpp | 23 +- flake.lock | 26 ++ flake.nix | 13 +- nix/bin.nix | 26 +- nix/tests.nix | 6 +- tests/experimental/CMakeLists.txt | 16 +- .../experimental/test_impl_header_parser.cpp | 18 +- tests/experimental/test_lidl_gen_client.cpp | 24 +- tests/experimental/test_lidl_lexer.cpp | 189 ---------- tests/experimental/test_lidl_parser.cpp | 344 ------------------ tests/experimental/test_lidl_serializer.cpp | 133 ------- tests/experimental/test_lidl_validator.cpp | 148 -------- 31 files changed, 227 insertions(+), 1609 deletions(-) delete mode 100644 cpp-generator/experimental/lidl_ast.h create mode 100644 cpp-generator/experimental/lidl_compat.h delete mode 100644 cpp-generator/experimental/lidl_lexer.cpp delete mode 100644 cpp-generator/experimental/lidl_lexer.h delete mode 100644 cpp-generator/experimental/lidl_parser.cpp delete mode 100644 cpp-generator/experimental/lidl_parser.h delete mode 100644 cpp-generator/experimental/lidl_serializer.cpp delete mode 100644 cpp-generator/experimental/lidl_serializer.h delete mode 100644 cpp-generator/experimental/lidl_validator.cpp delete mode 100644 cpp-generator/experimental/lidl_validator.h delete mode 100644 tests/experimental/test_lidl_lexer.cpp delete mode 100644 tests/experimental/test_lidl_parser.cpp delete mode 100644 tests/experimental/test_lidl_serializer.cpp delete mode 100644 tests/experimental/test_lidl_validator.cpp diff --git a/cpp-generator/CMakeLists.txt b/cpp-generator/CMakeLists.txt index f4edddf..4b3b01c 100644 --- a/cpp-generator/CMakeLists.txt +++ b/cpp-generator/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.14) project(LogosCppGenerator) -set(CMAKE_CXX_STANDARD 11) +# C++17: the generator consumes logos-lidl (built with C++17), and its AST +# aggregate-initialization (e.g. `{TypeExpr::Primitive, "bool", {}}` in the +# impl-header parser) needs aggregates-with-default-member-initializers. +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) @@ -9,21 +12,23 @@ set(CMAKE_AUTOMOC ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) +# logos-lidl: the canonical, language-neutral LIDL frontend (lexer/parser/AST/ +# serializer/validator). The generator links it instead of embedding its own +# copy; only the C++/Qt-specific backends (impl-header parsing, gen_client, +# gen_cdylib) live here now. +find_package(logos-lidl REQUIRED) + add_executable(logos-cpp-generator main.cpp legacy/main.cpp legacy/generator_lib.cpp experimental/lidl_emit_common.cpp - experimental/lidl_lexer.cpp - experimental/lidl_parser.cpp - experimental/lidl_serializer.cpp - experimental/lidl_validator.cpp experimental/lidl_gen_client.cpp experimental/lidl_gen_cdylib.cpp experimental/impl_header_parser.cpp ) -target_link_libraries(logos-cpp-generator PRIVATE Qt${QT_VERSION_MAJOR}::Core) +target_link_libraries(logos-cpp-generator PRIVATE Qt${QT_VERSION_MAJOR}::Core logos-lidl::logos_lidl) # nlohmann_json: pulled in transitively by logos_provider_object.h → # logos_provider_interface.h / logos_json_convert.h (header-only use). @@ -62,24 +67,3 @@ target_include_directories(logos-cpp-generator PRIVATE set_target_properties(logos-cpp-generator PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ) - -# ── LIDL frontend distribution ─────────────────────────────────────────────── -# logos-qt-sdk's logos-qt-generator (which emits ALL Qt glue: universal, -# cdylib, ui) compiles these sources directly — source-level sharing keeps the -# two generators on one frontend without a binary-ABI coupling. -install(FILES - experimental/lidl_ast.h - experimental/lidl_lexer.h - experimental/lidl_lexer.cpp - experimental/lidl_parser.h - experimental/lidl_parser.cpp - experimental/lidl_serializer.h - experimental/lidl_serializer.cpp - experimental/lidl_validator.h - experimental/lidl_validator.cpp - experimental/impl_header_parser.h - experimental/impl_header_parser.cpp - experimental/lidl_emit_common.h - experimental/lidl_emit_common.cpp - DESTINATION share/lidl-frontend -) diff --git a/cpp-generator/experimental/impl_header_parser.cpp b/cpp-generator/experimental/impl_header_parser.cpp index a321688..d6e2474 100644 --- a/cpp-generator/experimental/impl_header_parser.cpp +++ b/cpp-generator/experimental/impl_header_parser.cpp @@ -126,7 +126,7 @@ static bool parseMethodLine(const QString& line, MethodDecl& out) if (nameStart >= nameEnd) return false; - out.name = prefix.mid(nameStart, nameEnd - nameStart); + const QString methodName = prefix.mid(nameStart, nameEnd - nameStart); // Reject if the extracted name is a C++ keyword — this filters out // member variable declarations like "std::function onEvent" @@ -136,8 +136,9 @@ static bool parseMethodLine(const QString& line, MethodDecl& out) "auto", "return", "if", "else", "for", "while", "do", "switch", "case", "break", "continue", "const", "static", "inline", "virtual" }; - if (cppKeywords.contains(out.name)) + if (cppKeywords.contains(methodName)) return false; + out.name = methodName.toStdString(); QString retTypeStr = prefix.left(nameStart).trimmed(); out.returnType = cppTypeToLidl(retTypeStr); // Flag methods whose impl returns LogosMap / LogosList so the generator @@ -177,9 +178,9 @@ static bool parseMethodLine(const QString& line, MethodDecl& out) if (pNameStart >= pNameEnd) continue; ParamDecl pd; - pd.name = p.mid(pNameStart, pNameEnd - pNameStart); + pd.name = p.mid(pNameStart, pNameEnd - pNameStart).toStdString(); pd.type = cppTypeToLidl(p.left(pNameStart)); - out.params.append(pd); + out.params.push_back(pd); } } @@ -219,31 +220,31 @@ ImplParseResult parseImplHeader(const QString& headerPath, return result; } QJsonObject obj = doc.object(); - result.module.name = obj.value("name").toString(); - result.module.version = obj.value("version").toString(); - result.module.description = obj.value("description").toString(); - result.module.category = obj.value("category").toString(); + result.module.name = obj.value("name").toString().toStdString(); + result.module.version = obj.value("version").toString().toStdString(); + result.module.description = obj.value("description").toString().toStdString(); + result.module.category = obj.value("category").toString().toStdString(); QJsonArray deps = obj.value("dependencies").toArray(); for (const QJsonValue& v : deps) - result.module.depends.append(v.toString()); + result.module.depends.push_back(v.toString().toStdString()); // Read events declared in metadata.json QJsonArray events = obj.value("events").toArray(); for (const QJsonValue& ev : events) { QJsonObject evObj = ev.toObject(); EventDecl ed; - ed.name = evObj.value("name").toString(); - ed.description = evObj.value("description").toString(); + ed.name = evObj.value("name").toString().toStdString(); + ed.description = evObj.value("description").toString().toStdString(); QJsonArray params = evObj.value("params").toArray(); for (const QJsonValue& pv : params) { QJsonObject po = pv.toObject(); ParamDecl pd; - pd.name = po.value("name").toString(); + pd.name = po.value("name").toString().toStdString(); pd.type = cppTypeToLidl(po.value("type").toString()); - ed.params.append(pd); + ed.params.push_back(pd); } - if (!ed.name.isEmpty()) - result.module.events.append(ed); + if (!ed.name.empty()) + result.module.events.push_back(ed); } } @@ -421,8 +422,8 @@ ImplParseResult parseImplHeader(const QString& headerPath, EventDecl ed; ed.name = md.name; ed.params = md.params; - ed.description = joinDocLines(pendingDoc); - result.module.events.append(ed); + ed.description = joinDocLines(pendingDoc).toStdString(); + result.module.events.push_back(ed); } } pendingDoc.clear(); @@ -457,9 +458,9 @@ ImplParseResult parseImplHeader(const QString& headerPath, "onContextReady", "modules", "modulePath", "instanceId", "instancePersistencePath" }; - if (!reserved.contains(md.name)) { - md.description = joinDocLines(pendingDoc); - result.module.methods.append(md); + if (!reserved.contains(qs(md.name))) { + md.description = joinDocLines(pendingDoc).toStdString(); + result.module.methods.push_back(md); } } } @@ -469,7 +470,7 @@ ImplParseResult parseImplHeader(const QString& headerPath, } done: - if (result.module.methods.isEmpty()) { + if (result.module.methods.empty()) { err << "Warning: no public methods found in class " << className << " in " << headerPath << "\n"; } diff --git a/cpp-generator/experimental/impl_header_parser.h b/cpp-generator/experimental/impl_header_parser.h index d5ecd08..452978c 100644 --- a/cpp-generator/experimental/impl_header_parser.h +++ b/cpp-generator/experimental/impl_header_parser.h @@ -1,7 +1,7 @@ #ifndef IMPL_HEADER_PARSER_H #define IMPL_HEADER_PARSER_H -#include "lidl_ast.h" +#include "lidl_compat.h" #include #include diff --git a/cpp-generator/experimental/lidl_ast.h b/cpp-generator/experimental/lidl_ast.h deleted file mode 100644 index b29b70a..0000000 --- a/cpp-generator/experimental/lidl_ast.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef LIDL_AST_H -#define LIDL_AST_H - -#include -#include -#include - -struct TypeExpr { - enum Kind { Primitive, Array, Map, Optional, Named }; - Kind kind = Primitive; - QString name; // primitive name ("tstr","int",...) or custom type name - QVector elements; // Array: [0]=elem, Map: [0]=key [1]=val, Optional: [0]=inner - - bool operator==(const TypeExpr& o) const { - return kind == o.kind && name == o.name && elements == o.elements; - } - bool operator!=(const TypeExpr& o) const { return !(*this == o); } -}; - -struct FieldDecl { - QString name; - TypeExpr type; - bool optional = false; - - bool operator==(const FieldDecl& o) const { - return name == o.name && type == o.type && optional == o.optional; - } -}; - -struct ParamDecl { - QString name; - TypeExpr type; - - bool operator==(const ParamDecl& o) const { - return name == o.name && type == o.type; - } -}; - -struct MethodDecl { - QString name; - QVector params; - TypeExpr returnType; - // Doc comment adjacent to the method declaration (becomes "description"). - QString description; - // True when the impl returns LogosMap or LogosList (nlohmann::json). - // The generator will emit nlohmann→Qt conversion code in the glue layer. - bool jsonReturn = false; - // True when the impl returns StdLogosResult. - // The generator will emit a stdResultToQt() conversion in the glue layer. - bool resultReturn = false; - - bool operator==(const MethodDecl& o) const { - return name == o.name && params == o.params && returnType == o.returnType - && description == o.description - && jsonReturn == o.jsonReturn && resultReturn == o.resultReturn; - } -}; - -struct EventDecl { - QString name; - QVector params; - // Doc comment adjacent to the event declaration (becomes "description"). - QString description; - - bool operator==(const EventDecl& o) const { - return name == o.name && params == o.params && description == o.description; - } -}; - -struct TypeDecl { - QString name; - QVector fields; - - bool operator==(const TypeDecl& o) const { - return name == o.name && fields == o.fields; - } -}; - -struct ModuleDecl { - QString name; - QString version; - QString description; - QString category; - QStringList depends; - QVector types; - QVector methods; - QVector events; - - bool operator==(const ModuleDecl& o) const { - return name == o.name && version == o.version - && description == o.description && category == o.category - && depends == o.depends && types == o.types - && methods == o.methods && events == o.events; - } -}; - -#endif // LIDL_AST_H diff --git a/cpp-generator/experimental/lidl_compat.h b/cpp-generator/experimental/lidl_compat.h new file mode 100644 index 0000000..c556c9a --- /dev/null +++ b/cpp-generator/experimental/lidl_compat.h @@ -0,0 +1,57 @@ +#ifndef LIDL_COMPAT_H +#define LIDL_COMPAT_H + +// Bridge the cpp-generator's Qt-flavored codegen backends onto the canonical +// logos-lidl frontend. The lexer / parser / AST / serializer / validator now +// live in logos-lidl (std-typed, language-neutral); this header brings those +// types into the global scope the backends use unqualified and adds thin +// Qt-friendly shims so the existing emission code (QTextStream) keeps +// compiling. The backends themselves (impl-header parsing, gen_client, +// gen_cdylib) stay here — they are the C++/Qt-specific parts. + +#include "lidl/ast.hpp" +#include "lidl/parser.hpp" +#include "lidl/serializer.hpp" +#include "lidl/validator.hpp" + +#include +#include +#include + +// The canonical AST, in the global scope the generator backends reference it +// from (they predate the logos-lidl extraction and use the unqualified names). +using lidl::TypeExpr; +using lidl::FieldDecl; +using lidl::ParamDecl; +using lidl::MethodDecl; +using lidl::EventDecl; +using lidl::TypeDecl; +using lidl::ModuleDecl; + +// std::string -> QString, and let QTextStream accept std::string directly so +// emission of AST string fields (`s << md.name`) keeps compiling unchanged. +inline QString qs(const std::string& s) { return QString::fromStdString(s); } +inline QTextStream& operator<<(QTextStream& s, const std::string& v) +{ + return s << QString::fromStdString(v); +} + +// Name-compatible shims over the canonical frontend so the call sites that used +// the deleted experimental lexer/parser/serializer/validator keep their shape. +using LidlParseResult = lidl::ParseResult; +using LidlValidationResult = lidl::ValidationResult; + +inline lidl::ParseResult lidlParse(const QString& source) +{ + return lidl::parse(source.toStdString()); +} +inline QString lidlSerialize(const ModuleDecl& module) +{ + return QString::fromStdString(lidl::serialize(module)); +} +inline lidl::ValidationResult lidlValidate(const ModuleDecl& module) +{ + return lidl::validate(module); +} + +#endif // LIDL_COMPAT_H diff --git a/cpp-generator/experimental/lidl_emit_common.h b/cpp-generator/experimental/lidl_emit_common.h index ac366c6..cfa3aaa 100644 --- a/cpp-generator/experimental/lidl_emit_common.h +++ b/cpp-generator/experimental/lidl_emit_common.h @@ -1,11 +1,11 @@ -// Shared emit helpers used by every code-emitting backend (the Qt-free -// client/wrapper generation here in cpp-sdk AND the Qt glue generation in -// logos-qt-sdk's logos-qt-generator). Distributed with the LIDL frontend -// sources (share/lidl-frontend/) so external generators compile them in. +// Shared LIDL-type -> target-type-name mapping used by the C++/Qt code-emitting +// backends. These are the language-specific half of codegen (the canonical +// frontend — lexer/parser/AST/serializer/validator — lives in logos-lidl); each +// SDK keeps its own type-name mapping (Qt vs std vs Rust). #pragma once #include -#include "lidl_ast.h" +#include "lidl_compat.h" QString lidlToPascalCase(const QString& name); QString lidlTypeToQt(const TypeExpr& te); diff --git a/cpp-generator/experimental/lidl_gen_cdylib.cpp b/cpp-generator/experimental/lidl_gen_cdylib.cpp index 871edc1..e480df6 100644 --- a/cpp-generator/experimental/lidl_gen_cdylib.cpp +++ b/cpp-generator/experimental/lidl_gen_cdylib.cpp @@ -83,12 +83,12 @@ void emitInterfaceJson(QTextStream& s, const ModuleDecl& module) for (const MethodDecl& md : module.methods) { s << " {\n nlohmann::json obj;\n"; s << " obj[\"name\"] = \"" << md.name << "\";\n"; - if (!md.description.isEmpty()) { - QString esc = md.description; + if (!md.description.empty()) { + QString esc = qs(md.description); esc.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n"); s << " obj[\"description\"] = \"" << esc << "\";\n"; } - QString sig = md.name + "("; + QString sig = qs(md.name) + "("; for (int i = 0; i < md.params.size(); ++i) { sig += lidlTypeToQt(md.params[i].type); if (i + 1 < md.params.size()) sig += ","; @@ -97,7 +97,7 @@ void emitInterfaceJson(QTextStream& s, const ModuleDecl& module) s << " obj[\"signature\"] = \"" << sig << "\";\n"; s << " obj[\"returnType\"] = \"" << lidlTypeToQt(md.returnType) << "\";\n"; s << " obj[\"isInvokable\"] = true;\n"; - if (!md.params.isEmpty()) { + if (!md.params.empty()) { s << " nlohmann::json params = nlohmann::json::array();\n"; for (const ParamDecl& pd : md.params) { s << " params.push_back({{\"type\", \"" << lidlTypeToQt(pd.type) @@ -111,19 +111,19 @@ void emitInterfaceJson(QTextStream& s, const ModuleDecl& module) s << " {\n nlohmann::json obj;\n"; s << " obj[\"type\"] = \"event\";\n"; s << " obj[\"name\"] = \"" << ed.name << "\";\n"; - if (!ed.description.isEmpty()) { - QString esc = ed.description; + if (!ed.description.empty()) { + QString esc = qs(ed.description); esc.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n"); s << " obj[\"description\"] = \"" << esc << "\";\n"; } - QString sig = ed.name + "("; + QString sig = qs(ed.name) + "("; for (int i = 0; i < ed.params.size(); ++i) { sig += lidlTypeToQt(ed.params[i].type); if (i + 1 < ed.params.size()) sig += ","; } sig += ")"; s << " obj[\"signature\"] = \"" << sig << "\";\n"; - if (!ed.params.isEmpty()) { + if (!ed.params.empty()) { s << " nlohmann::json params = nlohmann::json::array();\n"; for (const ParamDecl& pd : ed.params) { s << " params.push_back({{\"type\", \"" << lidlTypeToQt(pd.type) @@ -146,7 +146,7 @@ bool lidlCdylibSupported(const ModuleDecl& module, QString* error) if (error) *error = QString("method '%1': parameter '%2' has a type outside the " "cdylib-supported (Qt-free) subset") - .arg(md.name, pd.name); + .arg(qs(md.name), qs(pd.name)); return false; } } @@ -155,12 +155,12 @@ bool lidlCdylibSupported(const ModuleDecl& module, QString* error) // name is the in-memory void from the header path. Treat both as void. const bool voidReturn = md.returnType.name == "void" - || (md.returnType.kind == TypeExpr::Primitive && md.returnType.name.isEmpty()); + || (md.returnType.kind == TypeExpr::Primitive && md.returnType.name.empty()); if (!voidReturn && !md.jsonReturn && !md.resultReturn && !typeSupported(md.returnType, /*isReturn=*/true)) { if (error) *error = QString("method '%1': return type outside the cdylib-supported " - "(Qt-free) subset").arg(md.name); + "(Qt-free) subset").arg(qs(md.name)); return false; } } @@ -170,7 +170,7 @@ bool lidlCdylibSupported(const ModuleDecl& module, QString* error) if (error) *error = QString("event '%1': parameter '%2' has a type outside the " "cdylib-supported (Qt-free) subset") - .arg(ed.name, pd.name); + .arg(qs(ed.name), qs(pd.name)); return false; } } @@ -374,7 +374,7 @@ QString lidlMakeModuleImplExports(const ModuleDecl& module, for (const MethodDecl& md : module.methods) { s << " if (m == \"" << md.name << "\") {\n"; s << " if (args.size() < " << md.params.size() << ") return nullptr;\n"; - QString call = "lidlImpl()." + md.name + "("; + QString call = "lidlImpl()." + qs(md.name) + "("; for (int i = 0; i < md.params.size(); ++i) { call += jsonArgToStd(md.params[i].type, QString("args.at(%1)").arg(i)); @@ -385,7 +385,7 @@ QString lidlMakeModuleImplExports(const ModuleDecl& module, // lidlBuiltinType); empty name is the header path's in-memory void. const bool voidReturn = md.returnType.name == "void" - || (md.returnType.kind == TypeExpr::Primitive && md.returnType.name.isEmpty()) + || (md.returnType.kind == TypeExpr::Primitive && md.returnType.name.empty()) || lidlTypeToQt(md.returnType) == "void"; if (voidReturn) { s << " " << call << ";\n"; diff --git a/cpp-generator/experimental/lidl_gen_cdylib.h b/cpp-generator/experimental/lidl_gen_cdylib.h index 584eaf6..0fd4f42 100644 --- a/cpp-generator/experimental/lidl_gen_cdylib.h +++ b/cpp-generator/experimental/lidl_gen_cdylib.h @@ -1,7 +1,7 @@ #ifndef LIDL_GEN_CDYLIB_H #define LIDL_GEN_CDYLIB_H -#include "lidl_ast.h" +#include "lidl_compat.h" #include // --------------------------------------------------------------------------- diff --git a/cpp-generator/experimental/lidl_gen_client.cpp b/cpp-generator/experimental/lidl_gen_client.cpp index c5bfbab..7119f0f 100644 --- a/cpp-generator/experimental/lidl_gen_client.cpp +++ b/cpp-generator/experimental/lidl_gen_client.cpp @@ -1,7 +1,5 @@ #include "lidl_gen_client.h" #include "lidl_emit_common.h" -#include "lidl_parser.h" -#include "lidl_validator.h" #include #include @@ -23,7 +21,7 @@ static bool isRefType(const QString& qt) || qt == "QVariantList" || qt == "QVariantMap" || qt == "QByteArray"; } -static void emitParam(QTextStream& s, const QString& qtType, const QString& name) +static void emitParam(QTextStream& s, const QString& qtType, const std::string& name) { if (isRefType(qtType)) s << "const " << qtType << "& " << name; @@ -64,7 +62,7 @@ static QString asyncDefaultVal(const QString& qt) QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode) { - QString className = lidlToPascalCase(module.name); + QString className = lidlToPascalCase(qs(module.name)); QString h; QTextStream s(&h); @@ -117,7 +115,7 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode) } // Optional error out-channel: pass a logos::CallError* to distinguish // a failed remote call from a legitimately default-valued result. - if (!md.params.isEmpty()) s << ", "; + if (!md.params.empty()) s << ", "; s << "logos::CallError* err = nullptr);\n"; QString asyncCb = (ret == "void") ? QString("std::function") @@ -127,7 +125,7 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode) emitParam(s, lidlTypeToQt(md.params[i].type), md.params[i].name); if (i + 1 < md.params.size()) s << ", "; } - if (!md.params.isEmpty()) s << ", "; + if (!md.params.empty()) s << ", "; s << asyncCb << " callback, Timeout timeout = Timeout());\n"; } @@ -157,8 +155,8 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode) QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode) { - QString className = lidlToPascalCase(module.name); - QString headerRel = module.name + "_api.h"; + QString className = lidlToPascalCase(qs(module.name)); + QString headerRel = qs(module.name) + "_api.h"; QString c; QTextStream s(&c); @@ -169,7 +167,7 @@ QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode) // mode, the runtime m_moduleName member in Bound (interface) mode. const QString targetExpr = (bindMode == BindMode::Bound) ? QStringLiteral("m_moduleName") - : (QStringLiteral("\"") + module.name + QStringLiteral("\"")); + : (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 @@ -292,15 +290,15 @@ QString lidlMakeSource(const ModuleDecl& module, BindMode bindMode) QString lidlGenerateMetadataJson(const ModuleDecl& module) { QJsonObject obj; - obj["name"] = module.name; - obj["version"] = module.version.isEmpty() ? "0.0.0" : module.version; + obj["name"] = qs(module.name); + obj["version"] = module.version.empty() ? QStringLiteral("0.0.0") : qs(module.version); obj["type"] = "core"; - obj["category"] = module.category.isEmpty() ? "general" : module.category; - obj["description"] = module.description; - obj["main"] = module.name + "_plugin"; + 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 QString& d : module.depends) - deps.append(d); + for (const std::string& d : module.depends) + deps.append(qs(d)); obj["dependencies"] = deps; QJsonDocument doc(obj); return doc.toJson(QJsonDocument::Indented); @@ -324,14 +322,14 @@ int lidlGenerateClientStubs(const QString& lidlPath, const QString& outputDir, if (pr.hasError()) { err << lidlPath << ":" << pr.errorLine << ":" << pr.errorColumn << ": " << pr.error << "\n"; return 4; } LidlValidationResult vr = lidlValidate(pr.module); - if (vr.hasErrors()) { for (const QString& e : vr.errors) err << lidlPath << ": " << e << "\n"; return 5; } + if (vr.hasErrors()) { for (const std::string& e : vr.errors) err << lidlPath << ": " << e << "\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(mod.name + "_api.h"); - QString sourceAbs = QDir(genDirPath).filePath(mod.name + "_api.cpp"); + 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()); } diff --git a/cpp-generator/experimental/lidl_gen_client.h b/cpp-generator/experimental/lidl_gen_client.h index bb8cc48..2b4a15c 100644 --- a/cpp-generator/experimental/lidl_gen_client.h +++ b/cpp-generator/experimental/lidl_gen_client.h @@ -1,7 +1,7 @@ #ifndef LIDL_GEN_CLIENT_H #define LIDL_GEN_CLIENT_H -#include "lidl_ast.h" +#include "lidl_compat.h" #include "../legacy/generator_lib.h" // BindMode #include #include diff --git a/cpp-generator/experimental/lidl_lexer.cpp b/cpp-generator/experimental/lidl_lexer.cpp deleted file mode 100644 index 14b7d5f..0000000 --- a/cpp-generator/experimental/lidl_lexer.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "lidl_lexer.h" - -#include - -static LidlToken makeToken(LidlToken::Type type, const QString& text, int line, int col) -{ - LidlToken t; - t.type = type; - t.text = text; - t.line = line; - t.column = col; - return t; -} - -static const QHash& lidlKeywords() -{ - static const QHash kw = { - {"module", LidlToken::Module}, - {"type", LidlToken::TypeKw}, - {"method", LidlToken::Method}, - {"event", LidlToken::Event}, - {"version", LidlToken::Version}, - {"description", LidlToken::Description}, - {"category", LidlToken::Category}, - {"depends", LidlToken::Depends}, - }; - return kw; -} - -static bool isIdentStart(QChar c) { return c.isLetter() || c == '_'; } -static bool isIdentChar(QChar c) { return c.isLetterOrNumber() || c == '_'; } - -LidlLexResult lidlTokenize(const QString& source) -{ - LidlLexResult result; - int pos = 0, line = 1, col = 1; - const int len = source.size(); - - auto makeError = [&](const QString& msg) { - result.error = msg; - result.errorLine = line; - result.errorColumn = col; - }; - - while (pos < len) { - QChar c = source[pos]; - - if (c == ' ' || c == '\t' || c == '\r') { ++pos; ++col; continue; } - if (c == '\n') { ++pos; ++line; col = 1; continue; } - if (c == ';') { while (pos < len && source[pos] != '\n') ++pos; continue; } - - int startLine = line, startCol = col; - - if (c == '-' && pos + 1 < len && source[pos + 1] == '>') { - result.tokens.append(makeToken(LidlToken::Arrow, "->", startLine, startCol)); - pos += 2; col += 2; continue; - } - - LidlToken::Type symType = LidlToken::Error; - switch (c.unicode()) { - case '{': symType = LidlToken::LBrace; break; - case '}': symType = LidlToken::RBrace; break; - case '(': symType = LidlToken::LParen; break; - case ')': symType = LidlToken::RParen; break; - case '[': symType = LidlToken::LBracket; break; - case ']': symType = LidlToken::RBracket; break; - case ':': symType = LidlToken::Colon; break; - case ',': symType = LidlToken::Comma; break; - case '?': symType = LidlToken::Question; break; - default: break; - } - if (symType != LidlToken::Error) { - result.tokens.append(makeToken(symType, QString(c), startLine, startCol)); - ++pos; ++col; continue; - } - - if (c == '"') { - ++pos; ++col; - QString value; - while (pos < len && source[pos] != '"') { - if (source[pos] == '\n') { makeError("Unterminated string literal"); return result; } - if (source[pos] == '\\' && pos + 1 < len) { - ++pos; ++col; - QChar esc = source[pos]; - if (esc == 'n') value += '\n'; - else if (esc == 't') value += '\t'; - else if (esc == '\\') value += '\\'; - else if (esc == '"') value += '"'; - else value += esc; - } else { value += source[pos]; } - ++pos; ++col; - } - if (pos >= len) { makeError("Unterminated string literal"); return result; } - ++pos; ++col; - result.tokens.append(makeToken(LidlToken::StringLit, value, startLine, startCol)); - continue; - } - - if (isIdentStart(c)) { - int start = pos; - while (pos < len && isIdentChar(source[pos])) { ++pos; ++col; } - QString word = source.mid(start, pos - start); - const auto& kw = lidlKeywords(); - auto it = kw.find(word); - result.tokens.append(makeToken(it != kw.end() ? it.value() : LidlToken::Ident, word, startLine, startCol)); - continue; - } - - makeError(QString("Unexpected character '%1'").arg(c)); - return result; - } - - result.tokens.append(makeToken(LidlToken::Eof, QString(), line, col)); - return result; -} diff --git a/cpp-generator/experimental/lidl_lexer.h b/cpp-generator/experimental/lidl_lexer.h deleted file mode 100644 index 60c4453..0000000 --- a/cpp-generator/experimental/lidl_lexer.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef LIDL_LEXER_H -#define LIDL_LEXER_H - -#include -#include - -struct LidlToken { - enum Type { - // Keywords - Module, TypeKw, Method, Event, - Version, Description, Category, Depends, - // Literals - Ident, StringLit, - // Symbols - LBrace, RBrace, LParen, RParen, LBracket, RBracket, - Colon, Comma, Arrow, Question, - // Special - Eof, Error - }; - - Type type = Eof; - QString text; - int line = 0; - int column = 0; -}; - -struct LidlLexResult { - QVector tokens; - QString error; - int errorLine = 0; - int errorColumn = 0; - bool hasError() const { return !error.isEmpty(); } -}; - -LidlLexResult lidlTokenize(const QString& source); - -#endif // LIDL_LEXER_H diff --git a/cpp-generator/experimental/lidl_parser.cpp b/cpp-generator/experimental/lidl_parser.cpp deleted file mode 100644 index 318f7a1..0000000 --- a/cpp-generator/experimental/lidl_parser.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "lidl_parser.h" -#include "lidl_lexer.h" - -#include - -static const QSet& lidlBuiltinTypes() -{ - static const QSet bt = { - "tstr", "bstr", "int", "uint", "float64", "bool", "result", "any" - }; - return bt; -} - -class Parser { -public: - explicit Parser(const QVector& tokens) : m_tokens(tokens) {} - - LidlParseResult parse() { - LidlParseResult result; - if (!parseModule(result.module)) { - result.error = m_error; result.errorLine = m_errorLine; result.errorColumn = m_errorColumn; - } - return result; - } - -private: - const QVector& m_tokens; - int m_pos = 0; - QString m_error; - int m_errorLine = 0, m_errorColumn = 0; - - const LidlToken& current() const { return m_tokens[m_pos < m_tokens.size() ? m_pos : m_tokens.size() - 1]; } - bool at(LidlToken::Type type) const { return current().type == type; } - bool consume(LidlToken::Type type) { if (!at(type)) return false; ++m_pos; return true; } - - // The LIDL keywords (module/type/method/event/version/description/ - // category/depends) are reserved only *structurally* — at the start of - // a declaration. In a *name* position (a module/type/field/method/ - // event name, a parameter name, or a named-type reference) they are - // ordinary identifiers. A module with, say, an event parameter named - // `version` serializes to `event versionReady(version: tstr)`, and the - // consumer must be able to read that back. So treat any keyword token - // as an identifier wherever a bare name is expected. The grammar stays - // unambiguous because every name position is reached only after a - // structural token has already been consumed. - bool atName() const { - switch (current().type) { - case LidlToken::Ident: - case LidlToken::Module: case LidlToken::TypeKw: case LidlToken::Method: - case LidlToken::Event: case LidlToken::Version: case LidlToken::Description: - case LidlToken::Category: case LidlToken::Depends: - return true; - default: - return false; - } - } - - bool expect(LidlToken::Type type, const QString& context) { - if (consume(type)) return true; - error(QString("Expected %1 in %2, got '%3'").arg(tokenTypeName(type), context, current().text)); - return false; - } - - void error(const QString& msg) { - if (!m_error.isEmpty()) return; - m_error = msg; m_errorLine = current().line; m_errorColumn = current().column; - } - - static QString tokenTypeName(LidlToken::Type t) { - switch (t) { - case LidlToken::Module: return "'module'"; case LidlToken::TypeKw: return "'type'"; - case LidlToken::Method: return "'method'"; case LidlToken::Event: return "'event'"; - case LidlToken::Version: return "'version'"; case LidlToken::Description: return "'description'"; - case LidlToken::Category: return "'category'"; case LidlToken::Depends: return "'depends'"; - case LidlToken::Ident: return "identifier"; case LidlToken::StringLit: return "string literal"; - case LidlToken::LBrace: return "'{'"; case LidlToken::RBrace: return "'}'"; - case LidlToken::LParen: return "'('"; case LidlToken::RParen: return "')'"; - case LidlToken::LBracket: return "'['"; case LidlToken::RBracket: return "']'"; - case LidlToken::Colon: return "':'"; case LidlToken::Comma: return "','"; - case LidlToken::Arrow: return "'->'"; case LidlToken::Question: return "'?'"; - case LidlToken::Eof: return "end of input"; case LidlToken::Error: return "error"; - } - return "unknown"; - } - - bool parseModule(ModuleDecl& mod) { - if (!expect(LidlToken::Module, "module declaration")) return false; - if (!atName()) { error("Expected module name"); return false; } - mod.name = current().text; ++m_pos; - if (!expect(LidlToken::LBrace, "module declaration")) return false; - if (!parseModuleBody(mod)) return false; - if (!expect(LidlToken::RBrace, "module declaration")) return false; - if (!at(LidlToken::Eof)) { error("Unexpected content after module closing '}'"); return false; } - return true; - } - - bool parseModuleBody(ModuleDecl& mod) { - while (!at(LidlToken::RBrace) && !at(LidlToken::Eof)) { - switch (current().type) { - case LidlToken::Version: case LidlToken::Description: case LidlToken::Category: case LidlToken::Depends: - if (!parseMetadata(mod)) return false; break; - case LidlToken::TypeKw: if (!parseTypeDef(mod)) return false; break; - case LidlToken::Method: if (!parseMethodDef(mod)) return false; break; - case LidlToken::Event: if (!parseEventDef(mod)) return false; break; - default: error(QString("Unexpected token '%1' in module body").arg(current().text)); return false; - } - } - return true; - } - - bool parseMetadata(ModuleDecl& mod) { - if (at(LidlToken::Version)) { ++m_pos; if (!at(LidlToken::StringLit)) { error("Expected string after 'version'"); return false; } mod.version = current().text; ++m_pos; return true; } - if (at(LidlToken::Description)) { ++m_pos; if (!at(LidlToken::StringLit)) { error("Expected string after 'description'"); return false; } mod.description = current().text; ++m_pos; return true; } - if (at(LidlToken::Category)) { ++m_pos; if (!at(LidlToken::StringLit)) { error("Expected string after 'category'"); return false; } mod.category = current().text; ++m_pos; return true; } - if (at(LidlToken::Depends)) { - ++m_pos; - if (!expect(LidlToken::LBracket, "depends list")) return false; - if (!at(LidlToken::RBracket)) { - if (!atName()) { error("Expected identifier in depends list"); return false; } - mod.depends.append(current().text); ++m_pos; - while (consume(LidlToken::Comma)) { - if (!atName()) { error("Expected identifier after ',' in depends list"); return false; } - mod.depends.append(current().text); ++m_pos; - } - } - if (!expect(LidlToken::RBracket, "depends list")) return false; - return true; - } - error("Expected metadata keyword"); return false; - } - - bool parseTypeDef(ModuleDecl& mod) { - if (!expect(LidlToken::TypeKw, "type definition")) return false; - TypeDecl td; - if (!atName()) { error("Expected type name"); return false; } - td.name = current().text; ++m_pos; - if (!expect(LidlToken::LBrace, "type definition")) return false; - while (!at(LidlToken::RBrace) && !at(LidlToken::Eof)) { FieldDecl fd; if (!parseFieldDef(fd)) return false; td.fields.append(fd); } - if (!expect(LidlToken::RBrace, "type definition")) return false; - mod.types.append(td); return true; - } - - bool parseFieldDef(FieldDecl& fd) { - fd.optional = consume(LidlToken::Question); - if (!atName()) { error("Expected field name"); return false; } - fd.name = current().text; ++m_pos; - if (!expect(LidlToken::Colon, "field definition")) return false; - return parseTypeExpr(fd.type); - } - - bool parseMethodDef(ModuleDecl& mod) { - if (!expect(LidlToken::Method, "method definition")) return false; - MethodDecl md; - if (!atName()) { error("Expected method name"); return false; } - md.name = current().text; ++m_pos; - if (!expect(LidlToken::LParen, "method parameters")) return false; - if (!parseParams(md.params)) return false; - if (!expect(LidlToken::RParen, "method parameters")) return false; - if (!expect(LidlToken::Arrow, "method return type")) return false; - if (!parseTypeExpr(md.returnType)) return false; - // Optional trailing doc: `-> ret description "..."`. Carries the - // method's doc comment across a .lidl round-trip so introspection - // (lm / getMethods) still surfaces it. - if (at(LidlToken::Description)) { - ++m_pos; - if (!at(LidlToken::StringLit)) { error("Expected string after method 'description'"); return false; } - md.description = current().text; ++m_pos; - } - // Restore the return-shape flags from the parsed type so a .lidl - // round-trip carries the same semantics the impl-header parser sets - // (it derives them from C++ types: StdLogosResult -> result, - // LogosMap/LogosList -> json). Without this, a header-first universal - // module (header -> .lidl -> cdylib backend) loses the flags and the - // cdylib codegen/eligibility mis-handles result / map / list returns. - { - const TypeExpr& rt = md.returnType; - md.resultReturn = (rt.kind == TypeExpr::Primitive && rt.name == "result"); - md.jsonReturn = - rt.kind == TypeExpr::Map - || (rt.kind == TypeExpr::Primitive && rt.name == "any") - || (rt.kind == TypeExpr::Array && rt.elements.size() == 1 - && rt.elements[0].kind == TypeExpr::Primitive - && rt.elements[0].name == "any"); - } - mod.methods.append(md); return true; - } - - bool parseEventDef(ModuleDecl& mod) { - if (!expect(LidlToken::Event, "event definition")) return false; - EventDecl ed; - if (!atName()) { error("Expected event name"); return false; } - ed.name = current().text; ++m_pos; - if (!expect(LidlToken::LParen, "event parameters")) return false; - if (!parseParams(ed.params)) return false; - if (!expect(LidlToken::RParen, "event parameters")) return false; - if (at(LidlToken::Description)) { - ++m_pos; - if (!at(LidlToken::StringLit)) { error("Expected string after event 'description'"); return false; } - ed.description = current().text; ++m_pos; - } - mod.events.append(ed); return true; - } - - bool parseParams(QVector& params) { - if (at(LidlToken::RParen)) return true; - ParamDecl p; if (!parseParam(p)) return false; params.append(p); - while (consume(LidlToken::Comma)) { ParamDecl p2; if (!parseParam(p2)) return false; params.append(p2); } - return true; - } - - bool parseParam(ParamDecl& p) { - if (!atName()) { error("Expected parameter name"); return false; } - p.name = current().text; ++m_pos; - if (!expect(LidlToken::Colon, "parameter")) return false; - return parseTypeExpr(p.type); - } - - bool parseTypeExpr(TypeExpr& te) { - if (consume(LidlToken::Question)) { te.kind = TypeExpr::Optional; te.elements.resize(1); return parseTypeExpr(te.elements[0]); } - if (consume(LidlToken::LBracket)) { te.kind = TypeExpr::Array; te.elements.resize(1); if (!parseTypeExpr(te.elements[0])) return false; return expect(LidlToken::RBracket, "array type"); } - if (consume(LidlToken::LBrace)) { te.kind = TypeExpr::Map; te.elements.resize(2); if (!parseTypeExpr(te.elements[0])) return false; if (!expect(LidlToken::Colon, "map type")) return false; if (!parseTypeExpr(te.elements[1])) return false; return expect(LidlToken::RBrace, "map type"); } - if (atName()) { - const QString& name = current().text; - te.kind = lidlBuiltinTypes().contains(name) ? TypeExpr::Primitive : TypeExpr::Named; - te.name = name; ++m_pos; return true; - } - error(QString("Expected type expression, got '%1'").arg(current().text)); return false; - } -}; - -LidlParseResult lidlParse(const QString& source) { - LidlLexResult lexResult = lidlTokenize(source); - if (lexResult.hasError()) { LidlParseResult pr; pr.error = lexResult.error; pr.errorLine = lexResult.errorLine; pr.errorColumn = lexResult.errorColumn; return pr; } - Parser parser(lexResult.tokens); - return parser.parse(); -} diff --git a/cpp-generator/experimental/lidl_parser.h b/cpp-generator/experimental/lidl_parser.h deleted file mode 100644 index 62f033a..0000000 --- a/cpp-generator/experimental/lidl_parser.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef LIDL_PARSER_H -#define LIDL_PARSER_H - -#include "lidl_ast.h" -#include - -struct LidlParseResult { - ModuleDecl module; - QString error; - int errorLine = 0; - int errorColumn = 0; - bool hasError() const { return !error.isEmpty(); } -}; - -LidlParseResult lidlParse(const QString& source); - -#endif // LIDL_PARSER_H diff --git a/cpp-generator/experimental/lidl_serializer.cpp b/cpp-generator/experimental/lidl_serializer.cpp deleted file mode 100644 index a22976e..0000000 --- a/cpp-generator/experimental/lidl_serializer.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "lidl_serializer.h" -#include - -// Escape a description for a "..."-delimited LIDL string literal (the lexer -// decodes \\ \" \n \t). Keeps method/event docs intact across a .lidl -// round-trip so introspection (lm / getMethods) still shows them. -static QString lidlEscapeStr(QString in) { - in.replace('\\', "\\\\"); - in.replace('"', "\\\""); - in.replace('\n', "\\n"); - in.replace('\t', "\\t"); - return in; -} - -static QString serializeTypeExpr(const TypeExpr& te) { - switch (te.kind) { - case TypeExpr::Primitive: case TypeExpr::Named: return te.name; - case TypeExpr::Array: return "[" + serializeTypeExpr(te.elements[0]) + "]"; - case TypeExpr::Map: return "{" + serializeTypeExpr(te.elements[0]) + ": " + serializeTypeExpr(te.elements[1]) + "}"; - case TypeExpr::Optional: return "? " + serializeTypeExpr(te.elements[0]); - } - return QString(); -} - -static void serializeParams(QTextStream& s, const QVector& params) { - for (int i = 0; i < params.size(); ++i) { - s << params[i].name << ": " << serializeTypeExpr(params[i].type); - if (i + 1 < params.size()) s << ", "; - } -} - -QString lidlSerialize(const ModuleDecl& module) { - QString out; - QTextStream s(&out); - s << "module " << module.name << " {\n"; - if (!module.version.isEmpty()) s << " version \"" << module.version << "\"\n"; - if (!module.description.isEmpty()) s << " description \"" << lidlEscapeStr(module.description) << "\"\n"; - if (!module.category.isEmpty()) s << " category \"" << module.category << "\"\n"; - s << " depends ["; - for (int i = 0; i < module.depends.size(); ++i) { s << module.depends[i]; if (i + 1 < module.depends.size()) s << ", "; } - s << "]\n"; - for (const TypeDecl& td : module.types) { s << "\n type " << td.name << " {\n"; for (const FieldDecl& fd : td.fields) { s << " "; if (fd.optional) s << "? "; s << fd.name << ": " << serializeTypeExpr(fd.type) << "\n"; } s << " }\n"; } - if (!module.methods.isEmpty()) s << "\n"; - for (const MethodDecl& md : module.methods) { s << " method " << md.name << "("; serializeParams(s, md.params); s << ") -> " << serializeTypeExpr(md.returnType); if (!md.description.isEmpty()) s << " description \"" << lidlEscapeStr(md.description) << "\""; s << "\n"; } - if (!module.events.isEmpty()) s << "\n"; - for (const EventDecl& ed : module.events) { s << " event " << ed.name << "("; serializeParams(s, ed.params); s << ")"; if (!ed.description.isEmpty()) s << " description \"" << lidlEscapeStr(ed.description) << "\""; s << "\n"; } - s << "}\n"; - return out; -} diff --git a/cpp-generator/experimental/lidl_serializer.h b/cpp-generator/experimental/lidl_serializer.h deleted file mode 100644 index 764986e..0000000 --- a/cpp-generator/experimental/lidl_serializer.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef LIDL_SERIALIZER_H -#define LIDL_SERIALIZER_H - -#include "lidl_ast.h" -#include - -QString lidlSerialize(const ModuleDecl& module); - -#endif // LIDL_SERIALIZER_H diff --git a/cpp-generator/experimental/lidl_validator.cpp b/cpp-generator/experimental/lidl_validator.cpp deleted file mode 100644 index d6e4e39..0000000 --- a/cpp-generator/experimental/lidl_validator.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "lidl_validator.h" -#include - -static const QSet& lidlBuiltinTypes() { - static const QSet bt = { "tstr", "bstr", "int", "uint", "float64", "bool", "result", "any" }; - return bt; -} - -class Validator { -public: - explicit Validator(const ModuleDecl& mod) : m_mod(mod) { for (const TypeDecl& td : mod.types) m_declaredTypes.insert(td.name); } - - LidlValidationResult validate() { - LidlValidationResult result; - if (m_mod.name.isEmpty()) result.errors.append("Module name is empty"); - - QSet seenTypes; - for (const TypeDecl& td : m_mod.types) { - if (lidlBuiltinTypes().contains(td.name)) result.errors.append(QString("Type '%1' shadows a builtin type").arg(td.name)); - if (seenTypes.contains(td.name)) result.errors.append(QString("Duplicate type definition '%1'").arg(td.name)); - seenTypes.insert(td.name); - for (const FieldDecl& fd : td.fields) validateTypeExpr(fd.type, result); - } - - QSet seenMethods; - for (const MethodDecl& md : m_mod.methods) { - if (seenMethods.contains(md.name)) result.errors.append(QString("Duplicate method definition '%1'").arg(md.name)); - seenMethods.insert(md.name); - validateTypeExpr(md.returnType, result); - QSet seenParams; - for (const ParamDecl& pd : md.params) { - validateTypeExpr(pd.type, result); - if (seenParams.contains(pd.name)) result.errors.append(QString("Duplicate parameter '%1' in method '%2'").arg(pd.name, md.name)); - seenParams.insert(pd.name); - } - } - - QSet seenEvents; - for (const EventDecl& ed : m_mod.events) { - if (seenEvents.contains(ed.name)) result.errors.append(QString("Duplicate event definition '%1'").arg(ed.name)); - seenEvents.insert(ed.name); - for (const ParamDecl& pd : ed.params) validateTypeExpr(pd.type, result); - } - return result; - } - -private: - const ModuleDecl& m_mod; - QSet m_declaredTypes; - - void validateTypeExpr(const TypeExpr& te, LidlValidationResult& result) { - switch (te.kind) { - case TypeExpr::Primitive: break; - case TypeExpr::Named: if (!m_declaredTypes.contains(te.name)) result.errors.append(QString("Unknown type '%1'").arg(te.name)); break; - case TypeExpr::Array: validateTypeExpr(te.elements[0], result); break; - case TypeExpr::Map: validateTypeExpr(te.elements[0], result); validateTypeExpr(te.elements[1], result); break; - case TypeExpr::Optional: validateTypeExpr(te.elements[0], result); break; - } - } -}; - -LidlValidationResult lidlValidate(const ModuleDecl& module) { Validator v(module); return v.validate(); } diff --git a/cpp-generator/experimental/lidl_validator.h b/cpp-generator/experimental/lidl_validator.h deleted file mode 100644 index a9f8ea4..0000000 --- a/cpp-generator/experimental/lidl_validator.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef LIDL_VALIDATOR_H -#define LIDL_VALIDATOR_H - -#include "lidl_ast.h" -#include -#include - -struct LidlValidationResult { - QStringList errors; - QStringList warnings; - bool hasErrors() const { return !errors.isEmpty(); } -}; - -LidlValidationResult lidlValidate(const ModuleDecl& module); - -#endif // LIDL_VALIDATOR_H diff --git a/cpp-generator/legacy/main.cpp b/cpp-generator/legacy/main.cpp index 821f338..3f84783 100644 --- a/cpp-generator/legacy/main.cpp +++ b/cpp-generator/legacy/main.cpp @@ -16,7 +16,7 @@ #include #include "logos_provider_interface.h" #include "generator_lib.h" -#include "../experimental/lidl_parser.h" +#include "../experimental/lidl_compat.h" #include "../experimental/impl_header_parser.h" // Escape a string for safe embedding inside a generated C++ string literal. @@ -82,11 +82,11 @@ static QJsonArray loadEventsFromLidl(const QString& lidlPath, QTextStream& err) for (const EventDecl& ed : pr.module.events) { QJsonObject obj; - obj["name"] = ed.name; + obj["name"] = qs(ed.name); QJsonArray params; for (const ParamDecl& pd : ed.params) { QJsonObject p; - p["name"] = pd.name; + p["name"] = qs(pd.name); p["type"] = lidlTypeExprToQtTypeName(pd.type); params.append(p); } @@ -155,14 +155,14 @@ static QJsonArray moduleMethodsToJson(const ModuleDecl& mod) QJsonArray arr; for (const MethodDecl& m : mod.methods) { QJsonObject o; - o["name"] = m.name; + o["name"] = qs(m.name); o["returnType"] = lidlTypeExprToQtTypeName(m.returnType); o["isInvokable"] = true; QJsonArray params; for (const ParamDecl& p : m.params) { QJsonObject po; po["type"] = lidlTypeExprToQtTypeName(p.type); - po["name"] = p.name; + po["name"] = qs(p.name); params.append(po); } o["parameters"] = params; @@ -178,11 +178,11 @@ static QJsonArray moduleEventsToJson(const ModuleDecl& mod) QJsonArray arr; for (const EventDecl& ed : mod.events) { QJsonObject o; - o["name"] = ed.name; + o["name"] = qs(ed.name); QJsonArray params; for (const ParamDecl& pd : ed.params) { QJsonObject p; - p["name"] = pd.name; + p["name"] = qs(pd.name); p["type"] = lidlTypeExprToQtTypeName(pd.type); params.append(p); } diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp index 427ac32..df6e3d7 100644 --- a/cpp-generator/main.cpp +++ b/cpp-generator/main.cpp @@ -1,8 +1,7 @@ #include "legacy/legacy_main.h" #include "experimental/lidl_gen_client.h" #include "experimental/lidl_gen_cdylib.h" -#include "experimental/lidl_parser.h" -#include "experimental/lidl_serializer.h" +#include "experimental/lidl_compat.h" #include "experimental/impl_header_parser.h" #include @@ -80,9 +79,9 @@ int main(int argc, char* argv[]) } else if (outDirIdx != -1 && outDirIdx + 1 < args.size()) { const QString d = stripAt(args.at(outDirIdx + 1)); QDir().mkpath(d); - outPath = QDir(d).filePath(mod.name + ".lidl"); + outPath = QDir(d).filePath(qs(mod.name) + ".lidl"); } else { - outPath = mod.name + ".lidl"; + outPath = qs(mod.name) + ".lidl"; } QFile f(outPath); @@ -173,12 +172,12 @@ int main(int argc, char* argv[]) } struct Out { QString file; QString content; }; QList outs; - outs.append({mod.name + "_module_impl.cpp", + outs.append({qs(mod.name) + "_module_impl.cpp", lidlMakeModuleImplExports(mod, implClass, implHeader)}); - if (!mod.events.isEmpty()) - outs.append({mod.name + "_events_cdylib.cpp", + if (!mod.events.empty()) + outs.append({qs(mod.name) + "_events_cdylib.cpp", lidlMakeEventsSourceCdylib(mod, implClass, implHeader)}); - outs.append({mod.name + ".lidl", lidlSerialize(mod)}); + outs.append({qs(mod.name) + ".lidl", lidlSerialize(mod)}); for (const Out& o : outs) { const QString abs = QDir(genDirPath).filePath(o.file); QFile f(abs); @@ -274,11 +273,11 @@ int main(int argc, char* argv[]) if (implHeaderIdx != -1 && implHeaderIdx + 1 < args.size()) implHeader = args.at(implHeaderIdx + 1); else - implHeader = mod.name + "_impl.h"; - outs.append({mod.name + "_module_impl.cpp", + implHeader = qs(mod.name) + "_impl.h"; + outs.append({qs(mod.name) + "_module_impl.cpp", lidlMakeModuleImplExports(mod, implClass, implHeader)}); - if (!mod.events.isEmpty()) - outs.append({mod.name + "_events_cdylib.cpp", + if (!mod.events.empty()) + outs.append({qs(mod.name) + "_events_cdylib.cpp", lidlMakeEventsSourceCdylib(mod, implClass, implHeader)}); } else { err << "Error: the uniform cdylib Qt glue is generated by " diff --git a/flake.lock b/flake.lock index 9a364fc..0cdc563 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,30 @@ { "nodes": { + "logos-lidl": { + "inputs": { + "logos-nix": [ + "logos-nix" + ], + "nixpkgs": [ + "logos-lidl", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781653473, + "narHash": "sha256-8l2tE2K5nY1grcFROQHC1By1jVI0NrfkS3k2v2y6R2I=", + "owner": "logos-co", + "repo": "logos-lidl", + "rev": "8c95d4f0cc6a10195c70ed71e85b7a4cddca02f8", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-lidl", + "type": "github" + } + }, "logos-nix": { "inputs": { "nixpkgs": "nixpkgs" @@ -61,6 +86,7 @@ }, "root": { "inputs": { + "logos-lidl": "logos-lidl", "logos-nix": "logos-nix", "logos-protocol": "logos-protocol", "nixpkgs": [ diff --git a/flake.nix b/flake.nix index 0aaf456..ac64eef 100644 --- a/flake.nix +++ b/flake.nix @@ -8,8 +8,13 @@ # wire is Qt-version-sensitive. inputs.logos-protocol.url = "github:logos-co/logos-protocol"; inputs.logos-protocol.inputs.logos-nix.follows = "logos-nix"; + # The canonical, language-neutral LIDL frontend (lexer/parser/AST/serializer/ + # validator) the code generator links. Follows our logos-nix so it resolves + # the identical nixpkgs pin. + inputs.logos-lidl.url = "github:logos-co/logos-lidl"; + inputs.logos-lidl.inputs.logos-nix.follows = "logos-nix"; - outputs = { self, nixpkgs, logos-nix, logos-protocol }: + outputs = { self, nixpkgs, logos-nix, logos-protocol, logos-lidl }: let systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f { @@ -24,10 +29,10 @@ src = ./.; # Individual package components - bin = import ./nix/bin.nix { inherit pkgs common src logos-protocol; }; + bin = import ./nix/bin.nix { inherit pkgs common src logos-protocol; logos-lidl = logos-lidl.packages.${pkgs.system}.logos-lidl; }; lib = import ./nix/lib.nix { inherit pkgs common src logos-protocol; }; include = import ./nix/include.nix { inherit pkgs common src logos-protocol; }; - tests = import ./nix/tests.nix { inherit pkgs common src logos-protocol; }; + tests = import ./nix/tests.nix { inherit pkgs common src logos-protocol; logos-lidl = logos-lidl.packages.${pkgs.system}.logos-lidl; }; # Combined SDK package. We re-declare propagatedBuildInputs on # the join so downstream Nix derivations that depend on the @@ -63,7 +68,7 @@ let common = import ./nix/default.nix { inherit pkgs; }; src = ./.; - tests = import ./nix/tests.nix { inherit pkgs common src logos-protocol; }; + tests = import ./nix/tests.nix { inherit pkgs common src logos-protocol; logos-lidl = logos-lidl.packages.${pkgs.system}.logos-lidl; }; in { inherit tests; diff --git a/nix/bin.nix b/nix/bin.nix index 0bad7a9..526700a 100644 --- a/nix/bin.nix +++ b/nix/bin.nix @@ -1,12 +1,15 @@ # Builds the logos-cpp-generator binary -{ pkgs, common, src, logos-protocol }: +{ pkgs, common, src, logos-protocol, logos-lidl }: pkgs.stdenv.mkDerivation { pname = "${common.pname}-generator"; version = common.version; - + inherit src; - inherit (common) nativeBuildInputs buildInputs cmakeFlags meta; + inherit (common) nativeBuildInputs cmakeFlags meta; + # logos-lidl provides the canonical LIDL frontend the generator links + # (find_package(logos-lidl) in cpp-generator/CMakeLists.txt). + buildInputs = common.buildInputs ++ [ logos-lidl ]; # Skip default configure phase since we do it in buildPhase dontUseCmakeConfigure = true; @@ -33,19 +36,18 @@ pkgs.stdenv.mkDerivation { cp build-generator/bin/logos-cpp-generator $out/bin/ fi - # LIDL frontend sources for external generators (logos-qt-sdk's - # logos-qt-generator compiles these in — source-level sharing, no - # binary ABI between the two generators). + # Shared C++/Qt codegen backend helpers for logos-qt-sdk's + # logos-qt-generator: the Qt type-name mapping (lidl_emit_common), the + # C++ impl-header source parser, and the compat shim that bridges them + # onto the canonical logos-lidl AST. The frontend itself (lexer/parser/ + # AST/serializer/validator) is NOT distributed here — both generators + # link logos-lidl for it. mkdir -p $out/share/lidl-frontend - cp cpp-generator/experimental/lidl_ast.h \ - cpp-generator/experimental/lidl_lexer.h cpp-generator/experimental/lidl_lexer.cpp \ - cpp-generator/experimental/lidl_parser.h cpp-generator/experimental/lidl_parser.cpp \ - cpp-generator/experimental/lidl_serializer.h cpp-generator/experimental/lidl_serializer.cpp \ - cpp-generator/experimental/lidl_validator.h cpp-generator/experimental/lidl_validator.cpp \ + cp cpp-generator/experimental/lidl_compat.h \ cpp-generator/experimental/impl_header_parser.h cpp-generator/experimental/impl_header_parser.cpp \ cpp-generator/experimental/lidl_emit_common.h cpp-generator/experimental/lidl_emit_common.cpp \ $out/share/lidl-frontend/ - + runHook postInstall ''; } diff --git a/nix/tests.nix b/nix/tests.nix index 274a98f..4b9d228 100644 --- a/nix/tests.nix +++ b/nix/tests.nix @@ -1,5 +1,5 @@ # Builds and runs the test suite -{ pkgs, common, src, logos-protocol }: +{ pkgs, common, src, logos-protocol, logos-lidl }: pkgs.stdenv.mkDerivation { pname = "${common.pname}-tests"; @@ -8,7 +8,9 @@ pkgs.stdenv.mkDerivation { inherit src; nativeBuildInputs = common.nativeBuildInputs; - buildInputs = common.buildInputs ++ [ pkgs.gtest ]; + # logos-lidl: the experimental backend tests link the canonical frontend + # (find_package(logos-lidl) in tests/experimental/CMakeLists.txt). + buildInputs = common.buildInputs ++ [ pkgs.gtest logos-lidl ]; cmakeFlags = common.cmakeFlags; dontUseCmakeConfigure = true; diff --git a/tests/experimental/CMakeLists.txt b/tests/experimental/CMakeLists.txt index 181a948..1c7ac98 100644 --- a/tests/experimental/CMakeLists.txt +++ b/tests/experimental/CMakeLists.txt @@ -1,12 +1,11 @@ set(EXPERIMENTAL_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/experimental) -add_executable(experimental_tests - # LIDL language tests - test_lidl_lexer.cpp - test_lidl_parser.cpp - test_lidl_validator.cpp - test_lidl_serializer.cpp +# The LIDL frontend (lexer/parser/AST/serializer/validator) now lives in +# logos-lidl and is covered by its own test suite; only the C++/Qt-specific +# backends (type mapping, gen_client, impl-header parsing) are tested here. +find_package(logos-lidl REQUIRED) +add_executable(experimental_tests # Type mapping tests test_lidl_type_mapping.cpp @@ -18,10 +17,6 @@ add_executable(experimental_tests # Sources under test ${EXPERIMENTAL_SRC_DIR}/lidl_emit_common.cpp - ${EXPERIMENTAL_SRC_DIR}/lidl_lexer.cpp - ${EXPERIMENTAL_SRC_DIR}/lidl_parser.cpp - ${EXPERIMENTAL_SRC_DIR}/lidl_validator.cpp - ${EXPERIMENTAL_SRC_DIR}/lidl_serializer.cpp ${EXPERIMENTAL_SRC_DIR}/lidl_gen_client.cpp ${EXPERIMENTAL_SRC_DIR}/impl_header_parser.cpp ) @@ -40,6 +35,7 @@ target_compile_definitions(experimental_tests PRIVATE target_link_libraries(experimental_tests PRIVATE GTest::gtest_main Qt${QT_VERSION_MAJOR}::Core + logos-lidl::logos_lidl ) gtest_discover_tests(experimental_tests) diff --git a/tests/experimental/test_impl_header_parser.cpp b/tests/experimental/test_impl_header_parser.cpp index 7892aab..e0833db 100644 --- a/tests/experimental/test_impl_header_parser.cpp +++ b/tests/experimental/test_impl_header_parser.cpp @@ -72,7 +72,7 @@ TEST_F(ImplHeaderParserTest, MethodTypes) ASSERT_FALSE(r.hasError()) << r.error.toStdString(); // Find specific methods and check their types - auto findMethod = [&](const QString& name) -> const MethodDecl* { + auto findMethod = [&](const std::string& name) -> const MethodDecl* { for (const auto& m : r.module.methods) if (m.name == name) return &m; return nullptr; @@ -95,7 +95,7 @@ TEST_F(ImplHeaderParserTest, MethodTypes) auto getCount = findMethod("getCount"); ASSERT_NE(getCount, nullptr); EXPECT_EQ(getCount->returnType.name, "int"); - EXPECT_TRUE(getCount->params.isEmpty()); + EXPECT_TRUE(getCount->params.empty()); // uint64_t getSize() → uint auto getSize = findMethod("getSize"); @@ -166,7 +166,7 @@ TEST_F(ImplHeaderParserTest, EmptyClass) fixturesDir() + "/empty_metadata.json", err); ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_TRUE(r.module.methods.isEmpty()); + EXPECT_TRUE(r.module.methods.empty()); // Should have a warning in err output EXPECT_TRUE(errOutput.contains("Warning")); } @@ -184,7 +184,7 @@ TEST_F(ImplHeaderParserTest, ComplexAccessSpecifiers) err); ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - auto findMethod = [&](const QString& name) -> const MethodDecl* { + auto findMethod = [&](const std::string& name) -> const MethodDecl* { for (const auto& m : r.module.methods) if (m.name == name) return &m; return nullptr; @@ -236,7 +236,7 @@ TEST_F(ImplHeaderParserTest, WrongClassName) err); // Not an error per se, but should find zero methods and warn ASSERT_FALSE(r.hasError()); - EXPECT_TRUE(r.module.methods.isEmpty()); + EXPECT_TRUE(r.module.methods.empty()); EXPECT_TRUE(errOutput.contains("Warning")); } @@ -264,7 +264,7 @@ TEST_F(ImplHeaderParserTest, UniversalTypesAndMetadataEvents) EXPECT_EQ(r.module.events[0].params[0].name, "info"); EXPECT_EQ(r.module.events[0].params[0].type.name, "tstr"); - auto findMethod = [&](const QString& name) -> const MethodDecl* { + auto findMethod = [&](const std::string& name) -> const MethodDecl* { for (const auto& m : r.module.methods) if (m.name == name) return &m; return nullptr; @@ -343,7 +343,7 @@ TEST_F(ImplHeaderParserTest, EventDocCommentsFromHeader) // A plain `//` comment is not a doc comment → no description captured. EXPECT_EQ(r.module.events[1].name, "heartbeat"); - EXPECT_TRUE(r.module.events[1].description.isEmpty()); + EXPECT_TRUE(r.module.events[1].description.empty()); // Single-line `///` doc comment. EXPECT_EQ(r.module.events[2].name, "shutdown"); @@ -366,12 +366,12 @@ TEST_F(ImplHeaderParserTest, SameLineSectionSpecifiers) err); ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - auto findEvent = [&](const QString& name) -> const EventDecl* { + auto findEvent = [&](const std::string& name) -> const EventDecl* { for (const auto& e : r.module.events) if (e.name == name) return &e; return nullptr; }; - auto findMethod = [&](const QString& name) -> const MethodDecl* { + auto findMethod = [&](const std::string& name) -> const MethodDecl* { for (const auto& m : r.module.methods) if (m.name == name) return &m; return nullptr; diff --git a/tests/experimental/test_lidl_gen_client.cpp b/tests/experimental/test_lidl_gen_client.cpp index 9e08ce8..653d1dd 100644 --- a/tests/experimental/test_lidl_gen_client.cpp +++ b/tests/experimental/test_lidl_gen_client.cpp @@ -8,37 +8,37 @@ static ModuleDecl makeTestModule() m.version = "1.0.0"; m.description = "Wallet"; m.category = "finance"; - m.depends << "crypto"; + m.depends.push_back("crypto"); { MethodDecl md; md.name = "createAccount"; md.returnType = { TypeExpr::Primitive, "tstr", {} }; ParamDecl p; p.name = "passphrase"; p.type = { TypeExpr::Primitive, "tstr", {} }; - md.params.append(p); - m.methods.append(md); + md.params.push_back(p); + m.methods.push_back(md); } { MethodDecl md; md.name = "getBalance"; md.returnType = { TypeExpr::Primitive, "uint", {} }; ParamDecl p; p.name = "address"; p.type = { TypeExpr::Primitive, "tstr", {} }; - md.params.append(p); - m.methods.append(md); + md.params.push_back(p); + m.methods.push_back(md); } { MethodDecl md; md.name = "listAccounts"; TypeExpr elem = { TypeExpr::Primitive, "tstr", {} }; md.returnType = { TypeExpr::Array, "", { elem } }; - m.methods.append(md); + m.methods.push_back(md); } EventDecl ed; ed.name = "onTransfer"; ParamDecl ep; ep.name = "hash"; ep.type = { TypeExpr::Primitive, "tstr", {} }; - ed.params.append(ep); - m.events.append(ed); + ed.params.push_back(ep); + m.events.push_back(ed); return m; } @@ -191,11 +191,11 @@ TEST(LidlGenClient, MethodWithManyParams) md.returnType = { TypeExpr::Primitive, "tstr", {} }; for (int i = 0; i < 7; ++i) { ParamDecl p; - p.name = QString("p%1").arg(i); + p.name = QString("p%1").arg(i).toStdString(); p.type = { TypeExpr::Primitive, "tstr", {} }; - md.params.append(p); + md.params.push_back(p); } - m.methods.append(md); + m.methods.push_back(md); QString s = lidlMakeSource(m); // >5 params should use QVariantList{} syntax @@ -209,7 +209,7 @@ TEST(LidlGenClient, VoidReturnMethod) MethodDecl md; md.name = "doStuff"; md.returnType = { TypeExpr::Primitive, "void", {} }; - m.methods.append(md); + m.methods.push_back(md); QString h = lidlMakeHeader(m); // void return should have async callback with void() diff --git a/tests/experimental/test_lidl_lexer.cpp b/tests/experimental/test_lidl_lexer.cpp deleted file mode 100644 index 657563b..0000000 --- a/tests/experimental/test_lidl_lexer.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include -#include "lidl_lexer.h" - -// --------------------------------------------------------------------------- -// Basic tokenization -// --------------------------------------------------------------------------- - -TEST(LidlLexer, EmptyInput) -{ - auto r = lidlTokenize(""); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 1); - EXPECT_EQ(r.tokens[0].type, LidlToken::Eof); -} - -TEST(LidlLexer, WhitespaceOnly) -{ - auto r = lidlTokenize(" \t\n\n "); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 1); - EXPECT_EQ(r.tokens[0].type, LidlToken::Eof); -} - -TEST(LidlLexer, Comment) -{ - auto r = lidlTokenize("; this is a comment\n"); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 1); - EXPECT_EQ(r.tokens[0].type, LidlToken::Eof); -} - -TEST(LidlLexer, CommentBeforeToken) -{ - auto r = lidlTokenize("; comment\nmodule"); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 2); - EXPECT_EQ(r.tokens[0].type, LidlToken::Module); - EXPECT_EQ(r.tokens[1].type, LidlToken::Eof); -} - -// --------------------------------------------------------------------------- -// Keywords -// --------------------------------------------------------------------------- - -TEST(LidlLexer, AllKeywords) -{ - auto r = lidlTokenize("module type method event version description category depends"); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 9); // 8 keywords + Eof - EXPECT_EQ(r.tokens[0].type, LidlToken::Module); - EXPECT_EQ(r.tokens[1].type, LidlToken::TypeKw); - EXPECT_EQ(r.tokens[2].type, LidlToken::Method); - EXPECT_EQ(r.tokens[3].type, LidlToken::Event); - EXPECT_EQ(r.tokens[4].type, LidlToken::Version); - EXPECT_EQ(r.tokens[5].type, LidlToken::Description); - EXPECT_EQ(r.tokens[6].type, LidlToken::Category); - EXPECT_EQ(r.tokens[7].type, LidlToken::Depends); -} - -TEST(LidlLexer, IdentifierNotKeyword) -{ - auto r = lidlTokenize("modules my_module foo123 _bar"); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 5); - for (int i = 0; i < 4; ++i) - EXPECT_EQ(r.tokens[i].type, LidlToken::Ident); - EXPECT_EQ(r.tokens[0].text, "modules"); - EXPECT_EQ(r.tokens[1].text, "my_module"); - EXPECT_EQ(r.tokens[2].text, "foo123"); - EXPECT_EQ(r.tokens[3].text, "_bar"); -} - -// --------------------------------------------------------------------------- -// Symbols -// --------------------------------------------------------------------------- - -TEST(LidlLexer, AllSymbols) -{ - auto r = lidlTokenize("{ } ( ) [ ] : , -> ?"); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 11); // 10 symbols + Eof - EXPECT_EQ(r.tokens[0].type, LidlToken::LBrace); - EXPECT_EQ(r.tokens[1].type, LidlToken::RBrace); - EXPECT_EQ(r.tokens[2].type, LidlToken::LParen); - EXPECT_EQ(r.tokens[3].type, LidlToken::RParen); - EXPECT_EQ(r.tokens[4].type, LidlToken::LBracket); - EXPECT_EQ(r.tokens[5].type, LidlToken::RBracket); - EXPECT_EQ(r.tokens[6].type, LidlToken::Colon); - EXPECT_EQ(r.tokens[7].type, LidlToken::Comma); - EXPECT_EQ(r.tokens[8].type, LidlToken::Arrow); - EXPECT_EQ(r.tokens[9].type, LidlToken::Question); -} - -// --------------------------------------------------------------------------- -// String literals -// --------------------------------------------------------------------------- - -TEST(LidlLexer, SimpleString) -{ - auto r = lidlTokenize("\"hello world\""); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens.size(), 2); - EXPECT_EQ(r.tokens[0].type, LidlToken::StringLit); - EXPECT_EQ(r.tokens[0].text, "hello world"); -} - -TEST(LidlLexer, StringWithEscapes) -{ - auto r = lidlTokenize("\"line1\\nline2\\ttab\\\\backslash\\\"quote\""); - ASSERT_FALSE(r.hasError()); - ASSERT_EQ(r.tokens[0].type, LidlToken::StringLit); - EXPECT_EQ(r.tokens[0].text, "line1\nline2\ttab\\backslash\"quote"); -} - -TEST(LidlLexer, EmptyString) -{ - auto r = lidlTokenize("\"\""); - ASSERT_FALSE(r.hasError()); - EXPECT_EQ(r.tokens[0].type, LidlToken::StringLit); - EXPECT_EQ(r.tokens[0].text, ""); -} - -TEST(LidlLexer, UnterminatedString) -{ - auto r = lidlTokenize("\"no closing"); - ASSERT_TRUE(r.hasError()); - EXPECT_TRUE(r.error.contains("Unterminated")); -} - -TEST(LidlLexer, NewlineInString) -{ - auto r = lidlTokenize("\"broken\nstring\""); - ASSERT_TRUE(r.hasError()); - EXPECT_TRUE(r.error.contains("Unterminated")); -} - -// --------------------------------------------------------------------------- -// Line/column tracking -// --------------------------------------------------------------------------- - -TEST(LidlLexer, LineColumnTracking) -{ - auto r = lidlTokenize("module test {\n method foo() -> tstr\n}"); - ASSERT_FALSE(r.hasError()); - // "module" at line 1 - EXPECT_EQ(r.tokens[0].line, 1); - EXPECT_EQ(r.tokens[0].column, 1); - // "test" at line 1 col 8 - EXPECT_EQ(r.tokens[1].line, 1); - EXPECT_EQ(r.tokens[1].column, 8); - // "{" at line 1 col 13 - EXPECT_EQ(r.tokens[2].line, 1); - EXPECT_EQ(r.tokens[2].column, 13); - // "method" at line 2 - EXPECT_EQ(r.tokens[3].line, 2); -} - -// --------------------------------------------------------------------------- -// Error handling -// --------------------------------------------------------------------------- - -TEST(LidlLexer, UnexpectedCharacter) -{ - auto r = lidlTokenize("module @bad"); - ASSERT_TRUE(r.hasError()); - EXPECT_TRUE(r.error.contains("Unexpected character")); -} - -// --------------------------------------------------------------------------- -// Full module tokenization -// --------------------------------------------------------------------------- - -TEST(LidlLexer, FullModule) -{ - QString src = R"( - module wallet_module { - version "1.0.0" - description "Wallet module" - depends [dep_a, dep_b] - method doSomething(input: tstr) -> bool - event onUpdate(data: tstr) - } - )"; - auto r = lidlTokenize(src); - ASSERT_FALSE(r.hasError()); - // Just verify no errors and reasonable token count - EXPECT_GT(r.tokens.size(), 20); - EXPECT_EQ(r.tokens.back().type, LidlToken::Eof); -} diff --git a/tests/experimental/test_lidl_parser.cpp b/tests/experimental/test_lidl_parser.cpp deleted file mode 100644 index ee85d68..0000000 --- a/tests/experimental/test_lidl_parser.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include -#include "lidl_parser.h" - -// --------------------------------------------------------------------------- -// Minimal valid module -// --------------------------------------------------------------------------- - -TEST(LidlParser, MinimalModule) -{ - auto r = lidlParse("module test { depends [] }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_EQ(r.module.name, "test"); - EXPECT_TRUE(r.module.methods.isEmpty()); - EXPECT_TRUE(r.module.events.isEmpty()); - EXPECT_TRUE(r.module.types.isEmpty()); -} - -// --------------------------------------------------------------------------- -// Metadata -// --------------------------------------------------------------------------- - -TEST(LidlParser, AllMetadata) -{ - QString src = R"(module my_mod { - version "2.0.0" - description "A test module" - category "testing" - depends [foo, bar, baz] - })"; - auto r = lidlParse(src); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_EQ(r.module.name, "my_mod"); - EXPECT_EQ(r.module.version, "2.0.0"); - EXPECT_EQ(r.module.description, "A test module"); - EXPECT_EQ(r.module.category, "testing"); - ASSERT_EQ(r.module.depends.size(), 3); - EXPECT_EQ(r.module.depends[0], "foo"); - EXPECT_EQ(r.module.depends[1], "bar"); - EXPECT_EQ(r.module.depends[2], "baz"); -} - -TEST(LidlParser, EmptyDepends) -{ - auto r = lidlParse("module m { depends [] }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_TRUE(r.module.depends.isEmpty()); -} - -// --------------------------------------------------------------------------- -// Methods -// --------------------------------------------------------------------------- - -TEST(LidlParser, SimpleMethod) -{ - auto r = lidlParse("module m { method greet(name: tstr) -> tstr }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods.size(), 1); - auto& md = r.module.methods[0]; - EXPECT_EQ(md.name, "greet"); - EXPECT_EQ(md.returnType.kind, TypeExpr::Primitive); - EXPECT_EQ(md.returnType.name, "tstr"); - ASSERT_EQ(md.params.size(), 1); - EXPECT_EQ(md.params[0].name, "name"); - EXPECT_EQ(md.params[0].type.name, "tstr"); -} - -TEST(LidlParser, MethodNoParams) -{ - auto r = lidlParse("module m { method getCount() -> int }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods.size(), 1); - EXPECT_TRUE(r.module.methods[0].params.isEmpty()); - EXPECT_EQ(r.module.methods[0].returnType.name, "int"); -} - -TEST(LidlParser, MethodMultipleParams) -{ - auto r = lidlParse("module m { method combine(a: tstr, b: tstr, n: int) -> tstr }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods[0].params.size(), 3); - EXPECT_EQ(r.module.methods[0].params[0].name, "a"); - EXPECT_EQ(r.module.methods[0].params[1].name, "b"); - EXPECT_EQ(r.module.methods[0].params[2].name, "n"); - EXPECT_EQ(r.module.methods[0].params[2].type.name, "int"); -} - -TEST(LidlParser, MultipleMethods) -{ - QString src = R"(module m { - method a() -> bool - method b(x: int) -> tstr - method c(y: float64, z: uint) -> bstr - })"; - auto r = lidlParse(src); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods.size(), 3); - EXPECT_EQ(r.module.methods[0].name, "a"); - EXPECT_EQ(r.module.methods[1].name, "b"); - EXPECT_EQ(r.module.methods[2].name, "c"); -} - -// --------------------------------------------------------------------------- -// Type expressions -// --------------------------------------------------------------------------- - -TEST(LidlParser, ArrayType) -{ - auto r = lidlParse("module m { method get() -> [tstr] }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - auto& ret = r.module.methods[0].returnType; - EXPECT_EQ(ret.kind, TypeExpr::Array); - ASSERT_EQ(ret.elements.size(), 1); - EXPECT_EQ(ret.elements[0].name, "tstr"); -} - -TEST(LidlParser, MapType) -{ - auto r = lidlParse("module m { method get() -> {tstr: int} }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - auto& ret = r.module.methods[0].returnType; - EXPECT_EQ(ret.kind, TypeExpr::Map); - ASSERT_EQ(ret.elements.size(), 2); - EXPECT_EQ(ret.elements[0].name, "tstr"); - EXPECT_EQ(ret.elements[1].name, "int"); -} - -TEST(LidlParser, OptionalType) -{ - auto r = lidlParse("module m { method get() -> ?tstr }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - auto& ret = r.module.methods[0].returnType; - EXPECT_EQ(ret.kind, TypeExpr::Optional); - ASSERT_EQ(ret.elements.size(), 1); - EXPECT_EQ(ret.elements[0].name, "tstr"); -} - -TEST(LidlParser, AllPrimitiveTypes) -{ - QString src = R"(module m { - method a() -> tstr - method b() -> bstr - method c() -> int - method d() -> uint - method e() -> float64 - method f() -> bool - method g() -> result - method h() -> any - })"; - auto r = lidlParse(src); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods.size(), 8); - EXPECT_EQ(r.module.methods[0].returnType.name, "tstr"); - EXPECT_EQ(r.module.methods[1].returnType.name, "bstr"); - EXPECT_EQ(r.module.methods[2].returnType.name, "int"); - EXPECT_EQ(r.module.methods[3].returnType.name, "uint"); - EXPECT_EQ(r.module.methods[4].returnType.name, "float64"); - EXPECT_EQ(r.module.methods[5].returnType.name, "bool"); - EXPECT_EQ(r.module.methods[6].returnType.name, "result"); - EXPECT_EQ(r.module.methods[7].returnType.name, "any"); -} - -// --------------------------------------------------------------------------- -// Events -// --------------------------------------------------------------------------- - -TEST(LidlParser, SimpleEvent) -{ - auto r = lidlParse("module m { event onData(payload: tstr) }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.events.size(), 1); - EXPECT_EQ(r.module.events[0].name, "onData"); - ASSERT_EQ(r.module.events[0].params.size(), 1); - EXPECT_EQ(r.module.events[0].params[0].name, "payload"); -} - -TEST(LidlParser, EventNoParams) -{ - auto r = lidlParse("module m { event ping() }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_TRUE(r.module.events[0].params.isEmpty()); -} - -// --------------------------------------------------------------------------- -// Keywords as names (contextual keywords) -// -// The reserved words (module/type/method/event/version/description/ -// category/depends) are keywords only at the start of a declaration. In a -// name position they are ordinary identifiers. Regression: a `versionReady` -// event with a parameter literally named `version` (as emitted for a module -// whose impl declares `versionReady(const std::string& version)`) used to -// fail to re-parse with "Expected parameter name", because `version` lexes -// as a keyword. -// --------------------------------------------------------------------------- - -TEST(LidlParser, KeywordAsParameterName) -{ - auto r = lidlParse("module m { event versionReady(version: tstr) }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.events.size(), 1); - EXPECT_EQ(r.module.events[0].name, "versionReady"); - ASSERT_EQ(r.module.events[0].params.size(), 1); - EXPECT_EQ(r.module.events[0].params[0].name, "version"); - EXPECT_EQ(r.module.events[0].params[0].type.name, "tstr"); -} - -TEST(LidlParser, KeywordAsMethodAndParamName) -{ - // `method`, `type`, `event`, `category` all used as ordinary names. - auto r = lidlParse("module m { method method(type: tstr, event: int) -> tstr }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.methods.size(), 1); - EXPECT_EQ(r.module.methods[0].name, "method"); - ASSERT_EQ(r.module.methods[0].params.size(), 2); - EXPECT_EQ(r.module.methods[0].params[0].name, "type"); - EXPECT_EQ(r.module.methods[0].params[1].name, "event"); -} - -TEST(LidlParser, KeywordAsFieldName) -{ - auto r = lidlParse("module m { type T { version: tstr description: int } }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.types.size(), 1); - ASSERT_EQ(r.module.types[0].fields.size(), 2); - EXPECT_EQ(r.module.types[0].fields[0].name, "version"); - EXPECT_EQ(r.module.types[0].fields[1].name, "description"); -} - -TEST(LidlParser, KeywordAsDependencyName) -{ - auto r = lidlParse("module m { depends [version, module, foo] }"); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.depends.size(), 3); - EXPECT_EQ(r.module.depends[0], "version"); - EXPECT_EQ(r.module.depends[1], "module"); - EXPECT_EQ(r.module.depends[2], "foo"); -} - -// --------------------------------------------------------------------------- -// Type definitions -// --------------------------------------------------------------------------- - -TEST(LidlParser, TypeDefinition) -{ - QString src = R"(module m { - type Person { - name: tstr - age: int - ? nickname: tstr - } - })"; - auto r = lidlParse(src); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - ASSERT_EQ(r.module.types.size(), 1); - auto& td = r.module.types[0]; - EXPECT_EQ(td.name, "Person"); - ASSERT_EQ(td.fields.size(), 3); - EXPECT_EQ(td.fields[0].name, "name"); - EXPECT_FALSE(td.fields[0].optional); - EXPECT_EQ(td.fields[2].name, "nickname"); - EXPECT_TRUE(td.fields[2].optional); -} - -// --------------------------------------------------------------------------- -// Error cases -// --------------------------------------------------------------------------- - -TEST(LidlParser, MissingModuleKeyword) -{ - auto r = lidlParse("test { }"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, MissingModuleName) -{ - auto r = lidlParse("module { }"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, MissingOpenBrace) -{ - auto r = lidlParse("module test }"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, MissingCloseBrace) -{ - auto r = lidlParse("module test {"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, MissingArrowInMethod) -{ - auto r = lidlParse("module m { method foo() tstr }"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, TrailingContent) -{ - auto r = lidlParse("module m { } extra_stuff"); - ASSERT_TRUE(r.hasError()); -} - -TEST(LidlParser, ErrorReportsLineColumn) -{ - auto r = lidlParse("module m {\n method foo() tstr\n}"); - ASSERT_TRUE(r.hasError()); - EXPECT_EQ(r.errorLine, 2); - EXPECT_GT(r.errorColumn, 0); -} - -// --------------------------------------------------------------------------- -// Full module -// --------------------------------------------------------------------------- - -TEST(LidlParser, CompleteModule) -{ - QString src = R"(module wallet { - version "1.0.0" - description "Wallet module" - category "finance" - depends [crypto, storage] - - type Account { - address: tstr - balance: uint - ? label: tstr - } - - method createAccount(passphrase: tstr) -> tstr - method getBalance(address: tstr) -> uint - method listAccounts() -> [tstr] - method transfer(from: tstr, to: tstr, amount: uint) -> result - - event onTransfer(from: tstr, to: tstr, amount: uint) - })"; - auto r = lidlParse(src); - ASSERT_FALSE(r.hasError()) << r.error.toStdString(); - EXPECT_EQ(r.module.name, "wallet"); - EXPECT_EQ(r.module.version, "1.0.0"); - EXPECT_EQ(r.module.depends.size(), 2); - EXPECT_EQ(r.module.types.size(), 1); - EXPECT_EQ(r.module.methods.size(), 4); - EXPECT_EQ(r.module.events.size(), 1); -} diff --git a/tests/experimental/test_lidl_serializer.cpp b/tests/experimental/test_lidl_serializer.cpp deleted file mode 100644 index 03729a4..0000000 --- a/tests/experimental/test_lidl_serializer.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include "lidl_serializer.h" -#include "lidl_parser.h" - -TEST(LidlSerializer, EmptyModule) -{ - ModuleDecl m; - m.name = "test"; - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("module test {")); - EXPECT_TRUE(out.contains("depends []\n")); - EXPECT_TRUE(out.contains("}\n")); -} - -TEST(LidlSerializer, WithMetadata) -{ - ModuleDecl m; - m.name = "my_mod"; - m.version = "1.0.0"; - m.description = "My module"; - m.category = "testing"; - m.depends << "dep_a" << "dep_b"; - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("version \"1.0.0\"")); - EXPECT_TRUE(out.contains("description \"My module\"")); - EXPECT_TRUE(out.contains("category \"testing\"")); - EXPECT_TRUE(out.contains("depends [dep_a, dep_b]")); -} - -TEST(LidlSerializer, WithMethod) -{ - ModuleDecl m; - m.name = "test"; - MethodDecl md; - md.name = "greet"; - md.returnType = { TypeExpr::Primitive, "tstr", {} }; - ParamDecl p; - p.name = "name"; - p.type = { TypeExpr::Primitive, "tstr", {} }; - md.params.append(p); - m.methods.append(md); - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("method greet(name: tstr) -> tstr")); -} - -TEST(LidlSerializer, WithEvent) -{ - ModuleDecl m; - m.name = "test"; - EventDecl ed; - ed.name = "onUpdate"; - ParamDecl p; - p.name = "data"; - p.type = { TypeExpr::Primitive, "bstr", {} }; - ed.params.append(p); - m.events.append(ed); - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("event onUpdate(data: bstr)")); -} - -TEST(LidlSerializer, ArrayType) -{ - ModuleDecl m; - m.name = "test"; - MethodDecl md; - md.name = "get"; - TypeExpr elem = { TypeExpr::Primitive, "tstr", {} }; - md.returnType = { TypeExpr::Array, "", { elem } }; - m.methods.append(md); - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("-> [tstr]")); -} - -TEST(LidlSerializer, MapType) -{ - ModuleDecl m; - m.name = "test"; - MethodDecl md; - md.name = "get"; - TypeExpr key = { TypeExpr::Primitive, "tstr", {} }; - TypeExpr val = { TypeExpr::Primitive, "int", {} }; - md.returnType = { TypeExpr::Map, "", { key, val } }; - m.methods.append(md); - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("-> {tstr: int}")); -} - -TEST(LidlSerializer, OptionalType) -{ - ModuleDecl m; - m.name = "test"; - MethodDecl md; - md.name = "get"; - TypeExpr inner = { TypeExpr::Primitive, "tstr", {} }; - md.returnType = { TypeExpr::Optional, "", { inner } }; - m.methods.append(md); - QString out = lidlSerialize(m); - EXPECT_TRUE(out.contains("-> ? tstr")); -} - -// --------------------------------------------------------------------------- -// Roundtrip: parse → serialize → parse and compare -// --------------------------------------------------------------------------- - -TEST(LidlSerializer, Roundtrip) -{ - QString src = R"(module wallet { - version "2.0.0" - description "Wallet" - category "finance" - depends [crypto] - - type Account { - addr: tstr - balance: uint - } - - method create(pass: tstr) -> tstr - method list() -> [tstr] - method transfer(from: tstr, to: tstr, amt: uint) -> result - - event onTx(hash: tstr) -} -)"; - auto r1 = lidlParse(src); - ASSERT_FALSE(r1.hasError()) << r1.error.toStdString(); - - QString serialized = lidlSerialize(r1.module); - auto r2 = lidlParse(serialized); - ASSERT_FALSE(r2.hasError()) << r2.error.toStdString(); - - EXPECT_EQ(r1.module, r2.module); -} diff --git a/tests/experimental/test_lidl_validator.cpp b/tests/experimental/test_lidl_validator.cpp deleted file mode 100644 index c99ec2c..0000000 --- a/tests/experimental/test_lidl_validator.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include "lidl_validator.h" - -static ModuleDecl makeModule(const QString& name) -{ - ModuleDecl m; - m.name = name; - return m; -} - -TEST(LidlValidator, ValidEmptyModule) -{ - auto r = lidlValidate(makeModule("test")); - EXPECT_FALSE(r.hasErrors()); -} - -TEST(LidlValidator, EmptyModuleName) -{ - auto r = lidlValidate(makeModule("")); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("empty")); -} - -TEST(LidlValidator, DuplicateMethodNames) -{ - ModuleDecl m = makeModule("test"); - MethodDecl md; - md.name = "doThing"; - md.returnType = { TypeExpr::Primitive, "tstr", {} }; - m.methods.append(md); - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("Duplicate method")); -} - -TEST(LidlValidator, DuplicateEventNames) -{ - ModuleDecl m = makeModule("test"); - EventDecl ed; - ed.name = "onUpdate"; - m.events.append(ed); - m.events.append(ed); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("Duplicate event")); -} - -TEST(LidlValidator, DuplicateTypeNames) -{ - ModuleDecl m = makeModule("test"); - TypeDecl td; - td.name = "MyType"; - m.types.append(td); - m.types.append(td); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("Duplicate type")); -} - -TEST(LidlValidator, TypeShadowsBuiltin) -{ - ModuleDecl m = makeModule("test"); - TypeDecl td; - td.name = "tstr"; - m.types.append(td); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("shadows")); -} - -TEST(LidlValidator, UnknownNamedType) -{ - ModuleDecl m = makeModule("test"); - MethodDecl md; - md.name = "foo"; - md.returnType = { TypeExpr::Named, "NonExistentType", {} }; - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("Unknown type")); -} - -TEST(LidlValidator, ValidNamedType) -{ - ModuleDecl m = makeModule("test"); - TypeDecl td; - td.name = "MyStruct"; - m.types.append(td); - - MethodDecl md; - md.name = "foo"; - md.returnType = { TypeExpr::Named, "MyStruct", {} }; - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_FALSE(r.hasErrors()); -} - -TEST(LidlValidator, DuplicateParamNames) -{ - ModuleDecl m = makeModule("test"); - MethodDecl md; - md.name = "foo"; - md.returnType = { TypeExpr::Primitive, "tstr", {} }; - ParamDecl p; - p.name = "arg"; - p.type = { TypeExpr::Primitive, "tstr", {} }; - md.params.append(p); - md.params.append(p); - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); - EXPECT_TRUE(r.errors[0].contains("Duplicate parameter")); -} - -TEST(LidlValidator, NestedArrayTypeValid) -{ - ModuleDecl m = makeModule("test"); - MethodDecl md; - md.name = "foo"; - TypeExpr elem = { TypeExpr::Primitive, "tstr", {} }; - md.returnType = { TypeExpr::Array, "", { elem } }; - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_FALSE(r.hasErrors()); -} - -TEST(LidlValidator, MapWithUnknownValueType) -{ - ModuleDecl m = makeModule("test"); - MethodDecl md; - md.name = "foo"; - TypeExpr key = { TypeExpr::Primitive, "tstr", {} }; - TypeExpr val = { TypeExpr::Named, "Missing", {} }; - md.returnType = { TypeExpr::Map, "", { key, val } }; - m.methods.append(md); - - auto r = lidlValidate(m); - EXPECT_TRUE(r.hasErrors()); -}