blob: 1aca09ef7833a7bc7e6a1d81ad7ca8b0cd26fd43 [file] [log] [blame]
/****************************************************************************
**
** 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> &parameters)
{
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 &parameter : 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> &parameters,
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 : 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("\"") << Qt::endl;
cpp << Qt::endl
<< QStringLiteral("#include <qscxmlinvokableservice.h>") << Qt::endl
<< QStringLiteral("#include <qscxmltabledata.h>") << Qt::endl;
for (const QString &inc : qAsConst(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("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 << 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.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