From 95aee1294e32aca966dd306667386460cd12182d Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sat, 18 Jan 2020 19:06:07 +0000 Subject: Refactor KConfigXT Summary: The current KConfigXT compiler is in a sad state: It's a massive file with loads of global variables that handle state, the generator is done within the main() function and it seems to have grown organically. There are no classes to separate logic / state / generation, what exists is code that generates code from a xml / ini pair, but it's hard to even discover what a bit of code is doing. The code istyle is C++ / Java from the nineties, which is not bad per see but it also uses quite a few things that are going to be deprecated in Qt 6 so I'm also taking the time make the code more streamlined with newer code style (no iterators, lambdas, auto usage, etc). The code that generates the files simplly pushes strings to a text stream, and it's hard to figure out when something starts or something ends: for instance, the code that generates the Constructor has more than sixty lines of code englobing some nested if - for - if - for constructs. Currently the code is "done" - there's one bug that I still need to find & fix regarding Translations, but the rest seems sane. The current testcode generates incorrect *whitespaces* regarding the old code (there's some parts that I feel that it's important to fix before merging, but overall, the whitespace changes are not bad and easier to handle, old code had a hand-counted amount of spaces before each line, new code has a function whitespace() that adds the current-and-correct amount of whitespaces based on indentation level that you start by startScope() and ends with endScope(). rest of the code still needs to be ported to it. I plan to fix the testcases whitespace by manually adding them, I'v fougth with the code for a while and added a few hacks there but I don't want to make the code hackish again. New code is not perfect by any means, but is a good step in the right direction. This code tries to Separate the compiler code into many different files / classes to be more obvious what's happening, and each class also has many helper methods to minimize copypaste. - CodeGenerator: Has base code for the header and source files that can be shared - HeaderGenerator: Logic for generating the header file - SourceGenerator: Logic for generating the source file - KcfgParser: Logic for parsing the kcfg file and extracting the information from the Xml file - CommonStructs: a header that contains the structs that are currently used everywhere. - KConfigParameters: (was CfgConfig - ConfigConfig, wat) - Has information passed via the kcfgc file - kcfg_compiler - will be renamed to main - start the other classes and generates the files. This code here currently has the begining of this separation, with the CodeGenerator and the HeaderGenerator in a ~good~ state, but unfinished. Test Plan: - Run the test cases, - Compare the diffs generated by the testcases and fix in the code the errors / differences - Run and compare real kde source with the new and old generators to look for errors Reviewers: #frameworks, ervin, bport, dfaure Reviewed By: dfaure Subscribers: davidre, bcooksley, cgiboudeaux, kossebau, bport, ngraham, kde-frameworks-devel Tags: #frameworks Differential Revision: https://phabricator.kde.org/D26202 --- src/kconfig_compiler/KConfigHeaderGenerator.cpp | 614 ++++++++++++++++++++++++ 1 file changed, 614 insertions(+) create mode 100644 src/kconfig_compiler/KConfigHeaderGenerator.cpp (limited to 'src/kconfig_compiler/KConfigHeaderGenerator.cpp') diff --git a/src/kconfig_compiler/KConfigHeaderGenerator.cpp b/src/kconfig_compiler/KConfigHeaderGenerator.cpp new file mode 100644 index 00000000..5fc16109 --- /dev/null +++ b/src/kconfig_compiler/KConfigHeaderGenerator.cpp @@ -0,0 +1,614 @@ +/* This file is part of the KDE libraries + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + Copyright (c) 2003 Cornelius Schumacher + Copyright (c) 2003 Waldo Bastian + Copyright (c) 2003 Zack Rusin + Copyright (c) 2006 MichaĆ«l Larouche + Copyright (c) 2008 Allen Winter + Copyright (C) 2020 Tomaz Cananbrava (tcanabrava@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "KConfigHeaderGenerator.h" +#include "KConfigCommonStructs.h" + +#include +#include + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +KConfigHeaderGenerator::KConfigHeaderGenerator( + const QString &inputFile, + const QString &baseDir, + const KConfigParameters &cfg, + ParseResult &result) + : KConfigCodeGeneratorBase(inputFile, baseDir, baseDir + cfg.baseName + QLatin1Char('.') + cfg.headerExtension, cfg, result) +{ +} + +void KConfigHeaderGenerator::start() +{ + KConfigCodeGeneratorBase::start(); + startHeaderGuards(); + createHeaders(); + + beginNamespaces(); + + createForwardDeclarations(); + + doClassDefinition(); + + endNamespaces(); + endHeaderGuards(); +} + +void KConfigHeaderGenerator::doClassDefinition() +{ + stream() << "class " << cfg().visibility << cfg().className << " : public " << cfg().inherits << endl; + startScope(); + + // Add Q_OBJECT macro if the config need signals. + if (!parseResult.signalList.isEmpty() || cfg().generateProperties) { + stream() << " Q_OBJECT" << endl; + } + stream() << " public:" << endl; + implementEnums(); + createConstructor(); + createDestructor(); + + for (auto *entry : parseResult.entries) { + const QString n = entry->name; + const QString t = entry->type; + + const QString returnType = (cfg().useEnumTypes && t == QLatin1String("Enum")) + ? enumType(entry, cfg().globalEnums) + : cppType(t); + + createSetters(entry); + createProperties(entry, returnType); + createGetters(entry, returnType); + createDefaultValueMember(entry); + createItemAcessors(entry, returnType); + } + + createSignals(); + stream() << " protected:" << endl; + createSingleton(); + + // TODO: Move those to functions too. + if (parseResult.hasNonModifySignals) { + stream() << whitespace() << "bool usrSave() override;" << endl; + } + + // Member variables + if (!cfg().memberVariables.isEmpty() + && cfg().memberVariables != QLatin1String("private") + && cfg().memberVariables != QLatin1String("dpointer")) { + stream() << " " << cfg().memberVariables << ":" << endl; + } + + // Class Parameters + for (const auto ¶meter : parseResult.parameters) { + stream() << whitespace() << "" << cppType(parameter.type) << " mParam" << parameter.name << ";" << endl; + } + + createNonDPointerHelpers(); + createDPointer(); + + if (cfg().customAddons) { + stream() << whitespace() << "// Include custom additions" << endl; + stream() << whitespace() << "#include \"" << cfg().baseName << "_addons." << cfg().headerExtension << '"' << endl; + } + + endScope(ScopeFinalizer::Semicolon); +} + +void KConfigHeaderGenerator::createHeaders() +{ + addHeaders(cfg().headerIncludes); + if (cfg().headerIncludes.size()) { + stream() << endl; + } + + if (!cfg().singleton && parseResult.parameters.isEmpty()) { + addHeaders({QStringLiteral("qglobal.h")}); + } + + if (cfg().inherits == QLatin1String("KCoreConfigSkeleton")) { + addHeaders({QStringLiteral("kcoreconfigskeleton.h")}); + } else { + addHeaders({QStringLiteral("kconfigskeleton.h")}); + } + + addHeaders({QStringLiteral("QCoreApplication"), QStringLiteral("QDebug")}); + stream() << endl; + + addHeaders(parseResult.includes); + if (parseResult.includes.size()) { + stream() << endl; + } +} + +void KConfigHeaderGenerator::startHeaderGuards() +{ + const bool hasNamespace = !cfg().nameSpace.isEmpty(); + const QString namespaceName = QString(QString(cfg().nameSpace).replace(QLatin1String("::"), QLatin1String("_"))).toUpper(); + const QString namespaceStr = hasNamespace ? namespaceName + QLatin1Char('_') : QStringLiteral(""); + const QString defineName = namespaceStr + cfg().className.toUpper() + QStringLiteral("_H"); + + stream() << "#ifndef " << defineName << endl; + stream() << "#define " << defineName << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::endHeaderGuards() +{ + stream() << endl; + stream() << "#endif"; + stream() << endl; + // HACK: Original files ended with two last newlines, add them. + stream() << endl; +} + +void KConfigHeaderGenerator::implementChoiceEnums(const CfgEntry *entry, const CfgEntry::Choices &choices) +{ + const QList chlist = choices.choices; + + if (chlist.isEmpty()) { + return; + } + + QStringList values; + for (const auto choice : qAsConst(chlist)) { + values.append(choices.prefix + choice.name); + } + + if (choices.name().isEmpty()) { + if (cfg().globalEnums) { + stream() << whitespace() << "enum " << enumName(entry->name, entry->choices) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + } else { + // Create an automatically named enum + stream() << whitespace() << "class " << enumName(entry->name, entry->choices) << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " public:" << endl; + stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; + stream() << whitespace() << "};" << endl; + } + } else if (!choices.external()) { + // Create a named enum + stream() << whitespace() << "enum " << enumName(entry->name, entry->choices) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + } +} + +void KConfigHeaderGenerator::implementValueEnums(const CfgEntry *entry, const QStringList &values) +{ + if (values.isEmpty()) { + return; + } + + if (cfg().globalEnums) { + // ### FIXME!! + // make the following string table an index-based string search! + // ### + stream() << whitespace() << "enum " << enumName(entry->param) << " { " << values.join(QStringLiteral(", ")) << " };" << endl; + stream() << whitespace() << "static const char* const " << enumName(entry->param) << "ToString[];" << endl; + } else { + stream() << whitespace() << "class " << enumName(entry->param) << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " public:" << endl; + stream() << whitespace() << " enum type { " << values.join(QStringLiteral(", ")) << ", COUNT };" << endl; + stream() << whitespace() << " static const char* const enumToString[];" << endl; + stream() << whitespace() << "};" << endl; + } +} + +void KConfigHeaderGenerator::implementEnums() +{ + if (!parseResult.entries.size()) { + return; + } + + for (const auto entry : parseResult.entries) { + const CfgEntry::Choices &choices = entry->choices; + const QStringList values = entry->paramValues; + + implementChoiceEnums(entry, choices); + implementValueEnums(entry, values); + } + stream() << endl; +} + +void KConfigHeaderGenerator::createSignals() +{ + // Signal definition. + const bool hasSignals = !parseResult.signalList.isEmpty(); + + unsigned val = 1 << parseResult.signalList.size(); + if (!val) { + cerr << "Too many signals to create unique bit masks" << endl; + exit(1); + } + + if (!hasSignals) { + return; + } + + stream() << "\n enum {" << endl; + val = 1; + + // HACK: Use C-Style for add a comma in all but the last element, + // just to make the source generated code equal to the old one. + // When we are sure, revert this to a range-based-for and just add + // a last comma, as it's valid c++. + for (int i = 0, end = parseResult.signalList.size(); i < end; i++) { + auto signal = parseResult.signalList.at(i); + stream() << whitespace() << " " << signalEnumName(signal.name) << " = 0x" << hex << val; + if (i != end-1) { + stream() << "," << endl; + } + + val <<= 1; + } + stream() << endl; + stream() << whitespace() << "};" << dec << endl << endl; + + stream() << " Q_SIGNALS:"; + for (const Signal &signal : parseResult.signalList) { + stream() << endl; + if (!signal.label.isEmpty()) { + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " " << signal.label << endl; + stream() << whitespace() << "*/" << endl; + } + stream() << whitespace() << "void " << signal.name << "("; + QList::ConstIterator it, itEnd = signal.arguments.constEnd(); + for (it = signal.arguments.constBegin(); it != itEnd;) { + Param argument = *it; + QString type = param(argument.type); + if (cfg().useEnumTypes && argument.type == QLatin1String("Enum")) { + for (auto *entry : parseResult.entries) { + if (entry->name == argument.name) { + type = enumType(entry, cfg().globalEnums); + break; + } + } + } + stream() << type << " " << argument.name; + if (++it != itEnd) { + stream() << ", "; + } + } + stream() << ");" << endl; + } + stream() << endl; + + stream() << " private:" << endl; + stream() << whitespace() << "void itemChanged(quint64 flags);" << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::createDPointer() +{ + if (!cfg().dpointer) { + return; + } + + // use a private class for both member variables and items + stream() << " private:" << endl; + for (const auto &entry : parseResult.entries) { + if (cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) { + stream() << whitespace() << ""; + if (cfg().staticAccessors) { + stream() << "static "; + } + stream() << cppType(entry->type) << " " << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << ";" << endl; + } + } + stream() << whitespace() << "" << cfg().className << "Private *d;" << endl; +} + +void KConfigHeaderGenerator::createConstructor() +{ + if (cfg().singleton) { + stream() << whitespace() << "static " << cfg().className << " *self();" << endl; + if (parseResult.cfgFileNameArg) { + stream() << whitespace() << "static void instance(const QString& cfgfilename);" << endl; + stream() << whitespace() << "static void instance(KSharedConfig::Ptr config);" << endl; + } + return; + } + + stream() << whitespace() << "" << cfg().className << "("; + if (parseResult.cfgFileNameArg) { + if (cfg().forceStringFilename) + stream() << " const QString &cfgfilename" << (parseResult.parameters.isEmpty() ? " = QString()" : ", "); + else + stream() << " KSharedConfig::Ptr config" << (parseResult.parameters.isEmpty() ? " = KSharedConfig::openConfig()" : ", "); + } + + bool first = true; + for (const auto parameter : parseResult.parameters) { + if (first) { + first = false; + } else { + stream() << ","; + } + + stream() << " " << param(parameter.type) << " " << parameter.name; + } + + if (cfg().parentInConstructor) { + if (parseResult.cfgFileNameArg || !parseResult.parameters.isEmpty()) { + stream() << ","; + } + stream() << " QObject *parent = nullptr"; + } + stream() << " );" << endl; +} + +void KConfigHeaderGenerator::createDestructor() +{ + stream() << whitespace() << "~" << cfg().className << "();" << endl << endl; +} + +void KConfigHeaderGenerator::createForwardDeclarations() +{ + // Private class declaration + if (cfg().dpointer) { + stream() << "class " << cfg().className << "Private;" << endl << endl; + } +} + +void KConfigHeaderGenerator::createProperties(const CfgEntry *entry, const QString& returnType) +{ + if (!cfg().generateProperties) { + return; + } + stream() << whitespace() << "Q_PROPERTY(" << returnType << ' ' << getFunction(entry->name); + stream() << " READ " << getFunction(entry->name); + + if (cfg().allMutators || cfg().mutators.contains(entry->name)) { + const QString signal = changeSignalName(entry->name); + stream() << " WRITE " << setFunction(entry->name); + stream() << " NOTIFY " << signal; + + //If we have the modified signal, we'll also need + //the changed signal as well + Signal s; + s.name = signal; + s.modify = true; + parseResult.signalList.append(s); + } else { + stream() << " CONSTANT"; + } + stream() << ")" << endl; +} + +void KConfigHeaderGenerator::createSetters(const CfgEntry *entry) +{ + // Manipulator + if (!cfg().allMutators && !cfg().mutators.contains(entry->name)) { + return; + } + + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Set " << entry->label << endl; + stream() << whitespace() << "*/" << endl; + + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + + stream() << whitespace() << "void " << setFunction(entry->name) << "( "; + if (!entry->param.isEmpty()) { + stream() <paramType) << " i, "; + } + + stream() << (cfg().useEnumTypes && entry->type == QLatin1String("Enum") + ? enumType(entry, cfg().globalEnums) + : param(entry->type)); + + stream() << " v )"; + + // function body inline only if not using dpointer + // for BC mode + if (!cfg().dpointer) { + stream() << endl; + startScope(); + memberMutatorBody(entry); + endScope(); + stream() << endl; + } else { + stream() << ";" << endl << endl; + } +} + +void KConfigHeaderGenerator::createGetters(const CfgEntry *entry, const QString &returnType) +{ + // Accessor + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get " << entry->label << endl; + stream() << whitespace() << "*/" << endl; + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + stream() << whitespace() << ""; + stream() << returnType; + stream() << " " << getFunction(entry->name) << "("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const(); + + // function body inline only if not using dpointer + // for BC mode + if (!cfg().dpointer) { + stream() << endl; + startScope(); + stream() << whitespace() << memberAccessorBody(entry, cfg().globalEnums); + endScope(); + stream() << endl; + } else { + stream() << ";" << endl << endl; + } +} + +void KConfigHeaderGenerator::createItemAcessors(const CfgEntry *entry, const QString &returnType) +{ + // Item accessor + if (!cfg().itemAccessors) { + return; + } + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get Item object corresponding to " << entry->name << "()" + << endl; + stream() << whitespace() << "*/" << endl; + stream() << whitespace() << "Item" << itemType(entry->type) << " *" + << getFunction(entry->name) << "Item("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")"; + if (!cfg().dpointer) { + stream() << endl; + startScope(); + stream() << whitespace() << itemAccessorBody(entry, cfg()); + endScope(); + } else { + stream() << ";" << endl; + } + + stream() <name)) && !entry->defaultValue.isEmpty())) { + return; + } + stream() << whitespace() << "/**" << endl; + stream() << whitespace() << " Get " << entry->label << " default value" << endl; + stream() << whitespace() << "*/" << endl; + if (cfg().staticAccessors) { + stream() << whitespace() << "static" << endl; + } + stream() << whitespace() << ""; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << enumType(entry, cfg().globalEnums); + } else { + stream() << cppType(entry->type); + } + stream() << " " << getDefaultFunction(entry->name) << "("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << endl; + stream() << whitespace() << "{" << endl; + stream() << whitespace() << " return "; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << "static_cast<" << enumType(entry, cfg().globalEnums) << ">("; + } + stream() << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " i "; + } + stream() << ")"; + if (cfg().useEnumTypes && entry->type == QLatin1String("Enum")) { + stream() << ")"; + } + stream() << ";" << endl; + stream() << whitespace() << "}" << endl; + stream() << endl; +} + +void KConfigHeaderGenerator::createSingleton() +{ + // Private constructor for singleton + if (!cfg().singleton) { + return; + } + + stream() << whitespace() << "" << cfg().className << "("; + if (parseResult.cfgFileNameArg) { + stream() << "KSharedConfig::Ptr config"; + } + if (cfg().parentInConstructor) { + if (parseResult.cfgFileNameArg) { + stream() << ", "; + } + stream() << "QObject *parent = nullptr"; + } + stream() << ");" << endl; + stream() << whitespace() << "friend class " << cfg().className << "Helper;" << endl << endl; +} + +void KConfigHeaderGenerator::createNonDPointerHelpers() +{ + if (cfg().memberVariables == QLatin1String("dpointer")) { + return; + } + + QString group; + for (auto *entry : parseResult.entries) { + if (entry->group != group) { + group = entry->group; + stream() << endl; + stream() << whitespace() << "// " << group << endl; + } + stream() << whitespace() << "" << cppType(entry->type) << " " << varName(entry->name, cfg()); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + + if (cfg().allDefaultGetters || cfg().defaultGetters.contains(entry->name)) { + stream() << whitespace() << ""; + if (cfg().staticAccessors) { + stream() << "static "; + } + stream() << cppType(entry->type) << " " << getDefaultFunction(entry->name) << "_helper("; + if (!entry->param.isEmpty()) { + stream() << " " << cppType(entry->paramType) << " i "; + } + stream() << ")" << Const() << ";" << endl; + } + } + + stream() << endl << " private:" << endl; + if (cfg().itemAccessors) { + for (auto *entry : parseResult.entries) { + stream() << whitespace() << "Item" << itemType(entry->type) << " *" << itemVar(entry, cfg()); + if (!entry->param.isEmpty()) { + stream() << QStringLiteral("[%1]").arg(entry->paramMax + 1); + } + stream() << ";" << endl; + } + } + + if (parseResult.hasNonModifySignals) { + stream() << whitespace() << "uint " << varName(QStringLiteral("settingsChanged"), cfg()) << ";" << endl; + } +} -- cgit v1.2.1