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 for (const QString &inc : std::as_const(t&: includes)) {
538 cpp << l(str: "#include <") << inc << l(str: ">") << Qt::endl;
539 }
540 cpp << Qt::endl
541 << revisionCheck.arg(args&: m_translationUnit->scxmlFileName,
542 args: QString::number(Q_QSCXMLC_OUTPUT_REVISION),
543 args: QString::fromLatin1(QT_VERSION_STR))
544 << Qt::endl;
545 if (!m_translationUnit->namespaceName.isEmpty())
546 cpp << l(str: "namespace ") << m_translationUnit->namespaceName << l(str: " {") << Qt::endl << Qt::endl;
547}
548
549void CppDumper::writeImplBody(const GeneratedTableData &table,
550 const QString &className,
551 DocumentModel::ScxmlDocument *doc,
552 const QStringList &factory,
553 const GeneratedTableData::MetaDataInfo &info)
554{
555 QString dataModelField, dataModelInitialization;
556 switch (doc->root->dataModel) {
557 case DocumentModel::Scxml::NullDataModel:
558 dataModelField = l(str: "QScxmlNullDataModel dataModel;");
559 dataModelInitialization = l(str: "stateMachine.setDataModel(&dataModel);");
560 break;
561 case DocumentModel::Scxml::JSDataModel:
562 dataModelField = l(str: "std::unique_ptr<QScxmlDataModel> dataModel;");
563 dataModelInitialization = l(
564 str: " dataModel.reset(QScxmlDataModel::createScxmlDataModel(QStringLiteral(\"ecmascriptdatamodel\")));\n"
565 " stateMachine.setDataModel(dataModel.get());\n"
566 );
567 break;
568 case DocumentModel::Scxml::CppDataModel:
569 dataModelField = QStringLiteral("// Data model %1 is set from outside.").arg(
570 a: doc->root->cppDataModelClassName);
571 dataModelInitialization = dataModelField;
572 break;
573 }
574
575 QString name;
576 if (table.theName == -1) {
577 name = QStringLiteral("QString()");
578 } else {
579 name = QStringLiteral("string(%1)").arg(a: table.theName);
580 }
581
582 QString serviceFactories;
583 if (factory.isEmpty()) {
584 serviceFactories = QStringLiteral(" Q_UNUSED(id);\n Q_UNREACHABLE();");
585 } else {
586 serviceFactories = QStringLiteral(" switch (id) {\n ")
587 + factory.join(QStringLiteral("\n "))
588 + QStringLiteral("\n default: Q_UNREACHABLE();\n }");
589 }
590
591
592 Replacements r;
593 r[QStringLiteral("classname")] = className;
594 r[QStringLiteral("name")] = name;
595 r[QStringLiteral("initialSetup")] = QString::number(table.initialSetup());
596 generateTables(td: table, replacements&: r);
597 r[QStringLiteral("dataModelField")] = dataModelField;
598 r[QStringLiteral("dataModelInitialization")] = dataModelInitialization;
599 r[QStringLiteral("theStateMachineTable")] =
600 GeneratedTableData::toString(stateMachineTable: table.stateMachineTable());
601 r[QStringLiteral("metaObject")] = generateMetaObject(className, info);
602 r[QStringLiteral("serviceFactories")] = serviceFactories;
603 genTemplate(out&: cpp, QStringLiteral(":/data.t"), replacements: r);
604}
605
606void CppDumper::writeImplEnd()
607{
608 if (!m_translationUnit->namespaceName.isEmpty()) {
609 cpp << Qt::endl
610 << QStringLiteral("} // %1 namespace").arg(a: m_translationUnit->namespaceName) << Qt::endl;
611 }
612}
613
614/*!
615 * \internal
616 * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers
617 * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode
618 * representation of the character. As identifiers with leading underscores followed by either
619 * another underscore or a capital letter are reserved in C++, we also escape those, by escaping
620 * the first underscore, using the above method.
621 *
622 * We keep track of all identifiers we have used so far and if we find two different names that
623 * map to the same mangled identifier by the above method, we append underscores to the new one
624 * until the result is unique.
625 *
626 * \note
627 * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers,
628 * many compilers forgot to read the spec and do not implement this. Some also do not
629 * implement C99 identifiers, because that is \e {at the implementation's discretion}. So,
630 * we are stuck with plain old boring identifiers.
631 */
632QString CppDumper::mangleIdentifier(const QString &str)
633{
634 auto isNonDigit = [](QChar c) -> bool {
635 return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) ||
636 (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) ||
637 c == QLatin1Char('_');
638 };
639
640 Q_ASSERT(!str.isEmpty());
641
642 QString mangled;
643 mangled.reserve(asize: str.size());
644
645 int i = 0;
646 if (str.startsWith(c: QLatin1Char('_')) && str.size() > 1) {
647 QChar ch = str.at(i: 1);
648 if (ch == QLatin1Char('_')
649 || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) {
650 mangled += QLatin1String("_0x5f_");
651 ++i;
652 }
653 }
654
655 for (int ei = str.size(); i != ei; ++i) {
656 auto c = str.at(i);
657 if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) {
658 mangled += c;
659 } else {
660 mangled += QLatin1String("_0x") + QString::number(c.unicode(), base: 16) + QLatin1Char('_');
661 }
662 }
663
664 while (true) {
665 auto it = m_mangledToOriginal.constFind(key: mangled);
666 if (it == m_mangledToOriginal.constEnd()) {
667 m_mangledToOriginal.insert(key: mangled, value: str);
668 break;
669 } else if (it.value() == str) {
670 break;
671 }
672 mangled += QStringLiteral("_"); // append underscores until we get a unique name
673 }
674
675 return mangled;
676}
677
678QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo &info)
679{
680 QString decls;
681
682 for (const QString &stateName : info.stateNames) {
683 if (stateName.isEmpty())
684 continue;
685
686 if (m_translationUnit->stateMethods) {
687 decls += QString::fromLatin1(ba: " Q_PROPERTY(bool %1 READ %2 NOTIFY %3)\n")
688 .arg(args: stateName, args: mangleIdentifier(str: stateName),
689 args: mangleIdentifier(str: stateName + QStringLiteral("Changed")));
690 } else {
691 decls += QString::fromLatin1(ba: " Q_PROPERTY(bool %1)\n").arg(a: stateName);
692 }
693 }
694
695 return decls;
696}
697
698QString CppDumper::generateAccessorDecls(const GeneratedTableData::MetaDataInfo &info)
699{
700 QString decls;
701
702 for (const QString &stateName : info.stateNames) {
703 if (!stateName.isEmpty())
704 decls += QString::fromLatin1(ba: " bool %1() const;\n").arg(a: mangleIdentifier(str: stateName));
705 }
706
707 return decls;
708}
709
710QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info)
711{
712 QString decls;
713
714 for (const QString &stateName : info.stateNames) {
715 if (!stateName.isEmpty()) {
716 decls += QString::fromLatin1(ba: " void %1(bool);\n")
717 .arg(a: mangleIdentifier(str: stateName + QStringLiteral("Changed")));
718 }
719 }
720
721 return decls;
722}
723
724QString CppDumper::generateMetaObject(const QString &className,
725 const GeneratedTableData::MetaDataInfo &info)
726{
727 ClassDef classDef;
728 classDef.classname = className.toUtf8();
729 classDef.qualified = classDef.classname;
730 classDef.superclassList << qMakePair(value1: QByteArray("QScxmlStateMachine"), value2: FunctionDef::Public);
731 classDef.hasQObject = true;
732 FunctionDef constructor;
733 constructor.name = className.toUtf8();
734 constructor.access = FunctionDef::Public;
735 constructor.isInvokable = true;
736 constructor.isConstructor = true;
737
738 ArgumentDef arg;
739 arg.type.name = "QObject *";
740 arg.type.rawName = arg.type.name;
741 arg.normalizedType = arg.type.name;
742 arg.name = "parent";
743 arg.typeNameForCast = arg.type.name + "*";
744 constructor.arguments.append(t: arg);
745 classDef.constructorList.append(t: constructor);
746
747 // stateNames:
748 int stateIdx = 0;
749 for (const QString &stateName : info.stateNames) {
750 if (stateName.isEmpty())
751 continue;
752
753 QByteArray utf8StateName = stateName.toUtf8();
754
755 FunctionDef signal;
756 signal.type.name = "void";
757 signal.type.rawName = signal.type.name;
758 signal.normalizedType = signal.type.name;
759 signal.name = utf8StateName + "Changed";
760 if (m_translationUnit->stateMethods)
761 signal.mangledName = mangleIdentifier(str: stateName + QStringLiteral("Changed")).toUtf8();
762 signal.access = FunctionDef::Public;
763 signal.isSignal = true;
764 signal.implementation = "QMetaObject::activate(%s, &staticMetaObject, %d, _a);";
765
766 ArgumentDef arg;
767 arg.type.name = "bool";
768 arg.type.rawName = arg.type.name;
769 arg.normalizedType = arg.type.name;
770 arg.name = "active";
771 arg.typeNameForCast = arg.type.name + "*";
772 signal.arguments << arg;
773 classDef.signalList << signal;
774
775 PropertyDef prop;
776 prop.name = stateName.toUtf8();
777 if (m_translationUnit->stateMethods)
778 prop.mangledName = mangleIdentifier(str: stateName).toUtf8();
779 prop.type = "bool";
780 prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")";
781 prop.notify = utf8StateName + "Changed";
782 prop.notifyId = classDef.signalList.size() - 1;
783 prop.gspec = PropertyDef::ValueSpec;
784 prop.scriptable = "true";
785 classDef.propertyList << prop;
786 }
787
788 // sub-statemachines:
789 QHash<QByteArray, QByteArray> knownQObjectClasses;
790 knownQObjectClasses.insert(key: QByteArray("QScxmlStateMachine"), value: QByteArray());
791
792 QBuffer buf;
793 buf.open(openMode: QIODevice::WriteOnly);
794 Generator generator(&classDef, QList<QByteArray>(), knownQObjectClasses,
795 QHash<QByteArray, QByteArray>(), buf);
796 generator.generateCode();
797 if (m_translationUnit->stateMethods) {
798 generator.generateAccessorDefs();
799 generator.generateSignalDefs();
800 }
801 buf.close();
802 return QString::fromUtf8(ba: buf.buffer());
803}
804
805QT_END_NAMESPACE
806

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