1/*
2 This file is part of the KDE libraries.
3
4 SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org>
5 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
6 SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
7 SPDX-FileCopyrightText: 2006 Michaƫl Larouche <michael.larouche@kdemail.net>
8 SPDX-FileCopyrightText: 2008 Allen Winter <winter@kde.org>
9 SPDX-FileCopyrightText: 2020 Tomaz Cananbrava <tcanabrava@kde.org>
10
11 SPDX-License-Identifier: LGPL-2.0-or-later
12*/
13
14#include "KConfigCodeGeneratorBase.h"
15
16#include <QFileInfo>
17#include <QLatin1Char>
18
19#include <QDebug>
20#include <ostream>
21
22#include <iostream>
23
24KConfigCodeGeneratorBase::KConfigCodeGeneratorBase(const QString &inputFile,
25 const QString &baseDir,
26 const QString &fileName,
27 const KConfigParameters &parameters,
28 ParseResult &parseResult)
29 : parseResult(parseResult)
30 , m_inputFile(inputFile)
31 , m_baseDir(baseDir)
32 , m_fileName(fileName)
33 , m_cfg(parameters)
34{
35 m_file.setFileName(m_fileName);
36 if (!m_file.open(flags: QIODevice::WriteOnly)) {
37 std::cerr << "Can not open '" << qPrintable(m_fileName) << "for writing." << std::endl;
38 exit(status: 1);
39 }
40 m_stream.setDevice(&m_file);
41
42 if (m_cfg.staticAccessors) {
43 m_this = QStringLiteral("self()->");
44 } else {
45 m_const = QStringLiteral(" const");
46 }
47}
48
49KConfigCodeGeneratorBase::~KConfigCodeGeneratorBase()
50{
51 save();
52}
53
54void KConfigCodeGeneratorBase::save()
55{
56 m_file.close();
57}
58
59// TODO: Remove this weird logic and adapt the testcases
60void KConfigCodeGeneratorBase::indent()
61{
62 if (m_indentLevel >= 4) {
63 m_indentLevel += 2;
64 } else {
65 m_indentLevel += 4;
66 }
67}
68
69void KConfigCodeGeneratorBase::unindent()
70{
71 if (m_indentLevel > 4) {
72 m_indentLevel -= 2;
73 } else {
74 m_indentLevel -= 4;
75 }
76}
77
78QString KConfigCodeGeneratorBase::whitespace() const
79{
80 QString spaces;
81 for (int i = 0; i < m_indentLevel; i++) {
82 spaces.append(c: QLatin1Char(' '));
83 }
84 return spaces;
85}
86
87void KConfigCodeGeneratorBase::startScope()
88{
89 m_stream << whitespace() << QLatin1Char('{');
90 m_stream << '\n';
91 indent();
92}
93
94void KConfigCodeGeneratorBase::endScope(ScopeFinalizer finalizer)
95{
96 unindent();
97 m_stream << whitespace() << QLatin1Char('}');
98 if (finalizer == ScopeFinalizer::Semicolon) {
99 m_stream << ';';
100 }
101 m_stream << '\n';
102}
103
104void KConfigCodeGeneratorBase::start()
105{
106 const QString m_fileName = QFileInfo(m_inputFile).fileName();
107 m_stream << "// This file is generated by kconfig_compiler_kf6 from " << m_fileName << ".kcfg"
108 << ".\n";
109 m_stream << "// All changes you do to this file will be lost.\n";
110}
111
112void KConfigCodeGeneratorBase::addHeaders(const QStringList &headerList)
113{
114 for (const auto &include : headerList) {
115 if (include.startsWith(c: QLatin1Char('"'))) {
116 m_stream << "#include " << include << '\n';
117 } else {
118 m_stream << "#include <" << include << ">\n";
119 }
120 }
121}
122
123// adds as many 'namespace foo {' lines to p_out as
124// there are namespaces in p_ns
125void KConfigCodeGeneratorBase::beginNamespaces()
126{
127 if (!m_cfg.nameSpace.isEmpty()) {
128 const auto nameSpaceList = m_cfg.nameSpace.split(QStringLiteral("::"));
129 for (const QString &ns : nameSpaceList) {
130 m_stream << "namespace " << ns << " {\n";
131 }
132 m_stream << '\n';
133 }
134}
135
136// adds as many '}' lines to p_out as
137// there are namespaces in p_ns
138void KConfigCodeGeneratorBase::endNamespaces()
139{
140 if (!m_cfg.nameSpace.isEmpty()) {
141 m_stream << '\n';
142 const int namespaceCount = m_cfg.nameSpace.count(QStringLiteral("::")) + 1;
143 for (int i = 0; i < namespaceCount; ++i) {
144 m_stream << "}\n";
145 }
146 }
147}
148
149// returns the member accessor implementation
150// which should go in the h file if inline
151// or the cpp file if not inline
152QString KConfigCodeGeneratorBase::memberAccessorBody(const CfgEntry *e, bool globalEnums) const
153{
154 QString result;
155 QTextStream out(&result, QIODevice::WriteOnly);
156 QString n = e->name;
157 QString t = e->type;
158 bool useEnumType = m_cfg.useEnumTypes && t == QLatin1String("Enum");
159
160 out << "return ";
161 if (useEnumType) {
162 out << "static_cast<" << enumType(e, globalEnums) << ">(";
163 }
164 out << m_this << varPath(n, cfg: m_cfg);
165 if (!e->param.isEmpty()) {
166 out << "[i]";
167 }
168 if (useEnumType) {
169 out << ")";
170 }
171 out << ";\n";
172
173 return result;
174}
175
176void KConfigCodeGeneratorBase::memberImmutableBody(const CfgEntry *e, bool globalEnums)
177{
178 stream() << whitespace() << "return " << m_this << "isImmutable( QStringLiteral( \"";
179 if (!e->param.isEmpty()) {
180 stream() << QString(e->paramName).replace(before: QLatin1String("$(%1)").arg(args: e->param), after: QLatin1String("%1")) << "\" ).arg( ";
181 if (e->paramType == QLatin1String("Enum")) {
182 stream() << "QLatin1String( ";
183
184 if (globalEnums) {
185 stream() << enumName(n: e->param) << "ToString[i]";
186 } else {
187 stream() << enumName(n: e->param) << "::enumToString[i]";
188 }
189
190 stream() << " )";
191 } else {
192 stream() << "i";
193 }
194 stream() << " )";
195 } else {
196 stream() << e->name << "\" )";
197 }
198 stream() << " );\n";
199}
200
201void KConfigCodeGeneratorBase::createIfSetLogic(const CfgEntry *e, const QString &varExpression)
202{
203 const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties;
204
205 m_stream << whitespace() << "if (";
206 if (hasBody) {
207 m_stream << "v != " << varExpression << " && ";
208 }
209
210 const auto immutablefunction = immutableFunction(n: e->name, className: m_cfg.dpointer ? m_cfg.className : QString{});
211 m_stream << "!" << m_this << immutablefunction << "(";
212 if (!e->param.isEmpty()) {
213 m_stream << " i ";
214 }
215 m_stream << "))";
216}
217
218void KConfigCodeGeneratorBase::memberMutatorBody(const CfgEntry *e)
219{
220 // HACK: Don't open '{' manually, use startScope / endScope to automatically handle whitespace indentation.
221 if (!e->min.isEmpty()) {
222 if (e->min != QLatin1String("0") || !isUnsigned(type: e->type)) { // skip writing "if uint<0" (#187579)
223 m_stream << whitespace() << "if (v < " << e->min << ")\n";
224 m_stream << whitespace() << "{\n";
225 m_stream << whitespace();
226 addDebugMethod(out&: m_stream, cfg: m_cfg, n: e->name);
227 m_stream << ": value \" << v << \" is less than the minimum value of " << e->min << "\";\n";
228 m_stream << whitespace() << " v = " << e->min << ";\n";
229 m_stream << whitespace() << "}\n";
230 }
231 }
232
233 if (!e->max.isEmpty()) {
234 m_stream << '\n';
235 m_stream << whitespace() << "if (v > " << e->max << ")\n";
236 m_stream << whitespace() << "{\n";
237 m_stream << whitespace();
238 addDebugMethod(out&: m_stream, cfg: m_cfg, n: e->name);
239 m_stream << ": value \" << v << \" is greater than the maximum value of " << e->max << "\";\n";
240 m_stream << whitespace() << " v = " << e->max << ";\n";
241 m_stream << whitespace() << "}\n\n";
242 }
243
244 const QString varExpression = m_this + varPath(n: e->name, cfg: m_cfg) + (e->param.isEmpty() ? QString{} : QStringLiteral("[i]"));
245
246 // TODO: Remove this `hasBody` logic, always use an '{' for the if.
247 const bool hasBody = !e->signalList.empty() || m_cfg.generateProperties;
248
249 // m_this call creates an `if (someTest ...) that's just to long to throw over the code.
250 createIfSetLogic(e, varExpression);
251 m_stream << (hasBody ? " {" : "") << '\n';
252 m_stream << whitespace() << " " << varExpression << " = v;\n";
253
254 const auto listSignal = e->signalList;
255 for (const Signal &signal : std::as_const(t: listSignal)) {
256 if (signal.modify) {
257 m_stream << whitespace() << " Q_EMIT " << m_this << signal.name << "();\n";
258 } else {
259 m_stream << whitespace() << " " << m_this << varPath(QStringLiteral("settingsChanged"), cfg: m_cfg) << ".insert(" << signalEnumName(signalName: signal.name) << ");\n";
260 }
261 }
262 if (hasBody) {
263 m_stream << whitespace() << "}\n";
264 }
265}
266

source code of kconfig/src/kconfig_compiler/KConfigCodeGeneratorBase.cpp