| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtScxml module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "scxmlcppdumper.h" |
| #include "generator.h" |
| |
| #include <QtScxml/private/qscxmlexecutablecontent_p.h> |
| |
| #include <QtCore/qfileinfo.h> |
| #include <QtCore/qbuffer.h> |
| #include <QtCore/qfile.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.length(); ++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); |
| QByteArray data; |
| data = QByteArray::fromRawData(reinterpret_cast<const char *>(file.data()), |
| int(file.size())); |
| const QString t = QString::fromLatin1(data); |
| data.clear(); |
| |
| int start = 0; |
| for (int openIdx = t.indexOf(QStringLiteral("${"), start); openIdx >= 0; openIdx = |
| t.indexOf(QStringLiteral("${"), start)) { |
| out << t.midRef(start, openIdx - start); |
| openIdx += 2; |
| const int closeIdx = t.indexOf(QLatin1Char('}'), openIdx); |
| Q_ASSERT(closeIdx >= openIdx); |
| QString key = t.mid(openIdx, closeIdx - openIdx); |
| if (!replacements.contains(key)) { |
| qFatal("Replacing '%s' failed: no replacement found", qPrintable(key)); |
| } |
| out << replacements.value(key); |
| start = closeIdx + 1; |
| } |
| out << t.midRef(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.length() + nr.length() + 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("STR_LIT(%1, %2, %3)").arg( |
| QString::number(idx), 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 QVector<QScxmlExecutableContent::StringId> &namelist, |
| const QVector<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; |
| QVector<GeneratedTableData> tables; |
| QVector<GeneratedTableData::MetaDataInfo> metaDataInfos; |
| QVector<GeneratedTableData::DataModelInfo> dataModelInfos; |
| QVector<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 QVector<QScxmlExecutableContent::StringId> &names, |
| const QVector<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)) |
| << endl; |
| |
| h << QStringLiteral("#ifndef ") << headerGuard << endl |
| << QStringLiteral("#define ") << headerGuard << endl |
| << endl; |
| h << l(headerStart); |
| if (!m_translationUnit->namespaceName.isEmpty()) |
| h << l("namespace ") << m_translationUnit->namespaceName << l(" {") << endl << endl; |
| |
| if (!forwardDecls.isEmpty()) { |
| for (const QString &forwardDecl : forwardDecls) |
| h << QStringLiteral("class %1;").arg(forwardDecl) << endl; |
| h << 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) << endl |
| << endl; |
| ns = QStringLiteral("::%1").arg(m_translationUnit->namespaceName); |
| } |
| |
| for (const QString &name : metatypeDecls) { |
| h << QStringLiteral("Q_DECLARE_METATYPE(%1::%2*)").arg(ns, name) << endl; |
| } |
| h << endl; |
| |
| h << QStringLiteral("#endif // ") << headerGuard << endl; |
| } |
| |
| void CppDumper::writeImplStart() |
| { |
| cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName, |
| QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
| l(QT_VERSION_STR)) |
| << endl; |
| |
| QStringList includes; |
| for (DocumentModel::ScxmlDocument *doc : qAsConst(m_translationUnit->allDocuments)) { |
| switch (doc->root->dataModel) { |
| case DocumentModel::Scxml::NullDataModel: |
| includes += l("QScxmlNullDataModel"); |
| break; |
| case DocumentModel::Scxml::JSDataModel: |
| includes += l("QScxmlEcmaScriptDataModel"); |
| 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("\"") << endl; |
| cpp << endl |
| << QStringLiteral("#include <qscxmlinvokableservice.h>") << endl |
| << QStringLiteral("#include <qscxmltabledata.h>") << endl; |
| for (const QString &inc : qAsConst(includes)) { |
| cpp << l("#include <") << inc << l(">") << endl; |
| } |
| cpp << endl |
| << revisionCheck.arg(m_translationUnit->scxmlFileName, |
| QString::number(Q_QSCXMLC_OUTPUT_REVISION), |
| QString::fromLatin1(QT_VERSION_STR)) |
| << endl; |
| if (!m_translationUnit->namespaceName.isEmpty()) |
| cpp << l("namespace ") << m_translationUnit->namespaceName << l(" {") << endl << 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("QScxmlEcmaScriptDataModel dataModel;"); |
| dataModelInitialization = l("stateMachine.setDataModel(&dataModel);"); |
| 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 << endl |
| << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << 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.length(); 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; |
| } |
| |
| QString CppDumper::generateMetaObject(const QString &className, |
| const GeneratedTableData::MetaDataInfo &info) |
| { |
| ClassDef classDef; |
| classDef.classname = className.toUtf8(); |
| classDef.qualified = classDef.classname; |
| classDef.superclassList << qMakePair(QByteArray("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"; |
| arg.typeNameForCast = arg.type.name + "*"; |
| 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"; |
| arg.typeNameForCast = arg.type.name + "*"; |
| signal.arguments << arg; |
| classDef.signalList << signal; |
| |
| ++classDef.notifyableProperties; |
| 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()); |
| |
| 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 |