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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtscxml/tools/qscxmlc/scxmlcppdumper.cpp