1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtScxml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "scxmlcppdumper.h" |
30 | #include "generator.h" |
31 | |
32 | #include <QtScxml/private/qscxmlexecutablecontent_p.h> |
33 | |
34 | #include <QtCore/qfileinfo.h> |
35 | #include <QtCore/qbuffer.h> |
36 | #include <QtCore/qfile.h> |
37 | #include <QtCore/qresource.h> |
38 | |
39 | #include <algorithm> |
40 | #include <functional> |
41 | |
42 | QT_BEGIN_NAMESPACE |
43 | |
44 | using namespace QScxmlInternal; |
45 | |
46 | namespace { |
47 | |
48 | static const QString = QString::fromLatin1( |
49 | str: "//\n" |
50 | "// Statemachine code from reading SCXML file '%1'\n" |
51 | "// Created by: The Qt SCXML Compiler version %2 (Qt %3)\n" |
52 | "// WARNING! All changes made in this file will be lost!\n" |
53 | "//\n" |
54 | ); |
55 | |
56 | static const QString revisionCheck = QString::fromLatin1( |
57 | str: "#if !defined(Q_QSCXMLC_OUTPUT_REVISION)\n" |
58 | "#error \"The header file '%1' doesn't include <qscxmltabledata.h>.\"\n" |
59 | "#elif Q_QSCXMLC_OUTPUT_REVISION != %2\n" |
60 | "#error \"This file was generated using the qscxmlc from %3. It\"\n" |
61 | "#error \"cannot be used with the include files from this version of Qt.\"\n" |
62 | "#error \"(The qscxmlc has changed too much.)\"\n" |
63 | "#endif\n" |
64 | ); |
65 | |
66 | QString cEscape(const QString &str) |
67 | { |
68 | QString res; |
69 | int lastI = 0; |
70 | for (int i = 0; i < str.length(); ++i) { |
71 | QChar c = str.at(i); |
72 | if (c < QLatin1Char(' ') || c == QLatin1Char('\\') || c == QLatin1Char('\"')) { |
73 | res.append(s: str.mid(position: lastI, n: i - lastI)); |
74 | lastI = i + 1; |
75 | if (c == QLatin1Char('\\')) { |
76 | res.append(s: QLatin1String("\\\\" )); |
77 | } else if (c == QLatin1Char('\"')) { |
78 | res.append(s: QLatin1String("\\\"" )); |
79 | } else if (c == QLatin1Char('\n')) { |
80 | res.append(s: QLatin1String("\\n" )); |
81 | } else if (c == QLatin1Char('\r')) { |
82 | res.append(s: QLatin1String("\\r" )); |
83 | } else { |
84 | char buf[6]; |
85 | ushort cc = c.unicode(); |
86 | buf[0] = '\\'; |
87 | buf[1] = 'u'; |
88 | for (int i = 0; i < 4; ++i) { |
89 | ushort ccc = cc & 0xF; |
90 | buf[5 - i] = ccc <= 9 ? '0' + ccc : 'a' + ccc - 10; |
91 | cc >>= 4; |
92 | } |
93 | res.append(s: QLatin1String(&buf[0], 6)); |
94 | } |
95 | } |
96 | } |
97 | if (lastI != 0) { |
98 | res.append(s: str.mid(position: lastI)); |
99 | return res; |
100 | } |
101 | return str; |
102 | } |
103 | |
104 | typedef QHash<QString, QString> Replacements; |
105 | static void genTemplate(QTextStream &out, const QString &filename, const Replacements &replacements) |
106 | { |
107 | QResource file(filename); |
108 | if (!file.isValid()) { |
109 | qFatal(msg: "Unable to open template '%s'" , qPrintable(filename)); |
110 | } |
111 | Q_ASSERT(file.compressionAlgorithm() == QResource::NoCompression); |
112 | QByteArray data; |
113 | data = QByteArray::fromRawData(reinterpret_cast<const char *>(file.data()), |
114 | size: int(file.size())); |
115 | const QString t = QString::fromLatin1(str: data); |
116 | data.clear(); |
117 | |
118 | int start = 0; |
119 | for (int openIdx = t.indexOf(QStringLiteral("${" ), from: start); openIdx >= 0; openIdx = |
120 | t.indexOf(QStringLiteral("${" ), from: start)) { |
121 | out << t.midRef(position: start, n: openIdx - start); |
122 | openIdx += 2; |
123 | const int closeIdx = t.indexOf(c: QLatin1Char('}'), from: openIdx); |
124 | Q_ASSERT(closeIdx >= openIdx); |
125 | QString key = t.mid(position: openIdx, n: closeIdx - openIdx); |
126 | if (!replacements.contains(akey: key)) { |
127 | qFatal(msg: "Replacing '%s' failed: no replacement found" , qPrintable(key)); |
128 | } |
129 | out << replacements.value(akey: key); |
130 | start = closeIdx + 1; |
131 | } |
132 | out << t.midRef(position: start); |
133 | } |
134 | |
135 | static const char * = |
136 | "#include <QScxmlStateMachine>\n" |
137 | "#include <QString>\n" |
138 | "#include <QVariant>\n" |
139 | "\n" ; |
140 | |
141 | using namespace DocumentModel; |
142 | |
143 | QString createContainer(const QStringList &elements) |
144 | { |
145 | QString result; |
146 | if (elements.isEmpty()) { |
147 | result += QStringLiteral("{}" ); |
148 | } else { |
149 | result += QStringLiteral("{ " ) + elements.join(QStringLiteral(", " )) + QStringLiteral(" }" ); |
150 | } |
151 | return result; |
152 | } |
153 | |
154 | static void generateList(QString &out, std::function<QString(int)> next) |
155 | { |
156 | const int maxLineLength = 80; |
157 | QString line; |
158 | for (int i = 0; ; ++i) { |
159 | const QString nr = next(i); |
160 | if (nr.isNull()) |
161 | break; |
162 | |
163 | if (i != 0) |
164 | line += QLatin1Char(','); |
165 | |
166 | if (line.length() + nr.length() + 1 > maxLineLength) { |
167 | out += line + QLatin1Char('\n'); |
168 | line.clear(); |
169 | } else if (i != 0) { |
170 | line += QLatin1Char(' '); |
171 | } |
172 | line += nr; |
173 | } |
174 | if (!line.isEmpty()) |
175 | out += line; |
176 | } |
177 | |
178 | void generateTables(const GeneratedTableData &td, Replacements &replacements) |
179 | { |
180 | { // instructions |
181 | auto instr = td.theInstructions; |
182 | QString out; |
183 | generateList(out, next: [&instr](int idx) -> QString { |
184 | if (instr.isEmpty() && idx == 0) // prevent generation of empty array |
185 | return QStringLiteral("-1" ); |
186 | if (idx < instr.size()) |
187 | return QString::number(instr.at(i: idx)); |
188 | else |
189 | return QString(); |
190 | }); |
191 | replacements[QStringLiteral("theInstructions" )] = out; |
192 | } |
193 | |
194 | { // dataIds |
195 | auto dataIds = td.theDataNameIds; |
196 | QString out; |
197 | generateList(out, next: [&dataIds](int idx) -> QString { |
198 | if (dataIds.size() == 0 && idx == 0) // prevent generation of empty array |
199 | return QStringLiteral("-1" ); |
200 | if (idx < dataIds.size()) |
201 | return QString::number(dataIds[idx]); |
202 | else |
203 | return QString(); |
204 | }); |
205 | replacements[QStringLiteral("dataNameCount" )] = QString::number(dataIds.size()); |
206 | replacements[QStringLiteral("dataIds" )] = out; |
207 | } |
208 | |
209 | { // evaluators |
210 | auto evaluators = td.theEvaluators; |
211 | QString out; |
212 | generateList(out, next: [&evaluators](int idx) -> QString { |
213 | if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array |
214 | return QStringLiteral("{ -1, -1 }" ); |
215 | if (idx >= evaluators.size()) |
216 | return QString(); |
217 | |
218 | const auto eval = evaluators.at(i: idx); |
219 | return QStringLiteral("{ %1, %2 }" ).arg(a: eval.expr).arg(a: eval.context); |
220 | }); |
221 | replacements[QStringLiteral("evaluatorCount" )] = QString::number(evaluators.size()); |
222 | replacements[QStringLiteral("evaluators" )] = out; |
223 | } |
224 | |
225 | { // assignments |
226 | auto assignments = td.theAssignments; |
227 | QString out; |
228 | generateList(out, next: [&assignments](int idx) -> QString { |
229 | if (assignments.isEmpty() && idx == 0) // prevent generation of empty array |
230 | return QStringLiteral("{ -1, -1, -1 }" ); |
231 | if (idx >= assignments.size()) |
232 | return QString(); |
233 | |
234 | auto assignment = assignments.at(i: idx); |
235 | return QStringLiteral("{ %1, %2, %3 }" ) |
236 | .arg(a: assignment.dest).arg(a: assignment.expr).arg(a: assignment.context); |
237 | }); |
238 | replacements[QStringLiteral("assignmentCount" )] = QString::number(assignments.size()); |
239 | replacements[QStringLiteral("assignments" )] = out; |
240 | } |
241 | |
242 | { // foreaches |
243 | auto foreaches = td.theForeaches; |
244 | QString out; |
245 | generateList(out, next: [&foreaches](int idx) -> QString { |
246 | if (foreaches.isEmpty() && idx == 0) // prevent generation of empty array |
247 | return QStringLiteral("{ -1, -1, -1, -1 }" ); |
248 | if (idx >= foreaches.size()) |
249 | return QString(); |
250 | |
251 | auto foreachItem = foreaches.at(i: idx); |
252 | return QStringLiteral("{ %1, %2, %3, %4 }" ).arg(a: foreachItem.array).arg(a: foreachItem.item) |
253 | .arg(a: foreachItem.index).arg(a: foreachItem.context); |
254 | }); |
255 | replacements[QStringLiteral("foreachCount" )] = QString::number(foreaches.size()); |
256 | replacements[QStringLiteral("foreaches" )] = out; |
257 | } |
258 | |
259 | { // strings |
260 | QString out; |
261 | auto strings = td.theStrings; |
262 | if (strings.isEmpty()) // prevent generation of empty array |
263 | strings.append(QStringLiteral("" )); |
264 | int ucharCount = 0; |
265 | generateList(out, next: [&ucharCount, &strings](int idx) -> QString { |
266 | if (idx >= strings.size()) |
267 | return QString(); |
268 | |
269 | const int length = strings.at(i: idx).size(); |
270 | const QString str = QStringLiteral("STR_LIT(%1, %2, %3)" ).arg( |
271 | args: QString::number(idx), args: QString::number(ucharCount), args: QString::number(length)); |
272 | ucharCount += length + 1; |
273 | return str; |
274 | }); |
275 | replacements[QStringLiteral("stringCount" )] = QString::number(strings.size()); |
276 | replacements[QStringLiteral("strLits" )] = out; |
277 | |
278 | out.clear(); |
279 | for (int i = 0, ei = strings.size(); i < ei; ++i) { |
280 | const QString &string = strings.at(i); |
281 | QString result; |
282 | if (i != 0) |
283 | result += QLatin1Char('\n'); |
284 | for (int charPos = 0, eCharPos = string.size(); charPos < eCharPos; ++charPos) { |
285 | result.append(QStringLiteral("0x%1," ) |
286 | .arg(a: QString::number(string.at(i: charPos).unicode(), base: 16))); |
287 | } |
288 | result.append(QStringLiteral("0%1 // %2: %3" ) |
289 | .arg(args: QLatin1String(i < ei - 1 ? "," : "" ), args: QString::number(i), |
290 | args: cEscape(str: string))); |
291 | out += result; |
292 | } |
293 | replacements[QStringLiteral("uniLits" )] = out; |
294 | replacements[QStringLiteral("stringdataSize" )] = QString::number(ucharCount + 1); |
295 | } |
296 | } |
297 | |
298 | void generateCppDataModelEvaluators(const GeneratedTableData::DataModelInfo &info, |
299 | Replacements &replacements) |
300 | { |
301 | const QString switchStart = QStringLiteral(" switch (id) {\n" ); |
302 | const QString switchEnd = QStringLiteral(" default: break;\n }" ); |
303 | const QString unusedId = QStringLiteral(" Q_UNUSED(id);" ); |
304 | QString stringEvals; |
305 | if (!info.stringEvaluators.isEmpty()) { |
306 | stringEvals += switchStart; |
307 | for (auto it = info.stringEvaluators.constBegin(), eit = info.stringEvaluators.constEnd(); |
308 | it != eit; ++it) { |
309 | stringEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
310 | stringEvals += QStringLiteral(" return [this]()->QString{ return %1; }();\n" ) |
311 | .arg(a: it.value()); |
312 | } |
313 | stringEvals += switchEnd; |
314 | } else { |
315 | stringEvals += unusedId; |
316 | } |
317 | replacements[QStringLiteral("evaluateToStringCases" )] = stringEvals; |
318 | |
319 | QString boolEvals; |
320 | if (!info.boolEvaluators.isEmpty()) { |
321 | boolEvals += switchStart; |
322 | for (auto it = info.boolEvaluators.constBegin(), eit = info.boolEvaluators.constEnd(); |
323 | it != eit; ++it) { |
324 | boolEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
325 | boolEvals += QStringLiteral(" return [this]()->bool{ return %1; }();\n" ) |
326 | .arg(a: it.value()); |
327 | } |
328 | boolEvals += switchEnd; |
329 | } else { |
330 | boolEvals += unusedId; |
331 | } |
332 | replacements[QStringLiteral("evaluateToBoolCases" )] = boolEvals; |
333 | |
334 | QString variantEvals; |
335 | if (!info.variantEvaluators.isEmpty()) { |
336 | variantEvals += switchStart; |
337 | for (auto it = info.variantEvaluators.constBegin(), eit = info.variantEvaluators.constEnd(); |
338 | it != eit; ++it) { |
339 | variantEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
340 | variantEvals += QStringLiteral(" return [this]()->QVariant{ return %1; }();\n" ) |
341 | .arg(a: it.value()); |
342 | } |
343 | variantEvals += switchEnd; |
344 | } else { |
345 | variantEvals += unusedId; |
346 | } |
347 | replacements[QStringLiteral("evaluateToVariantCases" )] = variantEvals; |
348 | |
349 | QString voidEvals; |
350 | if (!info.voidEvaluators.isEmpty()) { |
351 | voidEvals = switchStart; |
352 | for (auto it = info.voidEvaluators.constBegin(), eit = info.voidEvaluators.constEnd(); |
353 | it != eit; ++it) { |
354 | voidEvals += QStringLiteral(" case %1:\n" ).arg(a: it.key()); |
355 | voidEvals += QStringLiteral(" [this]()->void{ %1 }();\n" ).arg(a: it.value()); |
356 | voidEvals += QStringLiteral(" return;\n" ); |
357 | } |
358 | voidEvals += switchEnd; |
359 | } else { |
360 | voidEvals += unusedId; |
361 | } |
362 | replacements[QStringLiteral("evaluateToVoidCases" )] = voidEvals; |
363 | } |
364 | |
365 | int createFactoryId(QStringList &factories, const QString &className, |
366 | const QString &namespacePrefix, |
367 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
368 | const QVector<QScxmlExecutableContent::StringId> &namelist, |
369 | const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters) |
370 | { |
371 | const int idx = factories.size(); |
372 | |
373 | QString line = QStringLiteral("case %1: return new " ).arg(a: QString::number(idx)); |
374 | if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) { |
375 | line += QStringLiteral("QScxmlStaticScxmlServiceFactory(&%1::%2::staticMetaObject," ) |
376 | .arg(a1: namespacePrefix, a2: className); |
377 | } else { |
378 | line += QStringLiteral("QScxmlDynamicScxmlServiceFactory(" ); |
379 | } |
380 | line += QStringLiteral("invoke(%1, %2, %3, %4, %5, %6, %7), " ) |
381 | .arg(args: QString::number(invokeInfo.id), |
382 | args: QString::number(invokeInfo.prefix), |
383 | args: QString::number(invokeInfo.expr), |
384 | args: QString::number(invokeInfo.location), |
385 | args: QString::number(invokeInfo.context), |
386 | args: QString::number(invokeInfo.finalize)) |
387 | .arg(a: invokeInfo.autoforward ? QStringLiteral("true" ) : QStringLiteral("false" )); |
388 | { |
389 | QStringList l; |
390 | for (auto name : namelist) { |
391 | l.append(t: QString::number(name)); |
392 | } |
393 | line += QStringLiteral("%1, " ).arg(a: createContainer(elements: l)); |
394 | } |
395 | { |
396 | QStringList l; |
397 | for (const auto ¶meter : parameters) { |
398 | l += QStringLiteral("param(%1, %2, %3)" ) |
399 | .arg(args: QString::number(parameter.name), |
400 | args: QString::number(parameter.expr), |
401 | args: QString::number(parameter.location)); |
402 | } |
403 | line += QStringLiteral("%1);" ).arg(a: createContainer(elements: l)); |
404 | } |
405 | |
406 | factories.append(t: line); |
407 | return idx; |
408 | } |
409 | } // anonymous namespace |
410 | |
411 | void CppDumper::dump(TranslationUnit *unit) |
412 | { |
413 | Q_ASSERT(unit); |
414 | Q_ASSERT(unit->mainDocument); |
415 | |
416 | m_translationUnit = unit; |
417 | |
418 | QString namespacePrefix; |
419 | if (!m_translationUnit->namespaceName.isEmpty()) { |
420 | namespacePrefix = QStringLiteral("::%1" ).arg(a: m_translationUnit->namespaceName); |
421 | } |
422 | |
423 | QStringList classNames; |
424 | QVector<GeneratedTableData> tables; |
425 | QVector<GeneratedTableData::MetaDataInfo> metaDataInfos; |
426 | QVector<GeneratedTableData::DataModelInfo> dataModelInfos; |
427 | QVector<QStringList> factories; |
428 | auto docs = m_translationUnit->allDocuments; |
429 | tables.resize(asize: docs.size()); |
430 | metaDataInfos.resize(asize: tables.size()); |
431 | dataModelInfos.resize(asize: tables.size()); |
432 | factories.resize(asize: tables.size()); |
433 | auto classnameForDocument = m_translationUnit->classnameForDocument; |
434 | |
435 | for (int i = 0, ei = docs.size(); i != ei; ++i) { |
436 | auto doc = docs.at(i); |
437 | auto metaDataInfo = &metaDataInfos[i]; |
438 | GeneratedTableData::build(doc, table: &tables[i], metaDataInfo, dataModelInfo: &dataModelInfos[i], |
439 | func: [this, &factories, i, &classnameForDocument, &namespacePrefix]( |
440 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
441 | const QVector<QScxmlExecutableContent::StringId> &names, |
442 | const QVector<QScxmlExecutableContent::ParameterInfo> ¶meters, |
443 | const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int { |
444 | QString className; |
445 | if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) { |
446 | className = mangleIdentifier(str: classnameForDocument.value(akey: content.data())); |
447 | } |
448 | return createFactoryId(factories&: factories[i], className, namespacePrefix, |
449 | invokeInfo, namelist: names, parameters); |
450 | }); |
451 | classNames.append(t: mangleIdentifier(str: classnameForDocument.value(akey: doc))); |
452 | } |
453 | |
454 | const QString = QFileInfo(m_translationUnit->outHFileName).fileName(); |
455 | const QString = headerName.toUpper() |
456 | .replace(before: QLatin1Char('.'), after: QLatin1Char('_')) |
457 | .replace(before: QLatin1Char('-'), after: QLatin1Char('_')); |
458 | const QStringList forwardDecls = classNames.mid(pos: 1); |
459 | writeHeaderStart(headerGuard, forwardDecls); |
460 | writeImplStart(); |
461 | |
462 | for (int i = 0, ei = tables.size(); i != ei; ++i) { |
463 | const GeneratedTableData &table = tables.at(i); |
464 | DocumentModel::ScxmlDocument *doc = docs.at(i); |
465 | writeClass(className: classNames.at(i), info: metaDataInfos.at(i)); |
466 | writeImplBody(table, className: classNames.at(i), doc, factory: factories.at(i), info: metaDataInfos.at(i)); |
467 | |
468 | if (doc->root->dataModel == DocumentModel::Scxml::CppDataModel) { |
469 | Replacements r; |
470 | r[QStringLiteral("datamodel" )] = doc->root->cppDataModelClassName; |
471 | generateCppDataModelEvaluators(info: dataModelInfos.at(i), replacements&: r); |
472 | genTemplate(out&: cpp, QStringLiteral(":/cppdatamodel.t" ), replacements: r); |
473 | } |
474 | } |
475 | |
476 | writeHeaderEnd(headerGuard, metatypeDecls: classNames); |
477 | writeImplEnd(); |
478 | } |
479 | |
480 | void CppDumper::(const QString &, const QStringList &forwardDecls) |
481 | { |
482 | h << doNotEditComment.arg(args&: m_translationUnit->scxmlFileName, |
483 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
484 | args: QString::fromLatin1(QT_VERSION_STR)) |
485 | << Qt::endl; |
486 | |
487 | h << QStringLiteral("#ifndef " ) << headerGuard << Qt::endl |
488 | << QStringLiteral("#define " ) << headerGuard << Qt::endl |
489 | << Qt::endl; |
490 | h << l(str: headerStart); |
491 | if (!m_translationUnit->namespaceName.isEmpty()) |
492 | h << l(str: "namespace " ) << m_translationUnit->namespaceName << l(str: " {" ) << Qt::endl << Qt::endl; |
493 | |
494 | if (!forwardDecls.isEmpty()) { |
495 | for (const QString &forwardDecl : forwardDecls) |
496 | h << QStringLiteral("class %1;" ).arg(a: forwardDecl) << Qt::endl; |
497 | h << Qt::endl; |
498 | } |
499 | } |
500 | |
501 | void CppDumper::writeClass(const QString &className, const GeneratedTableData::MetaDataInfo &info) |
502 | { |
503 | Replacements r; |
504 | r[QStringLiteral("classname" )] = className; |
505 | r[QStringLiteral("properties" )] = generatePropertyDecls(info); |
506 | if (m_translationUnit->stateMethods) { |
507 | r[QStringLiteral("accessors" )] = generateAccessorDecls(info); |
508 | r[QStringLiteral("signals" )] = generateSignalDecls(info); |
509 | } else { |
510 | r[QStringLiteral("accessors" )] = QString(); |
511 | r[QStringLiteral("signals" )] = QString(); |
512 | } |
513 | genTemplate(out&: h, QStringLiteral(":/decl.t" ), replacements: r); |
514 | } |
515 | |
516 | void CppDumper::(const QString &, const QStringList &metatypeDecls) |
517 | { |
518 | QString ns; |
519 | if (!m_translationUnit->namespaceName.isEmpty()) { |
520 | h << QStringLiteral("} // %1 namespace " ).arg(a: m_translationUnit->namespaceName) << Qt::endl |
521 | << Qt::endl; |
522 | ns = QStringLiteral("::%1" ).arg(a: m_translationUnit->namespaceName); |
523 | } |
524 | |
525 | for (const QString &name : metatypeDecls) { |
526 | h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*)" ).arg(args&: ns, args: name) << Qt::endl; |
527 | } |
528 | h << Qt::endl; |
529 | |
530 | h << QStringLiteral("#endif // " ) << headerGuard << Qt::endl; |
531 | } |
532 | |
533 | void CppDumper::writeImplStart() |
534 | { |
535 | cpp << doNotEditComment.arg(args&: m_translationUnit->scxmlFileName, |
536 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
537 | args: l(QT_VERSION_STR)) |
538 | << Qt::endl; |
539 | |
540 | QStringList includes; |
541 | for (DocumentModel::ScxmlDocument *doc : qAsConst(t&: m_translationUnit->allDocuments)) { |
542 | switch (doc->root->dataModel) { |
543 | case DocumentModel::Scxml::NullDataModel: |
544 | includes += l(str: "QScxmlNullDataModel" ); |
545 | break; |
546 | case DocumentModel::Scxml::JSDataModel: |
547 | includes += l(str: "QScxmlEcmaScriptDataModel" ); |
548 | break; |
549 | case DocumentModel::Scxml::CppDataModel: |
550 | includes += doc->root->cppDataModelHeaderName; |
551 | break; |
552 | } |
553 | |
554 | } |
555 | includes.sort(); |
556 | includes.removeDuplicates(); |
557 | |
558 | QString = QFileInfo(m_translationUnit->outHFileName).fileName(); |
559 | cpp << l(str: "#include \"" ) << headerName << l(str: "\"" ) << Qt::endl; |
560 | cpp << Qt::endl |
561 | << QStringLiteral("#include <qscxmlinvokableservice.h>" ) << Qt::endl |
562 | << QStringLiteral("#include <qscxmltabledata.h>" ) << Qt::endl; |
563 | for (const QString &inc : qAsConst(t&: includes)) { |
564 | cpp << l(str: "#include <" ) << inc << l(str: ">" ) << Qt::endl; |
565 | } |
566 | cpp << Qt::endl |
567 | << revisionCheck.arg(args&: m_translationUnit->scxmlFileName, |
568 | args: QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
569 | args: QString::fromLatin1(QT_VERSION_STR)) |
570 | << Qt::endl; |
571 | if (!m_translationUnit->namespaceName.isEmpty()) |
572 | cpp << l(str: "namespace " ) << m_translationUnit->namespaceName << l(str: " {" ) << Qt::endl << Qt::endl; |
573 | } |
574 | |
575 | void CppDumper::writeImplBody(const GeneratedTableData &table, |
576 | const QString &className, |
577 | DocumentModel::ScxmlDocument *doc, |
578 | const QStringList &factory, |
579 | const GeneratedTableData::MetaDataInfo &info) |
580 | { |
581 | QString dataModelField, dataModelInitialization; |
582 | switch (doc->root->dataModel) { |
583 | case DocumentModel::Scxml::NullDataModel: |
584 | dataModelField = l(str: "QScxmlNullDataModel dataModel;" ); |
585 | dataModelInitialization = l(str: "stateMachine.setDataModel(&dataModel);" ); |
586 | break; |
587 | case DocumentModel::Scxml::JSDataModel: |
588 | dataModelField = l(str: "QScxmlEcmaScriptDataModel dataModel;" ); |
589 | dataModelInitialization = l(str: "stateMachine.setDataModel(&dataModel);" ); |
590 | break; |
591 | case DocumentModel::Scxml::CppDataModel: |
592 | dataModelField = QStringLiteral("// Data model %1 is set from outside." ).arg( |
593 | a: doc->root->cppDataModelClassName); |
594 | dataModelInitialization = dataModelField; |
595 | break; |
596 | } |
597 | |
598 | QString name; |
599 | if (table.theName == -1) { |
600 | name = QStringLiteral("QString()" ); |
601 | } else { |
602 | name = QStringLiteral("string(%1)" ).arg(a: table.theName); |
603 | } |
604 | |
605 | QString serviceFactories; |
606 | if (factory.isEmpty()) { |
607 | serviceFactories = QStringLiteral(" Q_UNUSED(id);\n Q_UNREACHABLE();" ); |
608 | } else { |
609 | serviceFactories = QStringLiteral(" switch (id) {\n " ) |
610 | + factory.join(QStringLiteral("\n " )) |
611 | + QStringLiteral("\n default: Q_UNREACHABLE();\n }" ); |
612 | } |
613 | |
614 | |
615 | Replacements r; |
616 | r[QStringLiteral("classname" )] = className; |
617 | r[QStringLiteral("name" )] = name; |
618 | r[QStringLiteral("initialSetup" )] = QString::number(table.initialSetup()); |
619 | generateTables(td: table, replacements&: r); |
620 | r[QStringLiteral("dataModelField" )] = dataModelField; |
621 | r[QStringLiteral("dataModelInitialization" )] = dataModelInitialization; |
622 | r[QStringLiteral("theStateMachineTable" )] = |
623 | GeneratedTableData::toString(stateMachineTable: table.stateMachineTable()); |
624 | r[QStringLiteral("metaObject" )] = generateMetaObject(className, info); |
625 | r[QStringLiteral("serviceFactories" )] = serviceFactories; |
626 | genTemplate(out&: cpp, QStringLiteral(":/data.t" ), replacements: r); |
627 | } |
628 | |
629 | void CppDumper::writeImplEnd() |
630 | { |
631 | if (!m_translationUnit->namespaceName.isEmpty()) { |
632 | cpp << Qt::endl |
633 | << QStringLiteral("} // %1 namespace" ).arg(a: m_translationUnit->namespaceName) << Qt::endl; |
634 | } |
635 | } |
636 | |
637 | /*! |
638 | * \internal |
639 | * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers |
640 | * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode |
641 | * representation of the character. As identifiers with leading underscores followed by either |
642 | * another underscore or a capital letter are reserved in C++, we also escape those, by escaping |
643 | * the first underscore, using the above method. |
644 | * |
645 | * We keep track of all identifiers we have used so far and if we find two different names that |
646 | * map to the same mangled identifier by the above method, we append underscores to the new one |
647 | * until the result is unique. |
648 | * |
649 | * \note |
650 | * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers, |
651 | * many compilers forgot to read the spec and do not implement this. Some also do not |
652 | * implement C99 identifiers, because that is \e {at the implementation's discretion}. So, |
653 | * we are stuck with plain old boring identifiers. |
654 | */ |
655 | QString CppDumper::mangleIdentifier(const QString &str) |
656 | { |
657 | auto isNonDigit = [](QChar c) -> bool { |
658 | return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) || |
659 | (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || |
660 | c == QLatin1Char('_'); |
661 | }; |
662 | |
663 | Q_ASSERT(!str.isEmpty()); |
664 | |
665 | QString mangled; |
666 | mangled.reserve(asize: str.size()); |
667 | |
668 | int i = 0; |
669 | if (str.startsWith(c: QLatin1Char('_')) && str.size() > 1) { |
670 | QChar ch = str.at(i: 1); |
671 | if (ch == QLatin1Char('_') |
672 | || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) { |
673 | mangled += QLatin1String("_0x5f_" ); |
674 | ++i; |
675 | } |
676 | } |
677 | |
678 | for (int ei = str.length(); i != ei; ++i) { |
679 | auto c = str.at(i); |
680 | if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { |
681 | mangled += c; |
682 | } else { |
683 | mangled += QLatin1String("_0x" ) + QString::number(c.unicode(), base: 16) + QLatin1Char('_'); |
684 | } |
685 | } |
686 | |
687 | while (true) { |
688 | auto it = m_mangledToOriginal.constFind(akey: mangled); |
689 | if (it == m_mangledToOriginal.constEnd()) { |
690 | m_mangledToOriginal.insert(akey: mangled, avalue: str); |
691 | break; |
692 | } else if (it.value() == str) { |
693 | break; |
694 | } |
695 | mangled += QStringLiteral("_" ); // append underscores until we get a unique name |
696 | } |
697 | |
698 | return mangled; |
699 | } |
700 | |
701 | QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo &info) |
702 | { |
703 | QString decls; |
704 | |
705 | for (const QString &stateName : info.stateNames) { |
706 | if (stateName.isEmpty()) |
707 | continue; |
708 | |
709 | if (m_translationUnit->stateMethods) { |
710 | decls += QString::fromLatin1(str: " Q_PROPERTY(bool %1 READ %2 NOTIFY %3)\n" ) |
711 | .arg(args: stateName, args: mangleIdentifier(str: stateName), |
712 | args: mangleIdentifier(str: stateName + QStringLiteral("Changed" ))); |
713 | } else { |
714 | decls += QString::fromLatin1(str: " Q_PROPERTY(bool %1)\n" ).arg(a: stateName); |
715 | } |
716 | } |
717 | |
718 | return decls; |
719 | } |
720 | |
721 | QString CppDumper::generateAccessorDecls(const GeneratedTableData::MetaDataInfo &info) |
722 | { |
723 | QString decls; |
724 | |
725 | for (const QString &stateName : info.stateNames) { |
726 | if (!stateName.isEmpty()) |
727 | decls += QString::fromLatin1(str: " bool %1() const;\n" ).arg(a: mangleIdentifier(str: stateName)); |
728 | } |
729 | |
730 | return decls; |
731 | } |
732 | |
733 | QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info) |
734 | { |
735 | QString decls; |
736 | |
737 | for (const QString &stateName : info.stateNames) { |
738 | if (!stateName.isEmpty()) { |
739 | decls += QString::fromLatin1(str: " void %1(bool);\n" ) |
740 | .arg(a: mangleIdentifier(str: stateName + QStringLiteral("Changed" ))); |
741 | } |
742 | } |
743 | |
744 | return decls; |
745 | } |
746 | |
747 | QString CppDumper::generateMetaObject(const QString &className, |
748 | const GeneratedTableData::MetaDataInfo &info) |
749 | { |
750 | ClassDef classDef; |
751 | classDef.classname = className.toUtf8(); |
752 | classDef.qualified = classDef.classname; |
753 | classDef.superclassList << qMakePair(x: QByteArray("QScxmlStateMachine" ), y: FunctionDef::Public); |
754 | classDef.hasQObject = true; |
755 | FunctionDef constructor; |
756 | constructor.name = className.toUtf8(); |
757 | constructor.access = FunctionDef::Public; |
758 | constructor.isInvokable = true; |
759 | constructor.isConstructor = true; |
760 | |
761 | ArgumentDef arg; |
762 | arg.type.name = "QObject *" ; |
763 | arg.type.rawName = arg.type.name; |
764 | arg.normalizedType = arg.type.name; |
765 | arg.name = "parent" ; |
766 | arg.typeNameForCast = arg.type.name + "*" ; |
767 | constructor.arguments.append(t: arg); |
768 | classDef.constructorList.append(t: constructor); |
769 | |
770 | // stateNames: |
771 | int stateIdx = 0; |
772 | for (const QString &stateName : info.stateNames) { |
773 | if (stateName.isEmpty()) |
774 | continue; |
775 | |
776 | QByteArray utf8StateName = stateName.toUtf8(); |
777 | |
778 | FunctionDef signal; |
779 | signal.type.name = "void" ; |
780 | signal.type.rawName = signal.type.name; |
781 | signal.normalizedType = signal.type.name; |
782 | signal.name = utf8StateName + "Changed" ; |
783 | if (m_translationUnit->stateMethods) |
784 | signal.mangledName = mangleIdentifier(str: stateName + QStringLiteral("Changed" )).toUtf8(); |
785 | signal.access = FunctionDef::Public; |
786 | signal.isSignal = true; |
787 | signal.implementation = "QMetaObject::activate(%s, &staticMetaObject, %d, _a);" ; |
788 | |
789 | ArgumentDef arg; |
790 | arg.type.name = "bool" ; |
791 | arg.type.rawName = arg.type.name; |
792 | arg.normalizedType = arg.type.name; |
793 | arg.name = "active" ; |
794 | arg.typeNameForCast = arg.type.name + "*" ; |
795 | signal.arguments << arg; |
796 | classDef.signalList << signal; |
797 | |
798 | ++classDef.notifyableProperties; |
799 | PropertyDef prop; |
800 | prop.name = stateName.toUtf8(); |
801 | if (m_translationUnit->stateMethods) |
802 | prop.mangledName = mangleIdentifier(str: stateName).toUtf8(); |
803 | prop.type = "bool" ; |
804 | prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")" ; |
805 | prop.notify = utf8StateName + "Changed" ; |
806 | prop.notifyId = classDef.signalList.size() - 1; |
807 | prop.gspec = PropertyDef::ValueSpec; |
808 | prop.scriptable = "true" ; |
809 | classDef.propertyList << prop; |
810 | } |
811 | |
812 | // sub-statemachines: |
813 | QHash<QByteArray, QByteArray> knownQObjectClasses; |
814 | knownQObjectClasses.insert(akey: QByteArray("QScxmlStateMachine" ), avalue: QByteArray()); |
815 | |
816 | QBuffer buf; |
817 | buf.open(openMode: QIODevice::WriteOnly); |
818 | Generator generator(&classDef, QList<QByteArray>(), knownQObjectClasses, |
819 | QHash<QByteArray, QByteArray>(), buf); |
820 | generator.generateCode(); |
821 | if (m_translationUnit->stateMethods) { |
822 | generator.generateAccessorDefs(); |
823 | generator.generateSignalDefs(); |
824 | } |
825 | buf.close(); |
826 | return QString::fromUtf8(str: buf.buffer()); |
827 | } |
828 | |
829 | QT_END_NAMESPACE |
830 | |