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
42QT_BEGIN_NAMESPACE
43
44using namespace QScxmlInternal;
45
46namespace {
47
48static const QString doNotEditComment = 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
56static 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
66QString 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
104typedef QHash<QString, QString> Replacements;
105static 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
135static const char *headerStart =
136 "#include <QScxmlStateMachine>\n"
137 "#include <QString>\n"
138 "#include <QVariant>\n"
139 "\n";
140
141using namespace DocumentModel;
142
143QString 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
154static 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
178void 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
298void 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
365int 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> &parameters)
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 &parameter : 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
411void 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> &parameters,
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 headerName = QFileInfo(m_translationUnit->outHFileName).fileName();
455 const QString headerGuard = 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
480void CppDumper::writeHeaderStart(const QString &headerGuard, 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
501void 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
516void CppDumper::writeHeaderEnd(const QString &headerGuard, 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
533void 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 headerName = 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
575void 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
629void 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 */
655QString 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
701QString 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
721QString 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
733QString 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
747QString 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
829QT_END_NAMESPACE
830

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