From 98c32e29f50465d4d4e16bafdf0491edbfb422b0 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Tue, 14 Jan 2020 18:52:43 +0000 Subject: WIP: 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: bport, ngraham, kde-frameworks-devel Tags: #frameworks Differential Revision: https://phabricator.kde.org/D26202 --- src/kconfig_compiler/KConfigCodeGeneratorBase.cpp | 276 ++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 src/kconfig_compiler/KConfigCodeGeneratorBase.cpp (limited to 'src/kconfig_compiler/KConfigCodeGeneratorBase.cpp') diff --git a/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp b/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp new file mode 100644 index 00000000..024af19c --- /dev/null +++ b/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp @@ -0,0 +1,276 @@ +/* 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 "KConfigCodeGeneratorBase.h" +#include "KConfigXTParameters.h" +#include "KConfigCommonStructs.h" + +#include +#include +#include + +#include +#include + +using std::endl; + +namespace +{ +QTextStream cout(stdout); +QTextStream cerr(stderr); +} + +KConfigCodeGeneratorBase::KConfigCodeGeneratorBase( + const QString& inputFile, + const QString& baseDir, + const QString& fileName, + const KConfigXTParameters ¶meters, + ParseResult &parseResult) + : inputFile(inputFile), baseDir(baseDir), fileName(fileName), cfg(parameters), parseResult(parseResult) +{ + file.setFileName(fileName); + if (!file.open(QIODevice::WriteOnly)) { + cerr << "Can not open '" << fileName << "for writing." << endl; + exit(1); + } + stream.setDevice(&file); + stream.setCodec("utf-8"); + + if (cfg.staticAccessors) { + This = QStringLiteral("self()->"); + } else { + Const = QStringLiteral(" const"); + } +} + +KConfigCodeGeneratorBase::~KConfigCodeGeneratorBase() +{ + save(); +} + +void KConfigCodeGeneratorBase::save() +{ + file.close(); +} + +void KConfigCodeGeneratorBase::indent() +{ + if (indentLevel >= 4) { + indentLevel += 2; + } else { + indentLevel += 4; + } +} + +void KConfigCodeGeneratorBase::unindent() +{ + if (indentLevel > 4) { + indentLevel -= 2; + } else { + indentLevel -= 4; + } +} + +QString KConfigCodeGeneratorBase::whitespace() +{ + QString spaces; + for (int i = 0; i < indentLevel; i++) { + spaces.append(QLatin1Char(' ')); + } + return spaces; +} + +void KConfigCodeGeneratorBase::startScope() +{ + stream << whitespace() << QLatin1Char('{'); + stream << endl; + indent(); +} + +void KConfigCodeGeneratorBase::endScope(ScopeFinalizer finalizer) +{ + unindent(); + stream << whitespace() << QLatin1Char('}'); + if (finalizer == ScopeFinalizer::Semicolon) { + stream << ';'; + } + stream << endl; +} + +void KConfigCodeGeneratorBase::start() +{ + const QString fileName = QFileInfo(inputFile).fileName(); + stream << "// This file is generated by kconfig_compiler_kf5 from " << fileName << ".kcfg" << "." << endl; + stream << "// All changes you do to this file will be lost." << endl; +} + +void KConfigCodeGeneratorBase::addHeaders(const QStringList& headerList) +{ + for (auto include : qAsConst(headerList)) { + if (include.startsWith(QLatin1Char('"'))) { + stream << "#include " << include << endl; + } else { + stream << "#include <" << include << ">" << endl; + } + } +} + +// adds as many 'namespace foo {' lines to p_out as +// there are namespaces in p_ns +void KConfigCodeGeneratorBase::beginNamespaces() +{ + if (!cfg.nameSpace.isEmpty()) { + for (const QString &ns : cfg.nameSpace.split(QStringLiteral("::"))) { + stream << "namespace " << ns << " {" << endl; + } + stream << endl; + } +} + +// adds as many '}' lines to p_out as +// there are namespaces in p_ns +void KConfigCodeGeneratorBase::endNamespaces() +{ + if (!cfg.nameSpace.isEmpty()) { + stream << endl; + const int namespaceCount = cfg.nameSpace.count(QStringLiteral("::")) + 1; + for (int i = 0; i < namespaceCount; ++i) { + stream << "}" << endl; + } + } +} + +// returns the member accesor implementation +// which should go in the h file if inline +// or the cpp file if not inline +QString KConfigCodeGeneratorBase::memberAccessorBody(const CfgEntry *e, bool globalEnums) const +{ + QString result; + QTextStream out(&result, QIODevice::WriteOnly); + QString n = e->name; + QString t = e->type; + bool useEnumType = cfg.useEnumTypes && t == QLatin1String("Enum"); + + out << "return "; + if (useEnumType) { + out << "static_cast<" << enumType(e, globalEnums) << ">("; + } + out << This << varPath(n, cfg); + if (!e->param.isEmpty()) { + out << "[i]"; + } + if (useEnumType) { + out << ")"; + } + out << ";" << endl; + + return result; +} + +void KConfigCodeGeneratorBase::createIfSetLogic(const CfgEntry *e, const QString &varExpression) +{ + const QString n = e->name; + const QString t = e->type; + const bool hasBody = !e->signalList.empty() || cfg.generateProperties; + + stream << whitespace() << "if ("; + if (hasBody) { + stream << "v != " << varExpression << " && "; + } + stream << "!" << This << "isImmutable( QStringLiteral( \""; + if (!e->param.isEmpty()) { + QString paramName = e->paramName; + + stream << paramName.replace(QStringLiteral("$(") + e->param + QStringLiteral(")"), QLatin1String("%1")) << "\" ).arg( "; + if (e->paramType == QLatin1String("Enum")) { + stream << "QLatin1String( "; + + if (cfg.globalEnums) { + stream << enumName(e->param) << "ToString[i]"; + } else { + stream << enumName(e->param) << "::enumToString[i]"; + } + + stream << " )"; + } else { + stream << "i"; + } + stream << " )"; + } else { + stream << n << "\" )"; + } + stream << " ))"; +} + +void KConfigCodeGeneratorBase::memberMutatorBody(const CfgEntry *e) +{ + QString n = e->name; + QString t = e->type; + + // HACK: Don't open '{' manually, use startScope / endScope to automatically handle whitespace indentation. + if (!e->min.isEmpty()) { + if (e->min != QLatin1String("0") || !isUnsigned(t)) { // skip writing "if uint<0" (#187579) + stream << whitespace() << "if (v < " << e->min << ")" << endl; + stream << whitespace() << "{" << endl; + stream << whitespace(); addDebugMethod(stream, cfg, n); + stream << ": value \" << v << \" is less than the minimum value of " << e->min << "\";" << endl; + stream << whitespace() << " v = " << e->min << ";" << endl; + stream << whitespace() << "}" << endl; + } + } + + if (!e->max.isEmpty()) { + stream << endl; + stream << whitespace() << "if (v > " << e->max << ")" << endl; + stream << whitespace() << "{" << endl; + stream << whitespace(); addDebugMethod(stream, cfg, n); + stream << ": value \" << v << \" is greater than the maximum value of " << e->max << "\";" << endl; + stream << whitespace() << " v = " << e->max << ";" << endl; + stream << whitespace() << "}" << endl << endl; + } + + const QString varExpression = This + varPath(n, cfg) + (e->param.isEmpty() ? QString() : QStringLiteral("[i]")); + + // TODO: Remove this `hasBody` logic, always use an '{' for the if. + const bool hasBody = !e->signalList.empty() || cfg.generateProperties; + + // This call creates an `if (someTest ...) that's just to long to throw over the code. + createIfSetLogic(e, varExpression); + stream << (hasBody ? " {" : "") << endl; + stream << whitespace() << " " << varExpression << " = v;" << endl; + + const auto listSignal = e->signalList; + for (const Signal &signal : qAsConst(listSignal)) { + if (signal.modify) { + stream << whitespace() << " Q_EMIT " << This << signal.name << "();" << endl; + } else { + stream << whitespace() << " " << This << varPath(QStringLiteral("settingsChanged"), cfg) + << " |= " << signalEnumName(signal.name) << ";" << endl; + } + } + if (hasBody) { + stream << whitespace() << "}" << endl; + } +} -- cgit v1.2.1