Browse Source

Initial version of generator

Alexey Edelev 5 years ago
parent
commit
7495ae7751
6 changed files with 440 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 8 0
      CMakeLists.txt
  3. 294 0
      src/generator/generator.cpp
  4. 45 0
      src/generator/generator.h
  5. 32 0
      src/generator/main.cpp
  6. 60 0
      src/generator/templates.h

+ 1 - 0
.gitignore

@@ -50,6 +50,7 @@
 *.qbs.user
 *.qbs.user.*
 *.moc
+*.user
 moc_*.cpp
 qrc_*.cpp
 ui_*.h

+ 8 - 0
CMakeLists.txt

@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(qtprotobuf)
+
+find_package(Protobuf)
+message("Protobuf libraries"${Protobuf_LIBRARIES})
+add_executable(${PROJECT_NAME} "src/generator/main.cpp" "src/generator/generator.cpp")
+target_link_libraries(${PROJECT_NAME} ${Protobuf_LIBRARIES} "-lprotoc")

+ 294 - 0
src/generator/generator.cpp

@@ -0,0 +1,294 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "generator.h"
+#include "templates.h"
+
+#include <google/protobuf/descriptor.pb.h>
+
+#include <google/protobuf/stubs/logging.h>
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/descriptor.h>
+
+#include <algorithm>
+#include <unordered_map>
+#include <vector>
+#include <sstream>
+#include <string>
+
+using namespace ::google::protobuf;
+using namespace ::google::protobuf::compiler;
+
+using namespace qtprotobuf;
+
+namespace utils {
+
+void split(const std::string& str, std::vector<std::string>& cont, char delim)
+{
+    std::stringstream ss(str);
+    std::string token;
+    while (std::getline(ss, token, delim)) {
+        cont.push_back(token);
+    }
+}
+
+}
+
+static std::unordered_map<FieldDescriptor::Type, std::string> TypeReflection = {
+    {FieldDescriptor::TYPE_DOUBLE, "qreal"},
+    {FieldDescriptor::TYPE_FLOAT, "qreal"},
+    //        {FieldDescriptor::TYPE_INT64, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    //        {FieldDescriptor::TYPE_UINT64,"int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    {FieldDescriptor::TYPE_INT32, "int"},
+    //        {FieldDescriptor::TYPE_FIXED64, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    //        {FieldDescriptor::TYPE_FIXED32, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    {FieldDescriptor::TYPE_BOOL, "bool"},
+    {FieldDescriptor::TYPE_STRING, "QString"},
+    {FieldDescriptor::TYPE_GROUP, ""},//Not supported and deprecated in protobuf
+//    {FieldDescriptor::TYPE_MESSAGE, ""},//Custom typename
+    {FieldDescriptor::TYPE_BYTES, "QByteArray"},
+    {FieldDescriptor::TYPE_UINT32, "int"},//Limited usage see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    {FieldDescriptor::TYPE_ENUM, "$custom_enumtype$"},
+    {FieldDescriptor::TYPE_SFIXED32, "int"},
+    //        {FieldDescriptor::TYPE_SFIXED64, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+    {FieldDescriptor::TYPE_SINT32, "int"},
+    //        {FieldDescriptor::TYPE_SINT64, "int"},//Not supported see https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html
+};
+
+class QtClassGenerator {
+    std::unique_ptr<io::ZeroCopyOutputStream> mOutput;
+    string mPackage;
+    const Descriptor* mMessage;
+    int mNamespaceCount;
+    io::Printer mPrinter;
+
+public:
+    QtClassGenerator(const std::string& package, const Descriptor* message, std::unique_ptr<io::ZeroCopyOutputStream> out) : mOutput(std::move(out))
+      , mPackage(package)
+      , mMessage(message)
+      , mNamespaceCount(0)
+      , mPrinter(mOutput.get(), '$')
+    {
+    }
+
+    void run() {
+        printPreamble();
+        printNamespaces();
+        printClass();
+        enclose();
+    }
+
+private:
+    bool producePropertyMap(const FieldDescriptor* field, std::map<string, string>& propertyMap) {
+        std::string typeName = getTypeName(field);
+
+        if (typeName.size() <= 0) {
+            std::cerr << "Type "
+                      << field->type_name()
+                      << " is not supported by Qt Generator"
+                         " please look at https://doc.qt.io/qt-5/qtqml-typesystem-basictypes.html"
+                         " for details" << std::endl;
+            return false;
+        }
+
+        string capProperty = field->camelcase_name();
+        capProperty[0] = ::toupper(capProperty[0]);
+
+        propertyMap = {{"type", typeName},
+            {"property_name", field->camelcase_name()},
+            {"property_name_cap", capProperty}};
+        return true;
+    }
+    void printPreamble() {
+        mPrinter.Print("#pragma once\n"
+                       "#include <QObject>\n");
+    }
+
+    void printNamespaces() {
+        std::vector<string> namespaces;
+        utils::split(mPackage, namespaces, '.');
+        mNamespaceCount = namespaces.size();
+        for(auto ns: namespaces) {
+            mPrinter.Print({{"namespace", ns}}, NamespaceTemplate);
+        }
+    }
+
+    void printClass() {
+        mPrinter.Print({{"classname", mMessage->name()}}, StartTemplate);
+
+        //private section
+        for(int i = 0; i < mMessage->field_count(); i++) {
+            printField(mMessage->field(i), PropertyTemplate);
+        }
+        for(int i = 0; i < mMessage->field_count(); i++) {
+            printField(mMessage->field(i), MemberTemplate);
+        }
+        printQEnums();
+
+        //public section
+        mPrinter.Print("public:\n");
+        printConstructor();
+        for(int i = 0; i < mMessage->field_count(); i++) {
+            printField(mMessage->field(i), GetterTemplate);
+        }
+        for(int i = 0; i < mMessage->field_count(); i++) {
+            auto field = mMessage->field(i);
+            if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+                printField(field, SetterTemplateComplexType);
+            } else {
+                printField(field, SetterTemplateSimpleType);
+            }
+        }
+        mPrinter.Print("signals:\n");
+        for(int i = 0; i < mMessage->field_count(); i++) {
+            printField(mMessage->field(i), SignalTemplate);
+        }
+    }
+
+    void printField(const FieldDescriptor* field, const char* fieldTemplate) {
+        std::map<string, string> propertyMap;
+        if(producePropertyMap(field, propertyMap)) {
+            mPrinter.Print(propertyMap, fieldTemplate);
+        }
+    }
+
+    void enclose() {
+        mPrinter.Print("};\n");
+        while(mNamespaceCount > 0) {
+            mPrinter.Print("}\n");
+            --mNamespaceCount;
+        }
+    }
+
+    std::string getTypeName(const FieldDescriptor* field) {
+        string typeName;
+        if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+            typeName = field->message_type()->name();
+        } else {
+            auto it = TypeReflection.find(field->type());
+            if(it != std::end(TypeReflection)) {
+                typeName = it->second;
+            }
+        }
+
+        if (field->is_repeated()) {
+            typeName = typeName.append("Model");
+        }
+
+        return typeName;
+    }
+
+    void printQEnums() {
+        if (mMessage->enum_type_count() <= 0) {
+            return;
+        }
+
+        mPrinter.Print("    Q_ENUMS(");
+        for(int i = 0; mMessage->enum_type_count(); i++) {
+            const auto enumDescr = mMessage->enum_type(i);
+            mPrinter.Print(enumDescr->name().c_str());
+        }
+        mPrinter.Print(")\n\n");
+        mPrinter.Print("public:\n");
+        for(int i = 0; mMessage->enum_type_count(); i++) {
+            const auto enumDescr = mMessage->enum_type(i);
+            mPrinter.Print({{"enum", enumDescr->name()}}, "    enum $enum$ {\n");
+            for (int j = 0; j < enumDescr->value_count(); j++) {
+                const auto valueDescr = enumDescr->value(j);
+                mPrinter.Print({{"enumvalue", valueDescr->name()},
+                                {"value", std::to_string(valueDescr->number())}}, "        $enumvalue$ = $value$,\n");
+            }
+            mPrinter.Print("};\n");
+        }
+    }
+
+    void printConstructor() {
+        //FIXME: Explicit default values are not allowed in proto3 seems
+        //this function is useless
+        mPrinter.Print({{"classname", mMessage->name()}},
+                       "$classname$(QObject parent = nullptr) : QObject(parent)\n");
+        for (int i = 0; i < mMessage->field_count(); i++) {
+            const FieldDescriptor* field = mMessage->field(i);
+            std::string defaultValue;
+            if (field->has_default_value()) {
+                switch(field->type()) {
+                case FieldDescriptor::TYPE_DOUBLE:
+                    defaultValue = std::to_string(field->default_value_double());
+                    break;
+                case FieldDescriptor::TYPE_FLOAT:
+                    defaultValue = std::to_string(field->default_value_float());
+                    break;
+                case FieldDescriptor::TYPE_INT32:
+                case FieldDescriptor::TYPE_UINT32:
+                case FieldDescriptor::TYPE_SFIXED32:
+                case FieldDescriptor::TYPE_SINT32:
+                    defaultValue = std::to_string(field->default_value_int32());
+                    break;
+                case FieldDescriptor::TYPE_BOOL:
+                    defaultValue = field->default_value_bool() ? "true" : "false";
+                    break;
+                case FieldDescriptor::TYPE_STRING:
+                    defaultValue = field->default_value_string();
+                    break;
+                case FieldDescriptor::TYPE_ENUM:
+                    defaultValue = field->default_value_enum()->name();
+                    break;
+                default:
+                    std::cerr << "Default value substitution"
+                                 " is not supported for type"
+                              << field->type_name() << std::endl;
+                    break;
+                }
+                if (defaultValue.size() > 0) {
+                    mPrinter.Print({{"property_name", field->camelcase_name()},
+                                    {"default_value", defaultValue}},
+                                   ", $m_property_name$($default_value$)\n");
+                }
+            }
+        }
+        mPrinter.Print("{}\n\n");
+    }
+};
+
+bool QtGenerator::Generate(const FileDescriptor* file,
+                           const std::string& parameter,
+                           GeneratorContext* generator_context,
+                           std::string* error) const
+{
+    if (file->syntax() != FileDescriptor::SYNTAX_PROTO3) {
+        *error = "Invalid proto used. This plugin only supports 'proto3' syntax";
+        return false;
+    }
+
+    for(int i = 0; i < file->message_type_count(); i++) {
+        const Descriptor* message = file->message_type(i);
+        string filename = message->name() + ".h";
+        std::transform(std::begin(filename), std::end(filename), std::begin(filename), ::tolower);
+        QtClassGenerator classGen(file->package(), message, std::move(std::unique_ptr<io::ZeroCopyOutputStream>(generator_context->Open(filename))));
+        classGen.run();
+    }
+
+    return true;
+}

+ 45 - 0
src/generator/generator.h

@@ -0,0 +1,45 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <google/protobuf/compiler/code_generator.h>
+#include <string>
+
+namespace google { namespace protobuf {
+class FileDescriptor;
+namespace compiler {
+class GeneratorContext;
+}}}
+
+namespace qtprotobuf {
+
+class QtGenerator : public ::google::protobuf::compiler::CodeGenerator
+{
+    virtual bool Generate(const ::google::protobuf::FileDescriptor* file,
+                          const std::string& parameter,
+                          ::google::protobuf::compiler::GeneratorContext* generator_context,
+                          std::string* error) const override;
+};
+
+}  // namespace qtprotobuf

+ 32 - 0
src/generator/main.cpp

@@ -0,0 +1,32 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <google/protobuf/compiler/plugin.h>
+
+#include "generator.h"
+
+int main(int argc, char* argv[])
+{
+    qtprotobuf::QtGenerator generator;
+    return ::google::protobuf::compiler::PluginMain(argc, argv, &generator);
+}

+ 60 - 0
src/generator/templates.h

@@ -0,0 +1,60 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace qtprotobuf {
+
+static const char* StartTemplate = "class $classname$ : public QObject\n"
+                      "{\n"
+                      "    Q_OBJECT\n";
+
+static const char* NamespaceTemplate = "namespace $namespace$\n"
+                                  "{\n";
+static const char* PropertyTemplate = "    Q_PROPERTY($type$ $property_name$ READ $property_name$ WRITE set$property_name_cap$ NOTIFY $property_name$Changed)\n";
+static const char* GetterTemplate = "    $type$ $property_name$() const {\n"
+                                    "        return m_$property_name$;\n"
+                                    "    }\n";
+
+static const char* SetterTemplateSimpleType = "    void set$property_name_cap$($type$ $property_name$) {\n"
+                                              "        if (m_$property_name$ != $property_name$) {\n"
+                                              "             m_$property_name$ == $property_name$;\n"
+                                              "             $property_name$Changed();\n"
+                                              "        }\n"
+                                              "    }\n";
+
+static const char* SetterTemplateComplexType = "    void set$property_name_cap$(const $type$ &$property_name$) {\n"
+                                              "        if (m_$property_name$ != $property_name$) {\n"
+                                              "             m_$property_name$ == $property_name$;\n"
+                                              "             $property_name$Changed();\n"
+                                              "        }\n"
+                                              "    }\n";
+
+static const char* SignalTemplate = "    void $property_name$Changed();\n";
+static const char* MemberTemplate = "    $type$ m_$property_name$Changed();\n";
+
+static const char* EnumTemplate = "$type$";
+
+}