Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include "scxmlcppdumper.h"
++ #include "generator.h"
++ #include "../../src/3rdparty/sha1/sha1.cpp"
++
++ #include <QtScxml/private/qscxmlexecutablecontent_p.h>
++
++ #include <QtCore/qbuffer.h>
++ #include <QtCore/qfile.h>
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qjsonobject.h>
++ #include <QtCore/qresource.h>
++
++ #include <algorithm>
++ #include <functional>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace QScxmlInternal;
++
++ namespace {
++
++ static const QString doNotEditComment = QString::fromLatin1(
++ "//\n"
++ "// Statemachine code from reading SCXML file '%1'\n"
++ "// Created by: The Qt SCXML Compiler version %2 (Qt %3)\n"
++ "// WARNING! All changes made in this file will be lost!\n"
++ "//\n"
++ );
++
++ static const QString revisionCheck = QString::fromLatin1(
++ "#if !defined(Q_QSCXMLC_OUTPUT_REVISION)\n"
++ "#error \"The header file '%1' doesn't include <qscxmltabledata.h>.\"\n"
++ "#elif Q_QSCXMLC_OUTPUT_REVISION != %2\n"
++ "#error \"This file was generated using the qscxmlc from %3. It\"\n"
++ "#error \"cannot be used with the include files from this version of Qt.\"\n"
++ "#error \"(The qscxmlc has changed too much.)\"\n"
++ "#endif\n"
++ );
++
++ QString cEscape(const QString &str)
++ {
++ QString res;
++ int lastI = 0;
++ for (int i = 0; i < str.size(); ++i) {
++ QChar c = str.at(i);
++ if (c < QLatin1Char(' ') || c == QLatin1Char('\\') || c == QLatin1Char('\"')) {
++ res.append(str.mid(lastI, i - lastI));
++ lastI = i + 1;
++ if (c == QLatin1Char('\\')) {
++ res.append(QLatin1String("\\\\"));
++ } else if (c == QLatin1Char('\"')) {
++ res.append(QLatin1String("\\\""));
++ } else if (c == QLatin1Char('\n')) {
++ res.append(QLatin1String("\\n"));
++ } else if (c == QLatin1Char('\r')) {
++ res.append(QLatin1String("\\r"));
++ } else {
++ char buf[6];
++ ushort cc = c.unicode();
++ buf[0] = '\\';
++ buf[1] = 'u';
++ for (int i = 0; i < 4; ++i) {
++ ushort ccc = cc & 0xF;
++ buf[5 - i] = ccc <= 9 ? '0' + ccc : 'a' + ccc - 10;
++ cc >>= 4;
++ }
++ res.append(QLatin1String(&buf[0], 6));
++ }
++ }
++ }
++ if (lastI != 0) {
++ res.append(str.mid(lastI));
++ return res;
++ }
++ return str;
++ }
++
++ typedef QHash<QString, QString> Replacements;
++ static void genTemplate(QTextStream &out, const QString &filename, const Replacements &replacements)
++ {
++ QResource file(filename);
++ if (!file.isValid()) {
++ qFatal("Unable to open template '%s'", qPrintable(filename));
++ }
++ Q_ASSERT(file.compressionAlgorithm() == QResource::NoCompression);
++ const QString data = QString::fromLatin1(
++ QByteArray::fromRawData(reinterpret_cast<const char *>(file.data()), int(file.size()))
++ );
++ const QStringView t { data };
++
++ int start = 0;
++ for (int openIdx = t.indexOf(QStringLiteral("${"), start); openIdx >= 0; openIdx =
++ t.indexOf(QStringLiteral("${"), start)) {
++ out << t.mid(start, openIdx - start);
++ openIdx += 2;
++ const int closeIdx = t.indexOf(QLatin1Char('}'), openIdx);
++ Q_ASSERT(closeIdx >= openIdx);
++ QString key = t.mid(openIdx, closeIdx - openIdx).toString();
++ if (!replacements.contains(key)) {
++ qFatal("Replacing '%s' failed: no replacement found", qPrintable(key));
++ }
++ out << replacements.value(key);
++ start = closeIdx + 1;
++ }
++ out << t.mid(start);
++ }
++
++ static const char *headerStart =
++ "#include <QScxmlStateMachine>\n"
++ "#include <QString>\n"
++ "#include <QVariant>\n"
++ "\n";
++
++ using namespace DocumentModel;
++
++ QString createContainer(const QStringList &elements)
++ {
++ QString result;
++ if (elements.isEmpty()) {
++ result += QStringLiteral("{}");
++ } else {
++ result += QStringLiteral("{ ") + elements.join(QStringLiteral(", ")) + QStringLiteral(" }");
++ }
++ return result;
++ }
++
++ static void generateList(QString &out, std::function<QString(int)> next)
++ {
++ const int maxLineLength = 80;
++ QString line;
++ for (int i = 0; ; ++i) {
++ const QString nr = next(i);
++ if (nr.isNull())
++ break;
++
++ if (i != 0)
++ line += QLatin1Char(',');
++
++ if (line.size() + nr.size() + 1 > maxLineLength) {
++ out += line + QLatin1Char('\n');
++ line.clear();
++ } else if (i != 0) {
++ line += QLatin1Char(' ');
++ }
++ line += nr;
++ }
++ if (!line.isEmpty())
++ out += line;
++ }
++
++ void generateTables(const GeneratedTableData &td, Replacements &replacements)
++ {
++ { // instructions
++ auto instr = td.theInstructions;
++ QString out;
++ generateList(out, [&instr](int idx) -> QString {
++ if (instr.isEmpty() && idx == 0) // prevent generation of empty array
++ return QStringLiteral("-1");
++ if (idx < instr.size())
++ return QString::number(instr.at(idx));
++ else
++ return QString();
++ });
++ replacements[QStringLiteral("theInstructions")] = out;
++ }
++
++ { // dataIds
++ auto dataIds = td.theDataNameIds;
++ QString out;
++ generateList(out, [&dataIds](int idx) -> QString {
++ if (dataIds.size() == 0 && idx == 0) // prevent generation of empty array
++ return QStringLiteral("-1");
++ if (idx < dataIds.size())
++ return QString::number(dataIds[idx]);
++ else
++ return QString();
++ });
++ replacements[QStringLiteral("dataNameCount")] = QString::number(dataIds.size());
++ replacements[QStringLiteral("dataIds")] = out;
++ }
++
++ { // evaluators
++ auto evaluators = td.theEvaluators;
++ QString out;
++ generateList(out, [&evaluators](int idx) -> QString {
++ if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array
++ return QStringLiteral("{ -1, -1 }");
++ if (idx >= evaluators.size())
++ return QString();
++
++ const auto eval = evaluators.at(idx);
++ return QStringLiteral("{ %1, %2 }").arg(eval.expr).arg(eval.context);
++ });
++ replacements[QStringLiteral("evaluatorCount")] = QString::number(evaluators.size());
++ replacements[QStringLiteral("evaluators")] = out;
++ }
++
++ { // assignments
++ auto assignments = td.theAssignments;
++ QString out;
++ generateList(out, [&assignments](int idx) -> QString {
++ if (assignments.isEmpty() && idx == 0) // prevent generation of empty array
++ return QStringLiteral("{ -1, -1, -1 }");
++ if (idx >= assignments.size())
++ return QString();
++
++ auto assignment = assignments.at(idx);
++ return QStringLiteral("{ %1, %2, %3 }")
++ .arg(assignment.dest).arg(assignment.expr).arg(assignment.context);
++ });
++ replacements[QStringLiteral("assignmentCount")] = QString::number(assignments.size());
++ replacements[QStringLiteral("assignments")] = out;
++ }
++
++ { // foreaches
++ auto foreaches = td.theForeaches;
++ QString out;
++ generateList(out, [&foreaches](int idx) -> QString {
++ if (foreaches.isEmpty() && idx == 0) // prevent generation of empty array
++ return QStringLiteral("{ -1, -1, -1, -1 }");
++ if (idx >= foreaches.size())
++ return QString();
++
++ auto foreachItem = foreaches.at(idx);
++ return QStringLiteral("{ %1, %2, %3, %4 }").arg(foreachItem.array).arg(foreachItem.item)
++ .arg(foreachItem.index).arg(foreachItem.context);
++ });
++ replacements[QStringLiteral("foreachCount")] = QString::number(foreaches.size());
++ replacements[QStringLiteral("foreaches")] = out;
++ }
++
++ { // strings
++ QString out;
++ auto strings = td.theStrings;
++ if (strings.isEmpty()) // prevent generation of empty array
++ strings.append(QStringLiteral(""));
++ int ucharCount = 0;
++ generateList(out, [&ucharCount, &strings](int idx) -> QString {
++ if (idx >= strings.size())
++ return QString();
++
++ const int length = strings.at(idx).size();
++ const QString str = QStringLiteral("%1, %2").arg(
++ QString::number(ucharCount), QString::number(length));
++ ucharCount += length + 1;
++ return str;
++ });
++ replacements[QStringLiteral("stringCount")] = QString::number(strings.size());
++ replacements[QStringLiteral("strLits")] = out;
++
++ out.clear();
++ for (int i = 0, ei = strings.size(); i < ei; ++i) {
++ const QString &string = strings.at(i);
++ QString result;
++ if (i != 0)
++ result += QLatin1Char('\n');
++ for (int charPos = 0, eCharPos = string.size(); charPos < eCharPos; ++charPos) {
++ result.append(QStringLiteral("0x%1,")
++ .arg(QString::number(string.at(charPos).unicode(), 16)));
++ }
++ result.append(QStringLiteral("0%1 // %2: %3")
++ .arg(QLatin1String(i < ei - 1 ? "," : ""), QString::number(i),
++ cEscape(string)));
++ out += result;
++ }
++ replacements[QStringLiteral("uniLits")] = out;
++ replacements[QStringLiteral("stringdataSize")] = QString::number(ucharCount + 1);
++ }
++ }
++
++ void generateCppDataModelEvaluators(const GeneratedTableData::DataModelInfo &info,
++ Replacements &replacements)
++ {
++ const QString switchStart = QStringLiteral(" switch (id) {\n");
++ const QString switchEnd = QStringLiteral(" default: break;\n }");
++ const QString unusedId = QStringLiteral(" Q_UNUSED(id);");
++ QString stringEvals;
++ if (!info.stringEvaluators.isEmpty()) {
++ stringEvals += switchStart;
++ for (auto it = info.stringEvaluators.constBegin(), eit = info.stringEvaluators.constEnd();
++ it != eit; ++it) {
++ stringEvals += QStringLiteral(" case %1:\n").arg(it.key());
++ stringEvals += QStringLiteral(" return [this]()->QString{ return %1; }();\n")
++ .arg(it.value());
++ }
++ stringEvals += switchEnd;
++ } else {
++ stringEvals += unusedId;
++ }
++ replacements[QStringLiteral("evaluateToStringCases")] = stringEvals;
++
++ QString boolEvals;
++ if (!info.boolEvaluators.isEmpty()) {
++ boolEvals += switchStart;
++ for (auto it = info.boolEvaluators.constBegin(), eit = info.boolEvaluators.constEnd();
++ it != eit; ++it) {
++ boolEvals += QStringLiteral(" case %1:\n").arg(it.key());
++ boolEvals += QStringLiteral(" return [this]()->bool{ return %1; }();\n")
++ .arg(it.value());
++ }
++ boolEvals += switchEnd;
++ } else {
++ boolEvals += unusedId;
++ }
++ replacements[QStringLiteral("evaluateToBoolCases")] = boolEvals;
++
++ QString variantEvals;
++ if (!info.variantEvaluators.isEmpty()) {
++ variantEvals += switchStart;
++ for (auto it = info.variantEvaluators.constBegin(), eit = info.variantEvaluators.constEnd();
++ it != eit; ++it) {
++ variantEvals += QStringLiteral(" case %1:\n").arg(it.key());
++ variantEvals += QStringLiteral(" return [this]()->QVariant{ return %1; }();\n")
++ .arg(it.value());
++ }
++ variantEvals += switchEnd;
++ } else {
++ variantEvals += unusedId;
++ }
++ replacements[QStringLiteral("evaluateToVariantCases")] = variantEvals;
++
++ QString voidEvals;
++ if (!info.voidEvaluators.isEmpty()) {
++ voidEvals = switchStart;
++ for (auto it = info.voidEvaluators.constBegin(), eit = info.voidEvaluators.constEnd();
++ it != eit; ++it) {
++ voidEvals += QStringLiteral(" case %1:\n").arg(it.key());
++ voidEvals += QStringLiteral(" [this]()->void{ %1 }();\n").arg(it.value());
++ voidEvals += QStringLiteral(" return;\n");
++ }
++ voidEvals += switchEnd;
++ } else {
++ voidEvals += unusedId;
++ }
++ replacements[QStringLiteral("evaluateToVoidCases")] = voidEvals;
++ }
++
++ int createFactoryId(QStringList &factories, const QString &className,
++ const QString &namespacePrefix,
++ const QScxmlExecutableContent::InvokeInfo &invokeInfo,
++ const QList<QScxmlExecutableContent::StringId> &namelist,
++ const QList<QScxmlExecutableContent::ParameterInfo> ¶meters)
++ {
++ const int idx = factories.size();
++
++ QString line = QStringLiteral("case %1: return new ").arg(QString::number(idx));
++ if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) {
++ line += QStringLiteral("QScxmlStaticScxmlServiceFactory(&%1::%2::staticMetaObject,")
++ .arg(namespacePrefix, className);
++ } else {
++ line += QStringLiteral("QScxmlDynamicScxmlServiceFactory(");
++ }
++ line += QStringLiteral("invoke(%1, %2, %3, %4, %5, %6, %7), ")
++ .arg(QString::number(invokeInfo.id),
++ QString::number(invokeInfo.prefix),
++ QString::number(invokeInfo.expr),
++ QString::number(invokeInfo.location),
++ QString::number(invokeInfo.context),
++ QString::number(invokeInfo.finalize))
++ .arg(invokeInfo.autoforward ? QStringLiteral("true") : QStringLiteral("false"));
++ {
++ QStringList l;
++ for (auto name : namelist) {
++ l.append(QString::number(name));
++ }
++ line += QStringLiteral("%1, ").arg(createContainer(l));
++ }
++ {
++ QStringList l;
++ for (const auto ¶meter : parameters) {
++ l += QStringLiteral("param(%1, %2, %3)")
++ .arg(QString::number(parameter.name),
++ QString::number(parameter.expr),
++ QString::number(parameter.location));
++ }
++ line += QStringLiteral("%1);").arg(createContainer(l));
++ }
++
++ factories.append(line);
++ return idx;
++ }
++ } // anonymous namespace
++
++ void CppDumper::dump(TranslationUnit *unit)
++ {
++ Q_ASSERT(unit);
++ Q_ASSERT(unit->mainDocument);
++
++ m_translationUnit = unit;
++
++ QString namespacePrefix;
++ if (!m_translationUnit->namespaceName.isEmpty()) {
++ namespacePrefix = QStringLiteral("::%1").arg(m_translationUnit->namespaceName);
++ }
++
++ QStringList classNames;
++ QList<GeneratedTableData> tables;
++ QList<GeneratedTableData::MetaDataInfo> metaDataInfos;
++ QList<GeneratedTableData::DataModelInfo> dataModelInfos;
++ QList<QStringList> factories;
++ auto docs = m_translationUnit->allDocuments;
++ tables.resize(docs.size());
++ metaDataInfos.resize(tables.size());
++ dataModelInfos.resize(tables.size());
++ factories.resize(tables.size());
++ auto classnameForDocument = m_translationUnit->classnameForDocument;
++
++ for (int i = 0, ei = docs.size(); i != ei; ++i) {
++ auto doc = docs.at(i);
++ auto metaDataInfo = &metaDataInfos[i];
++ GeneratedTableData::build(doc, &tables[i], metaDataInfo, &dataModelInfos[i],
++ [this, &factories, i, &classnameForDocument, &namespacePrefix](
++ const QScxmlExecutableContent::InvokeInfo &invokeInfo,
++ const QList<QScxmlExecutableContent::StringId> &names,
++ const QList<QScxmlExecutableContent::ParameterInfo> ¶meters,
++ const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int {
++ QString className;
++ if (invokeInfo.expr == QScxmlExecutableContent::NoEvaluator) {
++ className = mangleIdentifier(classnameForDocument.value(content.data()));
++ }
++ return createFactoryId(factories[i], className, namespacePrefix,
++ invokeInfo, names, parameters);
++ });
++ classNames.append(mangleIdentifier(classnameForDocument.value(doc)));
++ }
++
++ const QString headerName = QFileInfo(m_translationUnit->outHFileName).fileName();
++ const QString headerGuard = headerName.toUpper()
++ .replace(QLatin1Char('.'), QLatin1Char('_'))
++ .replace(QLatin1Char('-'), QLatin1Char('_'));
++ const QStringList forwardDecls = classNames.mid(1);
++ writeHeaderStart(headerGuard, forwardDecls);
++ writeImplStart();
++
++ for (int i = 0, ei = tables.size(); i != ei; ++i) {
++ const GeneratedTableData &table = tables.at(i);
++ DocumentModel::ScxmlDocument *doc = docs.at(i);
++ writeClass(classNames.at(i), metaDataInfos.at(i));
++ writeImplBody(table, classNames.at(i), doc, factories.at(i), metaDataInfos.at(i));
++
++ if (doc->root->dataModel == DocumentModel::Scxml::CppDataModel) {
++ Replacements r;
++ r[QStringLiteral("datamodel")] = doc->root->cppDataModelClassName;
++ generateCppDataModelEvaluators(dataModelInfos.at(i), r);
++ genTemplate(cpp, QStringLiteral(":/cppdatamodel.t"), r);
++ }
++ }
++
++ writeHeaderEnd(headerGuard, classNames);
++ writeImplEnd();
++ }
++
++ void CppDumper::writeHeaderStart(const QString &headerGuard, const QStringList &forwardDecls)
++ {
++ h << doNotEditComment.arg(m_translationUnit->scxmlFileName,
++ QString::number(Q_QSCXMLC_OUTPUT_REVISION),
++ QString::fromLatin1(QT_VERSION_STR))
++ << Qt::endl;
++
++ h << QStringLiteral("#ifndef ") << headerGuard << Qt::endl
++ << QStringLiteral("#define ") << headerGuard << Qt::endl
++ << Qt::endl;
++ h << l(headerStart);
++ if (!m_translationUnit->namespaceName.isEmpty())
++ h << l("namespace ") << m_translationUnit->namespaceName << l(" {") << Qt::endl << Qt::endl;
++
++ if (!forwardDecls.isEmpty()) {
++ for (const QString &forwardDecl : forwardDecls)
++ h << QStringLiteral("class %1;").arg(forwardDecl) << Qt::endl;
++ h << Qt::endl;
++ }
++ }
++
++ void CppDumper::writeClass(const QString &className, const GeneratedTableData::MetaDataInfo &info)
++ {
++ Replacements r;
++ r[QStringLiteral("classname")] = className;
++ r[QStringLiteral("properties")] = generatePropertyDecls(info);
++ if (m_translationUnit->stateMethods) {
++ r[QStringLiteral("accessors")] = generateAccessorDecls(info);
++ r[QStringLiteral("signals")] = generateSignalDecls(info);
++ } else {
++ r[QStringLiteral("accessors")] = QString();
++ r[QStringLiteral("signals")] = QString();
++ }
++ genTemplate(h, QStringLiteral(":/decl.t"), r);
++ }
++
++ void CppDumper::writeHeaderEnd(const QString &headerGuard, const QStringList &metatypeDecls)
++ {
++ QString ns;
++ if (!m_translationUnit->namespaceName.isEmpty()) {
++ h << QStringLiteral("} // %1 namespace ").arg(m_translationUnit->namespaceName) << Qt::endl
++ << Qt::endl;
++ ns = QStringLiteral("::%1").arg(m_translationUnit->namespaceName);
++ }
++
++ for (const QString &name : metatypeDecls) {
++ h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*)").arg(ns, name) << Qt::endl;
++ }
++ h << Qt::endl;
++
++ h << QStringLiteral("#endif // ") << headerGuard << Qt::endl;
++ }
++
++ void CppDumper::writeImplStart()
++ {
++ cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName,
++ QString::number(Q_QSCXMLC_OUTPUT_REVISION),
++ l(QT_VERSION_STR))
++ << Qt::endl;
++
++ QStringList includes;
++ for (DocumentModel::ScxmlDocument *doc : std::as_const(m_translationUnit->allDocuments)) {
++ switch (doc->root->dataModel) {
++ case DocumentModel::Scxml::NullDataModel:
++ includes += l("QScxmlNullDataModel");
++ break;
++ case DocumentModel::Scxml::JSDataModel:
++ includes += l("QScxmlDataModel");
++ break;
++ case DocumentModel::Scxml::CppDataModel:
++ includes += doc->root->cppDataModelHeaderName;
++ break;
++ }
++
++ }
++ includes.sort();
++ includes.removeDuplicates();
++
++ QString headerName = QFileInfo(m_translationUnit->outHFileName).fileName();
++ cpp << l("#include \"") << headerName << l("\"") << Qt::endl;
++ cpp << Qt::endl
++ << QStringLiteral("#include <qscxmlinvokableservice.h>") << Qt::endl
++ << QStringLiteral("#include <qscxmltabledata.h>") << Qt::endl
++ << QStringLiteral("#include <QtCore/qtmochelpers.h>") << Qt::endl;
++
++ for (const QString &inc : std::as_const(includes)) {
++ cpp << l("#include <") << inc << l(">") << Qt::endl;
++ }
++ cpp << Qt::endl
++ << revisionCheck.arg(m_translationUnit->scxmlFileName,
++ QString::number(Q_QSCXMLC_OUTPUT_REVISION),
++ QString::fromLatin1(QT_VERSION_STR))
++ << Qt::endl;
++ if (!m_translationUnit->namespaceName.isEmpty())
++ cpp << l("namespace ") << m_translationUnit->namespaceName << l(" {") << Qt::endl << Qt::endl;
++ }
++
++ void CppDumper::writeImplBody(const GeneratedTableData &table,
++ const QString &className,
++ DocumentModel::ScxmlDocument *doc,
++ const QStringList &factory,
++ const GeneratedTableData::MetaDataInfo &info)
++ {
++ QString dataModelField, dataModelInitialization;
++ switch (doc->root->dataModel) {
++ case DocumentModel::Scxml::NullDataModel:
++ dataModelField = l("QScxmlNullDataModel dataModel;");
++ dataModelInitialization = l("stateMachine.setDataModel(&dataModel);");
++ break;
++ case DocumentModel::Scxml::JSDataModel:
++ dataModelField = l("std::unique_ptr<QScxmlDataModel> dataModel;");
++ dataModelInitialization = l(
++ " dataModel.reset(QScxmlDataModel::createScxmlDataModel(QStringLiteral(\"ecmascriptdatamodel\")));\n"
++ " stateMachine.setDataModel(dataModel.get());\n"
++ );
++ break;
++ case DocumentModel::Scxml::CppDataModel:
++ dataModelField = QStringLiteral("// Data model %1 is set from outside.").arg(
++ doc->root->cppDataModelClassName);
++ dataModelInitialization = dataModelField;
++ break;
++ }
++
++ QString name;
++ if (table.theName == -1) {
++ name = QStringLiteral("QString()");
++ } else {
++ name = QStringLiteral("string(%1)").arg(table.theName);
++ }
++
++ QString serviceFactories;
++ if (factory.isEmpty()) {
++ serviceFactories = QStringLiteral(" Q_UNUSED(id);\n Q_UNREACHABLE();");
++ } else {
++ serviceFactories = QStringLiteral(" switch (id) {\n ")
++ + factory.join(QStringLiteral("\n "))
++ + QStringLiteral("\n default: Q_UNREACHABLE();\n }");
++ }
++
++
++ Replacements r;
++ r[QStringLiteral("classname")] = className;
++ r[QStringLiteral("name")] = name;
++ r[QStringLiteral("initialSetup")] = QString::number(table.initialSetup());
++ generateTables(table, r);
++ r[QStringLiteral("dataModelField")] = dataModelField;
++ r[QStringLiteral("dataModelInitialization")] = dataModelInitialization;
++ r[QStringLiteral("theStateMachineTable")] =
++ GeneratedTableData::toString(table.stateMachineTable());
++ r[QStringLiteral("metaObject")] = generateMetaObject(className, info);
++ r[QStringLiteral("serviceFactories")] = serviceFactories;
++ genTemplate(cpp, QStringLiteral(":/data.t"), r);
++ }
++
++ void CppDumper::writeImplEnd()
++ {
++ if (!m_translationUnit->namespaceName.isEmpty()) {
++ cpp << Qt::endl
++ << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << Qt::endl;
++ }
++ }
++
++ /*!
++ * \internal
++ * Mangles \a str to be a unique C++ identifier. Characters that are invalid for C++ identifiers
++ * are replaced by the pattern \c _0x<hex>_ where <hex> is the hexadecimal unicode
++ * representation of the character. As identifiers with leading underscores followed by either
++ * another underscore or a capital letter are reserved in C++, we also escape those, by escaping
++ * the first underscore, using the above method.
++ *
++ * We keep track of all identifiers we have used so far and if we find two different names that
++ * map to the same mangled identifier by the above method, we append underscores to the new one
++ * until the result is unique.
++ *
++ * \note
++ * Although C++11 allows for non-ascii (unicode) characters to be used in identifiers,
++ * many compilers forgot to read the spec and do not implement this. Some also do not
++ * implement C99 identifiers, because that is \e {at the implementation's discretion}. So,
++ * we are stuck with plain old boring identifiers.
++ */
++ QString CppDumper::mangleIdentifier(const QString &str)
++ {
++ auto isNonDigit = [](QChar c) -> bool {
++ return (c >= QLatin1Char('a') && c <= QLatin1Char('z')) ||
++ (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) ||
++ c == QLatin1Char('_');
++ };
++
++ Q_ASSERT(!str.isEmpty());
++
++ QString mangled;
++ mangled.reserve(str.size());
++
++ int i = 0;
++ if (str.startsWith(QLatin1Char('_')) && str.size() > 1) {
++ QChar ch = str.at(1);
++ if (ch == QLatin1Char('_')
++ || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z'))) {
++ mangled += QLatin1String("_0x5f_");
++ ++i;
++ }
++ }
++
++ for (int ei = str.size(); i != ei; ++i) {
++ auto c = str.at(i);
++ if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) {
++ mangled += c;
++ } else {
++ mangled += QLatin1String("_0x") + QString::number(c.unicode(), 16) + QLatin1Char('_');
++ }
++ }
++
++ while (true) {
++ auto it = m_mangledToOriginal.constFind(mangled);
++ if (it == m_mangledToOriginal.constEnd()) {
++ m_mangledToOriginal.insert(mangled, str);
++ break;
++ } else if (it.value() == str) {
++ break;
++ }
++ mangled += QStringLiteral("_"); // append underscores until we get a unique name
++ }
++
++ return mangled;
++ }
++
++ QString CppDumper::generatePropertyDecls(const GeneratedTableData::MetaDataInfo &info)
++ {
++ QString decls;
++
++ for (const QString &stateName : info.stateNames) {
++ if (stateName.isEmpty())
++ continue;
++
++ if (m_translationUnit->stateMethods) {
++ decls += QString::fromLatin1(" Q_PROPERTY(bool %1 READ %2 NOTIFY %3)\n")
++ .arg(stateName, mangleIdentifier(stateName),
++ mangleIdentifier(stateName + QStringLiteral("Changed")));
++ } else {
++ decls += QString::fromLatin1(" Q_PROPERTY(bool %1)\n").arg(stateName);
++ }
++ }
++
++ return decls;
++ }
++
++ QString CppDumper::generateAccessorDecls(const GeneratedTableData::MetaDataInfo &info)
++ {
++ QString decls;
++
++ for (const QString &stateName : info.stateNames) {
++ if (!stateName.isEmpty())
++ decls += QString::fromLatin1(" bool %1() const;\n").arg(mangleIdentifier(stateName));
++ }
++
++ return decls;
++ }
++
++ QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info)
++ {
++ QString decls;
++
++ for (const QString &stateName : info.stateNames) {
++ if (!stateName.isEmpty()) {
++ decls += QString::fromLatin1(" void %1(bool);\n")
++ .arg(mangleIdentifier(stateName + QStringLiteral("Changed")));
++ }
++ }
++
++ return decls;
++ }
++
++ QByteArray classDefJsonObjectHash(const QJsonObject &object)
++ {
++ const QByteArray json = QJsonDocument(object).toJson(QJsonValue::JsonFormat::Compact);
++ QByteArray hash(20, 0); // SHA1 produces 160 bits of data
++
++ {
++ Sha1State state;
++ sha1InitState(&state);
++ sha1Update(&state, reinterpret_cast<const uchar *>(json.constData()), json.size());
++ sha1FinalizeState(&state);
++ sha1ToHash(&state, reinterpret_cast<uchar *>(hash.data()));
++ }
++
++ static const char revisionPrefix[] = "0$";
++ const QByteArray hashB64 = hash.toBase64(QByteArray::OmitTrailingEquals);
++ return revisionPrefix + hashB64;
++ }
++
++ QString CppDumper::generateMetaObject(const QString &className,
++ const GeneratedTableData::MetaDataInfo &info)
++ {
++ ClassDef classDef;
++ classDef.classname = className.toUtf8();
++ classDef.qualified = classDef.classname;
++ classDef.superclassList << SuperClass {
++ QByteArray("QScxmlStateMachine"),
++ QByteArray(QT_STRINGIFY(QT_PREPEND_NAMESPACE(QScxmlStateMachine))),
++ FunctionDef::Public
++ };
++ classDef.hasQObject = true;
++ FunctionDef constructor;
++ constructor.name = className.toUtf8();
++ constructor.access = FunctionDef::Public;
++ constructor.isInvokable = true;
++ constructor.isConstructor = true;
++
++ ArgumentDef arg;
++ arg.type.name = "QObject *";
++ arg.type.rawName = arg.type.name;
++ arg.normalizedType = arg.type.name;
++ arg.name = "parent";
++ constructor.arguments.append(arg);
++ classDef.constructorList.append(constructor);
++
++ // stateNames:
++ int stateIdx = 0;
++ for (const QString &stateName : info.stateNames) {
++ if (stateName.isEmpty())
++ continue;
++
++ QByteArray utf8StateName = stateName.toUtf8();
++
++ FunctionDef signal;
++ signal.type.name = "void";
++ signal.type.rawName = signal.type.name;
++ signal.normalizedType = signal.type.name;
++ signal.name = utf8StateName + "Changed";
++ if (m_translationUnit->stateMethods)
++ signal.mangledName = mangleIdentifier(stateName + QStringLiteral("Changed")).toUtf8();
++ signal.access = FunctionDef::Public;
++ signal.isSignal = true;
++ signal.implementation = "QMetaObject::activate(%s, &staticMetaObject, %d, _a);";
++
++ ArgumentDef arg;
++ arg.type.name = "bool";
++ arg.type.rawName = arg.type.name;
++ arg.normalizedType = arg.type.name;
++ arg.name = "active";
++ signal.arguments << arg;
++ classDef.signalList << signal;
++
++ PropertyDef prop;
++ prop.name = stateName.toUtf8();
++ if (m_translationUnit->stateMethods)
++ prop.mangledName = mangleIdentifier(stateName).toUtf8();
++ prop.type = "bool";
++ prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")";
++ prop.notify = utf8StateName + "Changed";
++ prop.notifyId = classDef.signalList.size() - 1;
++ prop.gspec = PropertyDef::ValueSpec;
++ prop.scriptable = "true";
++ classDef.propertyList << prop;
++ }
++
++ // sub-statemachines:
++ QHash<QByteArray, QByteArray> knownQObjectClasses;
++ knownQObjectClasses.insert(QByteArray("QScxmlStateMachine"), QByteArray());
++
++ QHash<QByteArray, QJsonObject> classDefJsonObjects;
++ const QJsonObject jsonObject = classDef.toJson();
++ classDefJsonObjects.insert(classDef.qualified, jsonObject);
++
++ QBuffer buf;
++ buf.open(QIODevice::WriteOnly);
++ Generator generator(&classDef, QList<QByteArray>(), knownQObjectClasses,
++ QHash<QByteArray, QByteArray>(), buf);
++ generator.generateCode();
++ if (m_translationUnit->stateMethods) {
++ generator.generateAccessorDefs();
++ generator.generateSignalDefs();
++ }
++ buf.close();
++ return QString::fromUtf8(buf.buffer());
++ }
++
++ QT_END_NAMESPACE