ff4ff35918
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
834 lines
31 KiB
C++
834 lines
31 KiB
C++
// 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
|