diff options
Diffstat (limited to 'tools/qscxmlc')
-rw-r--r-- | tools/qscxmlc/cppdatamodel.t | 42 | ||||
-rw-r--r-- | tools/qscxmlc/data.t | 125 | ||||
-rw-r--r-- | tools/qscxmlc/decl.t | 21 | ||||
-rw-r--r-- | tools/qscxmlc/generator.cpp | 13 | ||||
-rw-r--r-- | tools/qscxmlc/qscxmlc.pro | 3 | ||||
-rw-r--r-- | tools/qscxmlc/scxmlcppdumper.cpp | 2037 | ||||
-rw-r--r-- | tools/qscxmlc/scxmlcppdumper.h | 28 | ||||
-rw-r--r-- | tools/qscxmlc/templates.qrc | 7 |
8 files changed, 960 insertions, 1316 deletions
diff --git a/tools/qscxmlc/cppdatamodel.t b/tools/qscxmlc/cppdatamodel.t new file mode 100644 index 0000000..524571b --- /dev/null +++ b/tools/qscxmlc/cppdatamodel.t @@ -0,0 +1,42 @@ +QString ${datamodel}::evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; + switch (id) { +${evaluateToStringCases} default: + Q_UNREACHABLE(); + *ok = false; + return QString(); + } +} + +bool ${datamodel}::evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; + switch (id) { +${evaluateToBoolCases} default: + Q_UNREACHABLE(); + *ok = false; + return false; + } +} + +QVariant ${datamodel}::evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; + switch (id) { +${evaluateToVariantCases} default: + Q_UNREACHABLE(); + *ok = false; + return QVariant(); + } +} + +void ${datamodel}::evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok) +{ + *ok = true; + switch (id) { +${evaluateToVoidCases} default: + Q_UNREACHABLE(); + *ok = false; + } +} diff --git a/tools/qscxmlc/data.t b/tools/qscxmlc/data.t new file mode 100644 index 0000000..a1eece5 --- /dev/null +++ b/tools/qscxmlc/data.t @@ -0,0 +1,125 @@ +struct ${classname}::Data: private QScxmlTableData { + Data(${classname} &stateMachine) + : stateMachine(stateMachine) + {} + + void init() { + stateMachine.setTableData(this); + ${dataModelInitialization} + } + + QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return ${name}; } + + QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return ${initialSetup}; } + + QScxmlExecutableContent::Instructions instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return theInstructions; } + + QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL + { *count = ${dataNameCount}; return dataIds; } + + QScxmlExecutableContent::EvaluatorInfo evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(evaluatorId >= 0); Q_ASSERT(evaluatorId < ${evaluatorCount}); return evaluators[evaluatorId]; } + + QScxmlExecutableContent::AssignmentInfo assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(assignmentId >= 0); Q_ASSERT(assignmentId < ${assignmentCount}); return assignments[assignmentId]; } + + QScxmlExecutableContent::ForeachInfo foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL + { Q_ASSERT(foreachId >= 0); Q_ASSERT(foreachId < ${foreachCount}); return foreaches[foreachId]; } + + QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL + { + Q_ASSERT(id >= QScxmlExecutableContent::NoString); Q_ASSERT(id < ${stringCount}); + if (id == QScxmlExecutableContent::NoString) return QString(); + return QString({static_cast<QStringData*>(strings.data + id)}); + } + + const qint32 *stateMachineTable() const Q_DECL_OVERRIDE Q_DECL_FINAL + { return theStateMachineTable; } + + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE Q_DECL_FINAL; + int signalIndexForEvent(const QString &event) const Q_DECL_OVERRIDE Q_DECL_FINAL; + + ${classname} &stateMachine; + ${dataModelField} + + static qint32 theInstructions[]; + static QScxmlExecutableContent::StringId dataIds[]; + static QScxmlExecutableContent::EvaluatorInfo evaluators[]; + static QScxmlExecutableContent::AssignmentInfo assignments[]; + static QScxmlExecutableContent::ForeachInfo foreaches[]; + static const qint32 theStateMachineTable[]; + static struct Strings { + QArrayData data[${stringCount}]; + qunicodechar stringdata[${stringdataSize}]; + } strings; + + static std::vector<QString> outgoingEvents; +}; + +${classname}::${classname}(QObject *parent) + : QScxmlStateMachine(parent) + , data(new Data(*this)) +{ qRegisterMetaType<${classname} *>(); data->init(); } + +${classname}::~${classname}() +{ delete data; } + +QScxmlInvokableServiceFactory *${classname}::Data::serviceFactory(int id) const +{ + switch (id) { + ${serviceFactories} + } +} + +std::vector<QString> ${classname}::Data::outgoingEvents = { +${outgoingEvents} +}; + +int ${classname}::Data::signalIndexForEvent(const QString &event) const +{ + auto it = std::lower_bound(outgoingEvents.begin(), outgoingEvents.end(), event); + if (it != outgoingEvents.end() && *it == event) { + return int(std::distance(outgoingEvents.begin(), it)); + } else { + return -1; + } +} + +qint32 ${classname}::Data::theInstructions[] = { +${theInstructions} +}; + +QScxmlExecutableContent::StringId ${classname}::Data::dataIds[] = { +${dataIds} +}; + +QScxmlExecutableContent::EvaluatorInfo ${classname}::Data::evaluators[] = { +${evaluators} +}; + +QScxmlExecutableContent::AssignmentInfo ${classname}::Data::assignments[] = { +${assignments} +}; + +QScxmlExecutableContent::ForeachInfo ${classname}::Data::foreaches[] = { +${foreaches} +}; + +#define STR_LIT(idx, ofs, len) \ + Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ + qptrdiff(offsetof(Strings, stringdata) + ofs * sizeof(qunicodechar) - idx * sizeof(QArrayData)) \ + ) +${classname}::Data::Strings ${classname}::Data::strings = {{ +${strLits} +},{ +${uniLits} +}}; + +const qint32 ${classname}::Data::theStateMachineTable[] = ${theStateMachineTable}; + +${metaObject} +${getters} +${slots} diff --git a/tools/qscxmlc/decl.t b/tools/qscxmlc/decl.t new file mode 100644 index 0000000..2b35d67 --- /dev/null +++ b/tools/qscxmlc/decl.t @@ -0,0 +1,21 @@ +class ${classname}: public QScxmlStateMachine +{ +public: + /* qmake ignore Q_OBJECT */ + Q_OBJECT + +public: + ${classname}(QObject *parent = 0); + ~${classname}(); + +${getters} +signals: +${signals} +public slots: +${slots} +private: + struct Data; + friend struct Data; + struct Data *data; +}; + diff --git a/tools/qscxmlc/generator.cpp b/tools/qscxmlc/generator.cpp index b37e928..561bc05 100644 --- a/tools/qscxmlc/generator.cpp +++ b/tools/qscxmlc/generator.cpp @@ -1146,13 +1146,13 @@ void Generator::generateStaticMetacall() Q_ASSERT(!f.normalizedType.isEmpty()); fprintf(out, " case %d: ", methodindex); - //---- + //---- Changed from the original in moc if (f.implementation) { fprintf(out, f.implementation, methodindex); fprintf(out, " break;\n"); continue; } - //---- + //---- End of change if (f.normalizedType != "void") fprintf(out, "{ %s _r = ", noRef(f.normalizedType).constData()); @@ -1327,8 +1327,13 @@ void Generator::generateStaticMetacall() fprintf(out, " case %d: *reinterpret_cast<int*>(_v) = QFlag(%s%s()); break;\n", propindex, prefix.constData(), p.read.constData()); else if (!p.read.isEmpty()) - fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s(); break;\n", - propindex, p.type.constData(), prefix.constData(), p.read.constData()); + //---- Changed from the original in moc + { + fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s%s; break;\n", + propindex, p.type.constData(), prefix.constData(), p.read.constData(), + p.read.endsWith(')') ? "" : "()"); + } + //---- End of change else fprintf(out, " case %d: *reinterpret_cast< %s*>(_v) = %s%s; break;\n", propindex, p.type.constData(), prefix.constData(), p.member.constData()); diff --git a/tools/qscxmlc/qscxmlc.pro b/tools/qscxmlc/qscxmlc.pro index 7621eef..7620d3d 100644 --- a/tools/qscxmlc/qscxmlc.pro +++ b/tools/qscxmlc/qscxmlc.pro @@ -11,3 +11,6 @@ SOURCES += \ main.cpp load(qt_tool) +load(resources) + +RESOURCES += templates.qrc diff --git a/tools/qscxmlc/scxmlcppdumper.cpp b/tools/qscxmlc/scxmlcppdumper.cpp index 6efa5f6..f6cc42a 100644 --- a/tools/qscxmlc/scxmlcppdumper.cpp +++ b/tools/qscxmlc/scxmlcppdumper.cpp @@ -33,11 +33,17 @@ #include <functional> #include <QFileInfo> #include <QBuffer> +#include <QFile> +#include <QResource> #include "generator.h" QT_BEGIN_NAMESPACE +using namespace QScxmlInternal; + +namespace { + static const QString doNotEditComment = QString::fromLatin1( "//\n" "// Statemachine code from reading SCXML file '%1'\n" @@ -56,91 +62,6 @@ static const QString revisionCheck = QString::fromLatin1( "#endif\n" ); -struct StringListDumper { - StringListDumper &operator <<(const QString &s) { - text.append(s); - return *this; - } - - StringListDumper &operator <<(const QLatin1String &s) { - text.append(s); - return *this; - } - StringListDumper &operator <<(const char *s) { - text.append(QLatin1String(s)); - return *this; - } - StringListDumper &operator <<(int i) { - text.append(QString::number(i)); - return *this; - } - StringListDumper &operator <<(const QByteArray &s) { - text.append(QString::fromUtf8(s)); - return *this; - } - - bool isEmpty() const { - return text.isEmpty(); - } - - void write(QTextStream &out, const QString &prefix, const QString &suffix, const QString &mainClassName = QString()) const - { - foreach (QString line, text) { - if (!mainClassName.isEmpty() && line.contains(QStringLiteral("%"))) { - line = line.arg(mainClassName); - } - out << prefix << line << suffix; - } - } - - void unique() - { - text.sort(); - text.removeDuplicates(); - } - - QStringList text; -}; - -struct Method { - StringListDumper initializer; - Method(const QString &decl = QString()): decl(decl) {} - Method(const StringListDumper &impl): impl(impl) {} - QString decl; // void f(int i = 0); - StringListDumper impl; // void f(int i) { m_i = ++i; } -}; - -struct ClassDump { - bool needsEventFilter; - StringListDumper implIncludes; - QString className; - QString dataModelClassName; - StringListDumper classFields; - StringListDumper tables; - Method init; - Method initDataModel; - StringListDumper dataMethods; - StringListDumper classMethods; - Method constructor; - Method destructor; - StringListDumper properties; - StringListDumper signalMethods; - QList<Method> publicMethods; - QList<Method> protectedMethods; - StringListDumper publicSlotDeclarations; - StringListDumper publicSlotDefinitions; - - QList<Method> dataModelMethods; - - ClassDump() - : needsEventFilter(false) - {} - - QByteArray metaData; -}; - -namespace { - QString cEscape(const QString &str) { QString res; @@ -179,1136 +100,328 @@ QString cEscape(const QString &str) 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)); + } + QByteArray data; + if (file.isCompressed() && file.size()) { + data = qUncompress(file.data(), int(file.size())); + } else { + 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 <QByteArray>\n" + "#include <QVariant>\n" "\n"; using namespace DocumentModel; -enum class Evaluator +QString createContainer(const QString &baseType, const QString &elementType, + const QStringList &elements, bool useCxx11) { - ToVariant, - ToString, - ToBool, - Assignment, - Foreach, - Script -}; - -class DumperVisitor: public QScxmlExecutableContent::Builder -{ - Q_DISABLE_COPY(DumperVisitor) - -public: - DumperVisitor(ClassDump &clazz, TranslationUnit *tu) - : namespacePrefix(QStringLiteral("::")) - , clazz(clazz) - , translationUnit(tu) - , m_bindLate(false) - , m_qtMode(false) - { - if (!tu->namespaceName.isEmpty()) { - namespacePrefix += QStringLiteral("%1::").arg(tu->namespaceName); - } - } - - void process(ScxmlDocument *doc) - { - Q_ASSERT(doc); - - clazz.className = mangleIdentifier(translationUnit->classnameForDocument.value(doc)); - m_qtMode = doc->qtMode; - - doc->root->accept(this); - - addSubStateMachineProperties(doc); - addEvents(); - - generateMetaObject(); - generateTables(); - } - - ~DumperVisitor() - { - Q_ASSERT(m_parents.isEmpty()); - } - -protected: - using NodeVisitor::visit; - - bool visit(Scxml *node) Q_DECL_OVERRIDE - { - // init: - if (!node->name.isEmpty()) { - clazz.dataMethods << QStringLiteral("QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return string(%1); }").arg(addString(node->name)) - << QString(); - clazz.init.impl << QStringLiteral("stateMachine.setObjectName(string(%1));").arg(addString(node->name)); + QString result; + if (useCxx11) { + if (elements.isEmpty()) { + result += QStringLiteral("{}"); } else { - clazz.dataMethods << QStringLiteral("QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return QString(); }") - << QString(); - } - if (node->dataModel == Scxml::CppDataModel) { - // Tell the builder not to generate any script strings when visiting any executable content. - // We'll take care of the evaluators ourselves. - setIsCppDataModel(true); + result += QStringLiteral("{ ") + elements.join(QStringLiteral(", ")) + QStringLiteral(" }"); } - - QString binding; - switch (node->binding) { - case Scxml::EarlyBinding: - binding = QStringLiteral("Early"); - break; - case Scxml::LateBinding: - binding = QStringLiteral("Late"); - m_bindLate = true; - break; - default: - Q_UNREACHABLE(); + } else { + result += QStringLiteral("%1<%2>()").arg(baseType, elementType); + if (!elements.isEmpty()) { + result += QStringLiteral(" << ") + elements.join(QStringLiteral(" << ")); } - clazz.init.impl << QStringLiteral("stateMachine.setDataBinding(QScxmlStateMachine::%1Binding);").arg(binding); - clazz.implIncludes << QStringLiteral("qscxmlexecutablecontent.h"); - clazz.init.impl << QStringLiteral("stateMachine.setTableData(this);"); - - foreach (AbstractState *s, node->initialStates) { - clazz.init.impl << QStringLiteral("%1.setAsInitialStateFor(&stateMachine);") - .arg(mangledName(s, StateName)); - } - - // visit the kids: - m_parents.append(node); - visit(node->children); - visit(node->dataElements); - - m_dataElements.append(node->dataElements); - if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return %1; }").arg(startNewSequence()) - << QString(); - generate(m_dataElements); - if (node->script) { - node->script->accept(this); - } - visit(&node->initialSetup); - endSequence(); - } else { - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return QScxmlExecutableContent::NoInstruction; }") - << QString(); - } - - m_parents.removeLast(); - - { // the data model: - switch (node->dataModel) { - case Scxml::NullDataModel: - clazz.classFields << QStringLiteral("QScxmlNullDataModel dataModel;"); - clazz.implIncludes << QStringLiteral("QScxmlNullDataModel"); - clazz.init.impl << QStringLiteral("stateMachine.setDataModel(&dataModel);"); - break; - case Scxml::JSDataModel: - clazz.classFields << QStringLiteral("QScxmlEcmaScriptDataModel dataModel;"); - clazz.implIncludes << QStringLiteral("QScxmlEcmaScriptDataModel"); - clazz.init.impl << QStringLiteral("stateMachine.setDataModel(&dataModel);"); - break; - case Scxml::CppDataModel: - clazz.dataModelClassName = node->cppDataModelClassName; - clazz.implIncludes << node->cppDataModelHeaderName; - break; - default: - Q_UNREACHABLE(); - } - } - return false; } + return result; +} - bool visit(State *node) Q_DECL_OVERRIDE - { - QString name = mangledName(node, PlainName); - QString stateName = mangledName(node, StateName); - // Property stuff: - if (isValidQPropertyName(node->id)) { - clazz.properties << QStringLiteral("Q_PROPERTY(bool %1 READ %2 NOTIFY %3)") - .arg(node->id).arg(name) - .arg(mangledName(node, SignalName)); - } - if (m_qtMode) { - Method getter(QStringLiteral("bool %1() const").arg(name)); - getter.impl << QStringLiteral("bool %2::%1() const").arg(name) - << QStringLiteral("{ return data->%1.active(); }").arg(stateName); - clazz.publicMethods << getter; - } - - // Declaration: - if (node->type == State::Final) { - clazz.classFields << QStringLiteral("QScxmlFinalState ") + stateName + QLatin1Char(';'); - } else { - clazz.classFields << QStringLiteral("QScxmlState ") + stateName + QLatin1Char(';'); - } - - // Initializer: - clazz.constructor.initializer << generateInitializer(node); - - // init: - if (!node->id.isEmpty()) { - clazz.init.impl << stateName + QStringLiteral(".setObjectName(string(%1));").arg(addString(node->id)); - } - if (node->type == State::Parallel) { - clazz.init.impl << stateName + QStringLiteral(".setChildMode(QState::ParallelStates);"); - } else { - foreach (AbstractState *initialState, node->initialStates) { - clazz.init.impl << stateName + QStringLiteral(".setInitialState(&") - + mangledName(initialState, StateName) - + QStringLiteral(");"); - } - - } - if (!node->id.isEmpty()) { - clazz.init.impl << QStringLiteral("QObject::connect(&") - + stateName - + QStringLiteral(", SIGNAL(activeChanged(bool)), &stateMachine, SIGNAL(") - + mangledName(node, SignalName) - + QStringLiteral("(bool)));"); - } - - m_stateNames.append(node->id); - m_stateFieldNames.append(stateName); - - // visit the kids: - m_parents.append(node); - if (!node->dataElements.isEmpty()) { - if (m_bindLate) { - clazz.init.impl << stateName + QStringLiteral(".setInitInstructions(%1);").arg(startNewSequence()); - generate(node->dataElements); - endSequence(); - } else { - m_dataElements.append(node->dataElements); - } - } - - visit(node->children); - if (!node->onEntry.isEmpty()) - clazz.init.impl << stateName + QStringLiteral(".setOnEntryInstructions(%1);").arg(generate(node->onEntry)); - if (!node->onExit.isEmpty()) - clazz.init.impl << stateName + QStringLiteral(".setOnExitInstructions(%1);").arg(generate(node->onExit)); - if (!node->invokes.isEmpty()) { - QStringList lines; - for (int i = 0, ei = node->invokes.size(); i != ei; ++i) { - Invoke *invoke = node->invokes.at(i); - QString line = QStringLiteral("new QScxmlInvokeScxmlFactory<%1>(").arg(scxmlClassName(invoke->content.data())); - line += QStringLiteral("%1, ").arg(Builder::createContext(QStringLiteral("invoke"))); - line += QStringLiteral("%1, ").arg(createEvaluatorString(QStringLiteral("invoke"), - QStringLiteral("srcexpr"), - invoke->srcexpr)); - line += QStringLiteral("%1, ").arg(addString(invoke->id)); - line += QStringLiteral("%1, ").arg(addString(node->id + QStringLiteral(".session-"))); - line += QStringLiteral("%1, ").arg(addString(invoke->idLocation)); - { - QStringList l; - foreach (const QString &name, invoke->namelist) { - l.append(QString::number(addString(name))); - } - line += QStringLiteral("%1, ").arg(createVector(QStringLiteral("QScxmlExecutableContent::StringId"), l)); - } - line += QStringLiteral("%1, ").arg(invoke->autoforward ? QStringLiteral("true") : QStringLiteral("false")); - { - QStringList l; - foreach (DocumentModel::Param *param, invoke->params) { - l += QStringLiteral("QScxmlInvokableServiceFactory::Param(%1, %2, %3)") - .arg(addString(param->name)) - .arg(createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), param->expr)) - .arg(addString(param->location)); - } - line += QStringLiteral("%1, ").arg(createVector(QStringLiteral("QScxmlInvokableServiceFactory::Param"), l)); - } - if (invoke->finalize.isEmpty()) { - line += QStringLiteral("QScxmlExecutableContent::NoInstruction"); - } else { - line += QString::number(startNewSequence()); - visit(&invoke->finalize); - endSequence(); - } - line += QLatin1Char(')'); - lines << line; - } - clazz.init.impl << stateName + QStringLiteral(".setInvokableServiceFactories("); - clazz.init.impl << QStringLiteral(" ") + createVector(QStringLiteral("QScxmlInvokableServiceFactory *"), lines); - clazz.init.impl << QStringLiteral(");"); - } - - if (node->type == State::Final) { - auto id = generate(node->doneData); - clazz.init.impl << stateName + QStringLiteral(".setDoneData(%1);").arg(id); - } - - m_parents.removeLast(); - return false; - } - - bool visit(Transition *node) Q_DECL_OVERRIDE - { - const QString tName = transitionName(node); - if (m_qtMode) { - foreach (const QString &event, node->events) { - if (!DocumentModel::isEventToBeGenerated(event)) - continue; - - // If the event name is not filtered out, is was already validated inside: - // bool ScxmlVerifier::visit(DocumentModel::Transition *transition) - // by a call to: validateEventName(); - m_knownEvents.insert(event); - } - } - - // Declaration: - clazz.classFields << QStringLiteral("QScxmlTransition ") + tName + QLatin1Char(';'); - - // Initializer: - QString initializer = tName + QStringLiteral("("); - QStringList elements; - foreach (const QString &event, node->events) - elements.append(qba(event)); - initializer += createList(QStringLiteral("QString"), elements); - initializer += QStringLiteral(")"); - clazz.constructor.initializer << initializer; - - // init: - if (node->condition) { - QString condExpr = *node->condition.data(); - auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), condExpr); - clazz.init.impl << tName + QStringLiteral(".setConditionalExpression(%1);").arg(cond); - } - - if (m_parents.last()->asHistoryState()) { - clazz.init.impl << QStringLiteral("%1.setDefaultTransition(&%2);").arg(parentStateMemberName(), tName); - } else { - clazz.init.impl << QStringLiteral("%1.addTransitionTo(&%2);").arg(tName, parentStateMemberName()); - } - - if (node->type == Transition::Internal) { - clazz.init.impl << tName + QStringLiteral(".setTransitionType(QAbstractTransition::InternalTransition);"); - } - QStringList targetNames; - foreach (DocumentModel::AbstractState *s, node->targetStates) - targetNames.append(QStringLiteral("&") + mangledName(s, StateName)); - QString targets = tName + QStringLiteral(".setTargetStates(") + createList(QStringLiteral("QAbstractState*"), targetNames); - clazz.init.impl << targets + QStringLiteral(");"); - - // visit the kids: - if (!node->instructionsOnTransition.isEmpty()) { - m_parents.append(node); - m_currentTransitionName = tName; - clazz.init.impl << tName + QStringLiteral(".setInstructionsOnTransition(%1);").arg(startNewSequence()); - visit(&node->instructionsOnTransition); - endSequence(); - m_parents.removeLast(); - m_currentTransitionName.clear(); - } - return false; - } - - bool visit(DocumentModel::HistoryState *node) Q_DECL_OVERRIDE - { - // Includes: - clazz.implIncludes << "QScxmlHistoryState"; - - const QString stateName = mangledName(node, StateName); - // Declaration: - clazz.classFields << QStringLiteral("QScxmlHistoryState ") + stateName + QLatin1Char(';'); - - // Initializer: - clazz.constructor.initializer << generateInitializer(node); +QString createVector(const QString &elementType, const QStringList &elements, bool useCxx11) +{ return createContainer(QStringLiteral("QVector"), elementType, elements, useCxx11); } - // init: - if (!node->id.isEmpty()) { - clazz.init.impl << stateName + QStringLiteral(".setObjectName(string(%1));").arg(addString(node->id)); - } - QString depth; - switch (node->type) { - case DocumentModel::HistoryState::Shallow: - depth = QStringLiteral("Shallow"); - break; - case DocumentModel::HistoryState::Deep: - depth = QStringLiteral("Deep"); +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; - default: - Q_UNREACHABLE(); - } - clazz.init.impl << stateName + QStringLiteral(".setHistoryType(QScxmlHistoryState::") + depth + QStringLiteral("History);"); - - // visit the kid: - if (Transition *t = node->defaultConfiguration()) { - - m_parents.append(node); - t->accept(this); - m_parents.removeLast(); - } - return false; - } - - bool visit(Send *node) Q_DECL_OVERRIDE - { - if (m_qtMode && node->type == QStringLiteral("qt:signal")) { - if (!m_signals.contains(node->event)) { - m_signals.insert(node->event); - m_signalNames.append(node->event); - clazz.signalMethods << QStringLiteral("void %1(const QVariant &data);").arg(node->event); - } - } - return QScxmlExecutableContent::Builder::visit(node); - } - -private: - enum NameForm { - PlainName, - SignalName, - MachineName, - StateName - }; + if (i != 0) + line += QLatin1Char(','); - QString mangledName(const QString &id, NameForm form) const - { - QString name = id; - switch (form) { - case PlainName: break; - case SignalName: name.append(QStringLiteral("Changed")); break; - case StateName: name.prepend(QStringLiteral("state_")); break; - case MachineName: name.prepend(QStringLiteral("machine_")); break; + if (line.length() + nr.length() + 1 > maxLineLength) { + out += line + QLatin1Char('\n'); + line.clear(); + } else if (i != 0) { + line += QLatin1Char(' '); } - - return name.isEmpty() ? name : mangleIdentifier(name); - } - - QString mangledName(AbstractState *state, NameForm form) const - { - Q_ASSERT(state); - - QString name = m_mangledNames.value(state)[form]; - if (!name.isEmpty()) - return name; - - QString id = state->id; - if (State *s = state->asState()) { - if (s->type == State::Initial) { - id = s->parent->asState()->id + QStringLiteral("_initial"); - } - } - - name = mangledName(id, form); - m_mangledNames[state][form] = name; - return name; - } - - QString transitionName(Transition *t) const - { - int idx = 0; - QString parentName; - auto parent = m_parents.last(); - if (State *parentState = parent->asState()) { - parentName = mangledName(parentState, PlainName); - idx = childIndex(t, parentState->children); - } else if (HistoryState *historyState = parent->asHistoryState()) { - parentName = mangledName(historyState, PlainName); - } else if (Scxml *scxml = parent->asScxml()) { - parentName = QStringLiteral("stateMachine"); - idx = childIndex(t, scxml->children); - } else { - Q_UNREACHABLE(); - } - return QStringLiteral("transition_%1_%2").arg(parentName, QString::number(idx)); + line += nr; } + if (!line.isEmpty()) + out += line; +} - static int childIndex(StateOrTransition *child, const QVector<StateOrTransition *> &children) { - int idx = 0; - foreach (StateOrTransition *sot, children) { - if (sot == child) - break; +void generateTables(const GeneratedTableData &td, const QStringList &outgoingEvents, + Replacements &replacements, bool useCxx11) +{ + { // 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 - ++idx; - } - return idx; - } - - QString createList(const QString &elementType, const QStringList &elements) const - { return createContainer(QStringLiteral("QList"), elementType, elements); } - - QString createVector(const QString &elementType, const QStringList &elements) const - { return createContainer(QStringLiteral("QVector"), elementType, elements); } - - QString createContainer(const QString &baseType, const QString &elementType, const QStringList &elements) const - { - QString result; - if (translationUnit->useCxx11) { - if (elements.isEmpty()) { - result += QStringLiteral("{}"); - } else { - result += QStringLiteral("{ ") + elements.join(QStringLiteral(", ")) + QStringLiteral(" }"); - } - } else { - result += QStringLiteral("%1<%2>()").arg(baseType, elementType); - if (!elements.isEmpty()) { - result += QStringLiteral(" << ") + elements.join(QStringLiteral(" << ")); - } - } - return result; - } - - QString generateInitializer(AbstractState *node) const - { - QString init = mangledName(node, StateName) + QStringLiteral("("); - if (State *parentState = node->parent->asState()) { - init += QStringLiteral("&") + mangledName(parentState, StateName); - } else { - init += QStringLiteral("&stateMachine"); - } - init += QLatin1Char(')'); - return init; - } - - void addSubStateMachineProperties(ScxmlDocument *doc) - { - foreach (ScxmlDocument *subDocs, doc->allSubDocuments) { - QString name = subDocs->root->name; - if (name.isEmpty()) - continue; - auto plainName = mangledName(name, PlainName); - auto qualifiedName = namespacePrefix + plainName; - if (m_serviceProps.contains(qMakePair(plainName, qualifiedName))) - continue; - m_serviceProps.append(qMakePair(name, qualifiedName)); - clazz.classFields << QStringLiteral("%1 *%2;").arg(qualifiedName, plainName); - clazz.constructor.initializer << QStringLiteral("%1(Q_NULLPTR)").arg(plainName); - if (isValidQPropertyName(name)) { - clazz.properties << QStringLiteral("Q_PROPERTY(%1%2 *%3 READ %2 NOTIFY %4)") - .arg(namespacePrefix, plainName, name, - mangledName(name, SignalName)); - } - if (m_qtMode) { - Method getter(QStringLiteral("%1 *%2() const").arg(qualifiedName, plainName)); - getter.impl << QStringLiteral("%1 *%2::%3() const").arg(qualifiedName, - clazz.className, plainName) - << QStringLiteral("{ return data->%1; }").arg(plainName); - clazz.publicMethods << getter; - clazz.signalMethods << QStringLiteral("void %1(%2 *statemachine);") - .arg(mangledName(name, SignalName), qualifiedName); + 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))); } - - clazz.dataMethods << QStringLiteral("%1 *%2() const") - .arg(qualifiedName, mangledName(name, MachineName)) - << QStringLiteral("{ return %1; }").arg(plainName) - << QString(); + 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 addEvents() { - QStringList knownEventsList = m_knownEvents.toList(); - std::sort(knownEventsList.begin(), knownEventsList.end()); - if (m_qtMode) { - foreach (const QString &event, knownEventsList) { - clazz.publicSlotDeclarations << QStringLiteral("void ") + event + QStringLiteral("(const QVariant &eventData = QVariant());"); - clazz.publicSlotDefinitions << QStringLiteral("void ") + clazz.className - + QStringLiteral("::") - + event - + QStringLiteral("(const QVariant &eventData)\n{ submitEvent(data->") + qba(event) - + QStringLiteral(", eventData); }"); - } - } - - if (!m_signalNames.isEmpty()) { - clazz.needsEventFilter = true; - clazz.init.impl << QStringLiteral("stateMachine.setScxmlEventFilter(this);"); - auto &dm = clazz.dataMethods; - dm << QStringLiteral("bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) Q_DECL_OVERRIDE {"); - if (m_qtMode) { - dm << QStringLiteral(" if (event->originType() != QStringLiteral(\"qt:signal\")) { return true; }") - << QStringLiteral(" %1 *m = static_cast<%1 *>(stateMachine);").arg(clazz.className); - foreach (const QString &signalName, m_signalNames) { - dm << QStringLiteral(" if (event->name() == %1) { emit m->%2(event->data()); return false; }") - .arg(qba(signalName), mangleIdentifier(signalName)); - } - } - dm << QStringLiteral(" return true;") - << QStringLiteral("}") - << QString(); + QStringList items; + foreach (const QString &event, outgoingEvents) { + items += QStringLiteral("QString({static_cast<QStringData*>(strings.data + %1)})") + .arg(td.theStrings.indexOf(event)); } + replacements[QStringLiteral("outgoingEvents")] = createContainer( + QStringLiteral("std::vector"), QStringLiteral("QString"), items, useCxx11); } +} - QString createContextString(const QString &instrName) const Q_DECL_OVERRIDE +void generateCppDataModelEvaluators(const GeneratedTableData::DataModelInfo &info, + Replacements &replacements) +{ { - if (!m_currentTransitionName.isEmpty()) { - QString state = parentStateName(); - return QStringLiteral("<%1> instruction in transition of state '%2'").arg(instrName, state); - } else { - return QStringLiteral("<%1> instruction in state '%2'").arg(instrName, parentStateName()); + QString evals; + for (auto it = info.stringEvaluators.constBegin(), eit = info.stringEvaluators.constEnd(); + it != eit; ++it) { + evals += QStringLiteral(" case %1:\n").arg(it.key()); + evals += QStringLiteral(" return [this]()->QString{ return %1; }();\n") + .arg(it.value()); } + replacements[QStringLiteral("evaluateToStringCases")] = evals; } - QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const Q_DECL_OVERRIDE - { - QString location = createContextString(instrName); - return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); - } - - QString parentStateName() const { - for (int i = m_parents.size() - 1; i >= 0; --i) { - Node *node = m_parents.at(i); - if (State *s = node->asState()) - return s->id; - else if (HistoryState *h = node->asHistoryState()) - return h->id; - else if (Scxml *l = node->asScxml()) - return l->name; + QString evals; + for (auto it = info.boolEvaluators.constBegin(), eit = info.boolEvaluators.constEnd(); + it != eit; ++it) { + evals += QStringLiteral(" case %1:\n").arg(it.key()); + evals += QStringLiteral(" return [this]()->bool{ return %1; }();\n") + .arg(it.value()); } - - return QString(); + replacements[QStringLiteral("evaluateToBoolCases")] = evals; } - QString parentStateMemberName() const { - Node *parent = m_parents.last(); - if (State *s = parent->asState()) - return mangledName(s, StateName); - else if (HistoryState *h = parent->asHistoryState()) - return mangledName(h, StateName); - else if (parent->asScxml()) - return QStringLiteral("stateMachine"); - else - Q_UNIMPLEMENTED(); - return QString(); - } - - static void generateList(StringListDumper &t, std::function<QString(int)> next) - { - const int maxLineLength = 80; - QString line; - for (int i = 0; ; ++i) { - QString nr = next(i); - if (nr.isNull()) - break; - - if (i != 0) - line += QLatin1Char(','); - - if (line.length() + nr.length() + 1 > maxLineLength) { - t << line; - line.clear(); - } else if (i != 0) { - line += QLatin1Char(' '); - } - line += nr; + QString evals; + for (auto it = info.variantEvaluators.constBegin(), eit = info.variantEvaluators.constEnd(); + it != eit; ++it) { + evals += QStringLiteral(" case %1:\n").arg(it.key()); + evals += QStringLiteral(" return [this]()->QVariant{ return %1; }();\n") + .arg(it.value()); } - if (!line.isEmpty()) - t << line; + replacements[QStringLiteral("evaluateToVariantCases")] = evals; } - void generateTables() { - StringListDumper &t = clazz.tables; - clazz.classFields << QString(); - QScopedPointer<QScxmlExecutableContent::DynamicTableData> td(tableData()); - - { // instructions - clazz.classFields << QStringLiteral("static qint32 theInstructions[];"); - t << QStringLiteral("qint32 %1::Data::theInstructions[] = {").arg(clazz.className); - auto instr = td->instructionTable(); - generateList(t, [&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(); - }); - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::Instructions instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL") - << QStringLiteral("{ return theInstructions; }") - << QString(); - } - - { // dataIds - int count; - auto dataIds = td->dataNames(&count); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::StringId dataIds[];"); - t << QStringLiteral("QScxmlExecutableContent::StringId %1::Data::dataIds[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("-1"); - } else { - generateList(t, [&dataIds, count](int idx) -> QString { - if (count == 0 && idx == 0) // prevent generation of empty array - return QStringLiteral("-1"); - if (idx < count) - return QString::number(dataIds[idx]); - else - return QString(); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ *count = %1; return dataIds; }").arg(count); - clazz.dataMethods << QString(); - } - - { // evaluators - auto evaluators = td->evaluators(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::EvaluatorInfo evaluators[];"); - t << QStringLiteral("QScxmlExecutableContent::EvaluatorInfo %1::Data::evaluators[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("{ -1, -1 }"); - } else { - generateList(t, [&evaluators](int idx) -> QString { - if (evaluators.isEmpty() && idx == 0) // prevent generation of empty array - return QStringLiteral("{ -1, -1 }"); - if (idx >= evaluators.size()) - return QString(); - - auto eval = evaluators.at(idx); - return QStringLiteral("{ %1, %2 }").arg(eval.expr).arg(eval.context); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::EvaluatorInfo evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(evaluatorId >= 0); Q_ASSERT(evaluatorId < %1); return evaluators[evaluatorId]; }").arg(evaluators.size()); - clazz.dataMethods << QString(); - - if (isCppDataModel()) { - { - StringListDumper stringEvals; - stringEvals << QStringLiteral("QString %1::evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = stringEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - stringEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->QString{ return %1; }();").arg(it.value()); - } - stringEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return QString();") - << QStringLiteral(" }"); - stringEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(stringEvals)); - } - - { - StringListDumper boolEvals; - boolEvals << QStringLiteral("bool %1::evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = boolEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - boolEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->bool{ return %1; }();").arg(it.value()); - } - boolEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return false;") - << QStringLiteral(" }"); - boolEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(boolEvals)); - } - - { - StringListDumper variantEvals; - variantEvals << QStringLiteral("QVariant %1::evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = variantEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - variantEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" return [this]()->QVariant{ return %1; }();").arg(it.value()); - } - variantEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" return QVariant();") - << QStringLiteral(" }"); - variantEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(variantEvals)); - } - - { - StringListDumper voidEvals; - voidEvals << QStringLiteral("void %1::evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok) {").arg(clazz.dataModelClassName) - << QStringLiteral(" *ok = true;") - << QStringLiteral(" switch (id) {"); - auto evals = voidEvaluators(); - for (auto it = evals.constBegin(), eit = evals.constEnd(); it != eit; ++it) { - voidEvals << QStringLiteral(" case %1:").arg(it.key()) - << QStringLiteral(" [this]()->void{ %1 }();").arg(it.value()) - << QStringLiteral(" break;"); - } - voidEvals << QStringLiteral(" default:") - << QStringLiteral(" Q_UNREACHABLE();") - << QStringLiteral(" *ok = false;") - << QStringLiteral(" }"); - voidEvals << QStringLiteral("}"); - clazz.dataModelMethods.append(Method(voidEvals)); - } - } - } - - { // assignments - auto assignments = td->assignments(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::AssignmentInfo assignments[];"); - t << QStringLiteral("QScxmlExecutableContent::AssignmentInfo %1::Data::assignments[] = {").arg(clazz.className); - if (isCppDataModel()) { - t << QStringLiteral("{ -1, -1, -1 }"); - } else { - generateList(t, [&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 ass = assignments.at(idx); - return QStringLiteral("{ %1, %2, %3 }").arg(ass.dest).arg(ass.expr).arg(ass.context); - }); - } - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::AssignmentInfo assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(assignmentId >= 0); Q_ASSERT(assignmentId < %1); return assignments[assignmentId]; }").arg(assignments.size()); - clazz.dataMethods << QString(); - } - - { // foreaches - auto foreaches = td->foreaches(); - clazz.classFields << QStringLiteral("static QScxmlExecutableContent::ForeachInfo foreaches[];"); - t << QStringLiteral("QScxmlExecutableContent::ForeachInfo %1::Data::foreaches[] = {").arg(clazz.className); - generateList(t, [&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 foreach = foreaches.at(idx); - return QStringLiteral("{ %1, %2, %3, %4 }").arg(foreach.array).arg(foreach.item).arg(foreach.index).arg(foreach.context); - }); - t << QStringLiteral("};") << QStringLiteral(""); - clazz.dataMethods << QStringLiteral("QScxmlExecutableContent::ForeachInfo foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{ Q_ASSERT(foreachId >= 0); Q_ASSERT(foreachId < %1); return foreaches[foreachId]; }").arg(foreaches.size()); - } - - { // strings - t << QStringLiteral("#define STR_LIT(idx, ofs, len) \\") - << QStringLiteral(" Q_STATIC_STRING_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \\") - << QStringLiteral(" qptrdiff(offsetof(Strings, stringdata) + ofs * sizeof(qunicodechar) - idx * sizeof(QArrayData)) \\") - << QStringLiteral(" )"); - - t << QStringLiteral("%1::Data::Strings %1::Data::strings = {{").arg(clazz.className); - auto strings = td->stringTable(); - if (strings.isEmpty()) // prevent generation of empty array - strings.append(QStringLiteral("")); - int ucharCount = 0; - generateList(t, [&ucharCount, &strings](int idx) -> QString { - if (idx >= strings.size()) - return QString(); - - int length = strings.at(idx).size(); - QString str = QStringLiteral("STR_LIT(%1, %2, %3)").arg(QString::number(idx), - QString::number(ucharCount), - QString::number(length)); - ucharCount += length + 1; - return str; - }); - t << QStringLiteral("},{"); - for (int i = 0, ei = strings.size(); i < ei; ++i) { - const QString &string = strings.at(i); - QString result; - 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))); - t << result; - } - t << QStringLiteral("}};") << QStringLiteral(""); - - clazz.classFields << QStringLiteral("static struct Strings {") - << QStringLiteral(" QArrayData data[%1];").arg(strings.size()) - << QStringLiteral(" qunicodechar stringdata[%1];").arg(ucharCount + 1) - << QStringLiteral("} strings;"); - - clazz.dataMethods << QStringLiteral("QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL"); - clazz.dataMethods << QStringLiteral("{"); - clazz.dataMethods << QStringLiteral(" Q_ASSERT(id >= QScxmlExecutableContent::NoString); Q_ASSERT(id < %1);").arg(strings.size()); - clazz.dataMethods << QStringLiteral(" if (id == QScxmlExecutableContent::NoString) return QString();"); - if (translationUnit->useCxx11) { - clazz.dataMethods << QStringLiteral(" return QString({static_cast<QStringData*>(strings.data + id)});"); - } else { - clazz.dataMethods << QStringLiteral(" QStringDataPtr data;"); - clazz.dataMethods << QStringLiteral(" data.ptr = static_cast<QStringData*>(strings.data + id);"); - clazz.dataMethods << QStringLiteral(" return QString(data);"); - } - clazz.dataMethods << QStringLiteral("}"); - clazz.dataMethods << QString(); + QString evals; + for (auto it = info.voidEvaluators.constBegin(), eit = info.voidEvaluators.constEnd(); + it != eit; ++it) { + evals += QStringLiteral(" case %1:\n").arg(it.key()); + evals += QStringLiteral(" [this]()->void{ %1 }();\n").arg(it.value()); + evals += QStringLiteral(" break;\n"); } + replacements[QStringLiteral("evaluateToVoidCases")] = evals; } +} - void generateMetaObject() +int createFactoryId(QStringList &factories, const QString &className, + const QString &namespacePrefix, + QScxmlExecutableContent::StringId invokeLocation, + QScxmlExecutableContent::EvaluatorId srcexpr, + QScxmlExecutableContent::StringId id, + QScxmlExecutableContent::StringId idPrefix, + QScxmlExecutableContent::StringId idlocation, + const QVector<QScxmlExecutableContent::StringId> &namelist, + bool autoforward, + const QVector<QScxmlExecutableContent::Param> ¶ms, + QScxmlExecutableContent::ContainerId finalize, + bool useCxx11) +{ + const int idx = factories.size(); + + QString line = QStringLiteral("case %1: return new ").arg(QString::number(idx)); + if (srcexpr == QScxmlExecutableContent::NoInstruction) { + line += QStringLiteral("QScxmlInvokeScxmlFactory<%1::%2>(%3, ") + .arg(namespacePrefix, className, QString::number(invokeLocation)); + } else { + line += QStringLiteral("QScxmlDynamicScxmlFactory(%1, %2, ") + .arg(invokeLocation).arg(srcexpr); + } + line += QStringLiteral("%1, ").arg(QString::number(id)); + line += QStringLiteral("%1, ").arg(QString::number(idPrefix)); + line += QStringLiteral("%1, ").arg(QString::number(idlocation)); { - ClassDef classDef; - classDef.classname = clazz.className.toUtf8(); - classDef.qualified = classDef.classname; - classDef.superclassList << qMakePair(QByteArray("QScxmlStateMachine"), FunctionDef::Public); - classDef.hasQObject = true; - - // Event signals: - foreach (const QString &signalName, m_signalNames) { - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = signalName.toUtf8(); - signal.access = FunctionDef::Public; - signal.isSignal = true; - - ArgumentDef arg; - arg.type.name = "const QVariant &"; - arg.type.rawName = arg.type.name; - arg.normalizedType = "QVariant"; - arg.name = "data"; - arg.typeNameForCast = arg.normalizedType + "*"; - signal.arguments << arg; - - classDef.signalList << signal; - } - - // stateNames: - foreach (const QString &stateName, m_stateNames) { - if (stateName.isEmpty()) - continue; - - const QByteArray mangledStateName = mangledName(stateName, StateName).toUtf8(); - const QString mangledSignalName = mangledName(stateName, SignalName); - - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = mangledSignalName.toUtf8(); - signal.access = FunctionDef::Private; - signal.isSignal = true; - if (!m_qtMode) { - signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; - } else { - clazz.signalMethods << QStringLiteral("void %1(bool active);") - .arg(mangledSignalName); - } - 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(); - prop.type = "bool"; - prop.read = "data->" + mangledStateName + ".active"; - prop.notify = mangledSignalName.toUtf8(); - prop.notifyId = classDef.signalList.size() - 1; - prop.gspec = PropertyDef::ValueSpec; - prop.scriptable = "true"; - classDef.propertyList << prop; - } - - // event slots: - foreach (const QString &eventName, m_knownEvents) { - FunctionDef slot; - slot.type.name = "void"; - slot.type.rawName = slot.type.name; - slot.normalizedType = slot.type.name; - slot.name = eventName.toUtf8(); - slot.access = FunctionDef::Public; - slot.isSlot = true; - - classDef.slotList << slot; - - ArgumentDef arg; - arg.type.name = "const QVariant &"; - arg.type.rawName = arg.type.name; - arg.normalizedType = "QVariant"; - arg.name = "data"; - arg.typeNameForCast = arg.normalizedType + "*"; - slot.arguments << arg; - - classDef.slotList << slot; - } - - // sub-statemachines: - QHash<QByteArray, QByteArray> knownQObjectClasses; - knownQObjectClasses.insert(QByteArray("QScxmlStateMachine"), QByteArray()); - Method reg(QStringLiteral("void setService(const QString &id, QScxmlInvokableService *service) Q_DECL_OVERRIDE Q_DECL_FINAL")); - reg.impl << QStringLiteral("void %1::setService(const QString &id, QScxmlInvokableService *service) {").arg(clazz.className); - if (m_serviceProps.isEmpty()) { - reg.impl << QStringLiteral(" Q_UNUSED(id);") - << QStringLiteral(" Q_UNUSED(service);"); + QStringList l; + foreach (auto name, namelist) { + l.append(QString::number(name)); } - for (const auto &service : m_serviceProps) { - auto serviceName = service.first; - const QString mangledServiceName = mangledName(serviceName, PlainName); - const QString fqServiceClass = service.second; - const QByteArray serviceClass = fqServiceClass.toUtf8(); - knownQObjectClasses.insert(serviceClass, ""); - - reg.impl << QStringLiteral(" SET_SERVICE_PROP(%1, %2, %3%2, %4)") - .arg(addString(serviceName)) - .arg(mangledServiceName, namespacePrefix).arg(classDef.signalList.size()); - - const QByteArray mangledMachineName = mangledName(serviceName, MachineName).toUtf8(); - const QByteArray mangledSignalName = mangledName(serviceName, SignalName).toUtf8(); - - FunctionDef signal; - signal.type.name = "void"; - signal.type.rawName = signal.type.name; - signal.normalizedType = signal.type.name; - signal.name = mangledSignalName; - signal.access = FunctionDef::Private; - signal.isSignal = true; - if (!m_qtMode) { - signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; - } - ArgumentDef arg; - arg.type.name = serviceClass + " *"; - arg.type.rawName = arg.type.name; - arg.type.referenceType = Type::Pointer; - arg.normalizedType = serviceClass + "*(*)"; - arg.name = "statemachine"; - arg.typeNameForCast = arg.type.name + "*"; - signal.arguments << arg; - classDef.signalList << signal; - - ++classDef.notifyableProperties; - PropertyDef prop; - prop.name = serviceName.toUtf8(); - prop.type = serviceClass + "*"; - prop.read = "data->" + mangledMachineName; - prop.notify = mangledSignalName; - prop.notifyId = classDef.signalList.size() - 1; - prop.gspec = PropertyDef::ValueSpec; - prop.scriptable = "true"; - classDef.propertyList << prop; - } - reg.impl << QStringLiteral("}"); - clazz.protectedMethods.append(reg); - - QBuffer buf(&clazz.metaData); - buf.open(QIODevice::WriteOnly); - Generator(&classDef, QList<QByteArray>(), knownQObjectClasses, - QHash<QByteArray, QByteArray>(), buf).generateCode(); - buf.close(); - } - - QString qba(const QString &bytes) - { - return QStringLiteral("string(%1)").arg(addString(bytes)); + line += QStringLiteral("%1, ").arg( + createVector(QStringLiteral("QScxmlExecutableContent::StringId"), l, useCxx11)); } - - QString scxmlClassName(DocumentModel::ScxmlDocument *doc) + line += QStringLiteral("%1, ").arg(autoforward ? QStringLiteral("true") + : QStringLiteral("false")); { - QString name = mangleIdentifier(translationUnit->classnameForDocument.value(doc)); - Q_ASSERT(!name.isEmpty()); - return namespacePrefix + name; - } - - /*! - * \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 mangleIdentifier(const QString &str) const - { - 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; - } + QStringList l; + foreach (const auto ¶m, params) { + l += QStringLiteral("QScxmlExecutableContent::Param(%1, %2, %3)") + .arg(QString::number(param.name), + QString::number(param.expr), + QString::number(param.location)); } - - for (int ei = str.length(); i != ei; ++i) { - auto c = str.at(i).unicode(); - if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { - mangled += c; - } else { - mangled += QLatin1String("_0x") + QString::number(c, 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; + line += QStringLiteral("%1, ").arg( + createVector(QStringLiteral("QScxmlExecutableContent::Param"), l, + useCxx11)); } + line += QStringLiteral("%1);").arg(finalize); -private: - QString namespacePrefix; - ClassDump &clazz; - TranslationUnit *translationUnit; - mutable QHash<AbstractState *, QHash<NameForm, QString> > m_mangledNames; - mutable QHash<QString, QString> m_mangledToOriginal; - QVector<Node *> m_parents; - QList<QPair<QString, QString>> m_serviceProps; - QSet<QString> m_knownEvents; - QSet<QString> m_signals; - QStringList m_signalNames; - QStringList m_stateNames; - QStringList m_stateFieldNames; - QString m_currentTransitionName; - bool m_bindLate; - bool m_qtMode; - QVector<DocumentModel::DataElement *> m_dataElements; -}; + factories.append(line); + return idx; +} } // anonymous namespace - void CppDumper::dump(TranslationUnit *unit) { Q_ASSERT(unit); @@ -1316,38 +429,83 @@ void CppDumper::dump(TranslationUnit *unit) m_translationUnit = unit; - QStringList classDecls; - QVector<ClassDump> clazzes; + 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->otherDocuments(); - clazzes.resize(docs.size() + 1); - DumperVisitor(clazzes[0], m_translationUnit).process(unit->mainDocument); + docs.prepend(unit->mainDocument); + tables.resize(docs.size()); + metaDataInfos.resize(tables.size()); + dataModelInfos.resize(tables.size()); + factories.resize(tables.size()); + auto classnameForDocument = unit->classnameForDocument; + for (int i = 0, ei = docs.size(); i != ei; ++i) { auto doc = docs.at(i); - ClassDump &clazz = clazzes[i + 1]; - DumperVisitor(clazz, m_translationUnit).process(doc); - classDecls.append(clazz.className); + auto metaDataInfo = &metaDataInfos[i]; + GeneratedTableData::build(doc, &tables[i], metaDataInfo, &dataModelInfos[i], + [this, &factories, i, &classnameForDocument, &namespacePrefix]( + QScxmlExecutableContent::StringId invokeLocation, + QScxmlExecutableContent::EvaluatorId srcexpr, + QScxmlExecutableContent::StringId id, + QScxmlExecutableContent::StringId idPrefix, + QScxmlExecutableContent::StringId idlocation, + const QVector<QScxmlExecutableContent::StringId> &namelist, + bool autoforward, + const QVector<QScxmlExecutableContent::Param> ¶ms, + QScxmlExecutableContent::ContainerId finalize, + const QSharedPointer<DocumentModel::ScxmlDocument> &content) -> int { + QString className; + if (srcexpr == QScxmlExecutableContent::NoInstruction) { + className = mangleIdentifier(classnameForDocument.value(content.data())); + } + return createFactoryId(factories[i], className, namespacePrefix, invokeLocation, + srcexpr, id, idPrefix, idlocation, namelist, autoforward, params, + finalize, m_translationUnit->useCxx11); + }); + classNames.append(mangleIdentifier(classnameForDocument.value(doc))); + std::sort(metaDataInfo->outgoingEvents.begin(), metaDataInfo->outgoingEvents.end()); } - QString headerName = QFileInfo(unit->outHFileName).fileName(); + const QString headerName = QFileInfo(unit->outHFileName).fileName(); const QString headerGuard = headerName.toUpper() .replace(QLatin1Char('.'), QLatin1Char('_')) .replace(QLatin1Char('-'), QLatin1Char('_')); - writeHeaderStart(headerGuard, classDecls); - writeImplStart(clazzes); + QStringList forwardDecls = classNames; + forwardDecls.pop_front(); + 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)); - foreach (const ClassDump &clazz, clazzes) { - writeClass(clazz); - writeImplBody(clazz); + 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); + } } - classDecls.append(clazzes.at(0).className); - writeHeaderEnd(headerGuard, classDecls); + 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)) + h << doNotEditComment.arg(m_translationUnit->scxmlFileName, + QString::number(Q_QSCXMLC_OUTPUT_REVISION), + QString::fromLatin1(QT_VERSION_STR)) << endl; h << QStringLiteral("#ifndef ") << headerGuard << endl @@ -1358,59 +516,22 @@ void CppDumper::writeHeaderStart(const QString &headerGuard, const QStringList & h << l("namespace ") << m_translationUnit->namespaceName << l(" {") << endl << endl; if (!forwardDecls.isEmpty()) { - foreach (const QString &name, forwardDecls) { - h << QStringLiteral("class %1;").arg(name) << endl; + for (int i = 1, ei = forwardDecls.size(); i != ei; ++i) { + h << QStringLiteral("class %1;").arg(forwardDecls.at(i)) << endl; } h << endl; } } -void CppDumper::writeClass(const ClassDump &clazz) +void CppDumper::writeClass(const QString &className, const GeneratedTableData::MetaDataInfo &info) { - h << l("class ") << clazz.className << QStringLiteral(": public QScxmlStateMachine\n{") << endl; - h << QStringLiteral("public:") << endl - << QStringLiteral(" /* qmake ignore Q_OBJECT */") << endl - << QStringLiteral(" Q_OBJECT") << endl; - clazz.properties.write(h, QStringLiteral(" "), QStringLiteral("\n")); - - h << endl - << QStringLiteral("public:") << endl; - h << l(" ") << clazz.className << l("(QObject *parent = 0);") << endl; - h << l(" ~") << clazz.className << "();" << endl; - - if (!clazz.publicMethods.isEmpty()) { - h << endl; - foreach (const Method &m, clazz.publicMethods) { - h << QStringLiteral(" ") << m.decl << QLatin1Char(';') << endl; - } - } - - if (!clazz.protectedMethods.isEmpty()) { - h << endl - << QStringLiteral("protected:") << endl; - foreach (const Method &m, clazz.protectedMethods) { - h << QStringLiteral(" ") << m.decl << QLatin1Char(';') << endl; - } - } - - if (!clazz.signalMethods.isEmpty()) { - h << endl - << QStringLiteral("signals:") << endl; - clazz.signalMethods.write(h, QStringLiteral(" "), QStringLiteral("\n")); - } - - if (!clazz.publicSlotDeclarations.isEmpty()) { - h << endl - << QStringLiteral("public slots:") << endl; - clazz.publicSlotDeclarations.write(h, QStringLiteral(" "), QStringLiteral("\n")); - } - - h << endl - << l("private:") << endl - << l(" struct Data;") << endl - << l(" friend struct Data;") << endl - << l(" struct Data *data;") << endl - << l("};") << endl << endl; + const bool qtMode = m_translationUnit->mainDocument->qtMode; + Replacements r; + r[QStringLiteral("classname")] = className; + r[QStringLiteral("signals")] = qtMode ? generateSignalDecls(info) : QString(); + r[QStringLiteral("slots")] = qtMode ? generateSlotDecls(info) : QString(); + r[QStringLiteral("getters")] = qtMode ? generateGetterDecls(info) : QString(); + genTemplate(h, QStringLiteral(":/decl.t"), r); } void CppDumper::writeHeaderEnd(const QString &headerGuard, const QStringList &metatypeDecls) @@ -1430,121 +551,423 @@ void CppDumper::writeHeaderEnd(const QString &headerGuard, const QStringList &me h << QStringLiteral("#endif // ") << headerGuard << endl; } -void CppDumper::writeImplStart(const QVector<ClassDump> &allClazzes) +void CppDumper::writeImplStart() { - cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName, QString::number(Q_QSCXMLC_OUTPUT_REVISION), QString::fromLatin1(QT_VERSION_STR)) + cpp << doNotEditComment.arg(m_translationUnit->scxmlFileName, + QString::number(Q_QSCXMLC_OUTPUT_REVISION), + l(QT_VERSION_STR)) << endl; - StringListDumper includes; - foreach (const ClassDump &clazz, allClazzes) { - includes.text += clazz.implIncludes.text; + QStringList includes; + foreach (DocumentModel::ScxmlDocument *doc, m_translationUnit->classnameForDocument.keys()) { + 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.unique(); + includes.sort(); + includes.removeDuplicates(); QString headerName = QFileInfo(m_translationUnit->outHFileName).fileName(); cpp << l("#include \"") << headerName << l("\"") << endl; cpp << endl - << QStringLiteral("#include <qscxmlqstates.h>") << endl + << QStringLiteral("#include <qscxmlinvokableservice.h>") << endl << QStringLiteral("#include <qscxmltabledata.h>") << endl; - if (!includes.isEmpty()) { - includes.write(cpp, QStringLiteral("#include <"), QStringLiteral(">\n")); - cpp << endl; + foreach (const QString &inc, 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)) + << 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 ClassDump &clazz) +void CppDumper::writeImplBody(const GeneratedTableData &table, + const QString &className, + DocumentModel::ScxmlDocument *doc, + const QStringList &factory, + const GeneratedTableData::MetaDataInfo &info) +{ + const bool qtMode = m_translationUnit->mainDocument->qtMode; + + 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 = factory.join(QStringLiteral("\n ")) + + QStringLiteral("\n default: Q_UNREACHABLE();"); + + Replacements r; + r[QStringLiteral("classname")] = className; + r[QStringLiteral("name")] = name; + r[QStringLiteral("initialSetup")] = QString::number(table.initialSetup()); + generateTables(table, info.outgoingEvents, r, m_translationUnit->useCxx11); + r[QStringLiteral("dataModelField")] = dataModelField; + r[QStringLiteral("dataModelInitialization")] = dataModelInitialization; + r[QStringLiteral("theStateMachineTable")] = + GeneratedTableData::toString(table.stateMachineTable()); + r[QStringLiteral("metaObject")] = generateMetaObject(className, info, qtMode); + r[QStringLiteral("serviceFactories")] = serviceFactories; + r[QStringLiteral("slots")] = qtMode ? generateSlotDefs(className, info) : QString(); + r[QStringLiteral("getters")] = qtMode ? generateGetterDefs(className, info) : QString(); + genTemplate(cpp, QStringLiteral(":/data.t"), r); +} + +void CppDumper::writeImplEnd() { - cpp << l("struct ") << clazz.className << l("::Data: private QScxmlTableData"); - if (clazz.needsEventFilter) { - cpp << QStringLiteral(", public QScxmlEventFilter"); + if (!m_translationUnit->namespaceName.isEmpty()) { + cpp << endl + << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << endl; } - cpp << l(" {") << endl; - - cpp << QStringLiteral(" Data(%1 &stateMachine)").arg(clazz.className) << endl - << QStringLiteral(" : stateMachine(stateMachine)") << endl; - clazz.constructor.initializer.write(cpp, QStringLiteral(" , "), QStringLiteral("\n")); - cpp << l(" {") << endl; - clazz.constructor.impl.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); - cpp << l(" }") << endl; - - cpp << endl; - cpp << l(" void init() {\n"); - clazz.init.impl.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); - cpp << l(" }") << endl; - cpp << endl; - clazz.dataMethods.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); +} - cpp << endl - << QStringLiteral(" %1 &stateMachine;").arg(clazz.className) << endl; - clazz.classFields.write(cpp, QStringLiteral(" "), QStringLiteral("\n")); +/*! + * \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('_'); + }; - cpp << l("};") << endl - << endl; - clazz.classMethods.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + Q_ASSERT(!str.isEmpty()); - cpp << clazz.className << l("::") << clazz.className << l("(QObject *parent)") << endl - << QStringLiteral(" : QScxmlStateMachine(parent)") << endl - << QStringLiteral(" , data(new Data(*this))") << endl - << QStringLiteral("{ qRegisterMetaType<%1 *>(); data->init(); }").arg(clazz.className) << endl - << endl; - cpp << clazz.className << l("::~") << clazz.className << l("()") << endl - << l("{ delete data; }") << endl - << endl; - foreach (const Method &m, clazz.publicMethods) { - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n"), clazz.className); - cpp << endl; - } - if (!clazz.protectedMethods.isEmpty()) { - cpp << "#define SET_SERVICE_PROP(s, n, fq, sig) \\\n" - " if (id == data->string(s)) { \\\n" - " QScxmlInvokableScxml *machine = service ? dynamic_cast<QScxmlInvokableScxml *>(service) : Q_NULLPTR; \\\n" - " fq *casted = machine ? dynamic_cast<fq*>(machine->stateMachine()) : Q_NULLPTR; \\\n" - " if (data->n != casted) { \\\n" - " data->n = casted; \\\n" - " void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&casted)) }; \\\n" - " QMetaObject::activate(this, &staticMetaObject, sig, _a); \\\n" - " } \\\n" - " return; \\\n" - " }\n" - << endl; - foreach (const Method &m, clazz.protectedMethods) { - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n"), clazz.className); - cpp << endl; + 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; } - cpp << QStringLiteral("#undef SET_SERVICE_PROP") << endl - << endl; } - clazz.publicSlotDefinitions.write(cpp, QStringLiteral("\n"), QStringLiteral("\n")); - cpp << endl; - clazz.tables.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + for (int ei = str.length(); i != ei; ++i) { + auto c = str.at(i).unicode(); + if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || isNonDigit(c)) { + mangled += c; + } else { + mangled += QLatin1String("_0x") + QString::number(c, 16) + QLatin1Char('_'); + } + } - if (!clazz.dataModelMethods.isEmpty()) { - bool first = true; - foreach (const Method &m, clazz.dataModelMethods) { - if (first) { - first = false; - } else { - cpp << endl; - } - m.impl.write(cpp, QStringLiteral(""), QStringLiteral("\n")); + 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 } - cpp << endl << clazz.metaData; + return mangled; } -void CppDumper::writeImplEnd() +QString CppDumper::generateSignalDecls(const GeneratedTableData::MetaDataInfo &info) { - if (!m_translationUnit->namespaceName.isEmpty()) { - cpp << endl - << QStringLiteral("} // %1 namespace").arg(m_translationUnit->namespaceName) << endl; + QString decls; + + foreach (const QString &stateName, info.stateNames) { + decls += QStringLiteral(" void %1Changed(bool active);\n") + .arg(mangleIdentifier(stateName)); + } + + foreach (const QString &machineName, info.subStateMachineNames) { + decls += QStringLiteral(" void %1Changed(QScxmlStateMachine *stateMachine);\n") + .arg(mangleIdentifier(machineName)); + } + + foreach (const QString &eventName, info.outgoingEvents) { + decls += QStringLiteral(" void %1(const QVariant &data);\n") + .arg(mangleIdentifier(eventName)); + } + + return decls; +} + +QString CppDumper::generateSlotDecls(const GeneratedTableData::MetaDataInfo &info) +{ + QString decls; + + foreach (const QString &eventName, info.incomingEvents) { + if (!DocumentModel::isEventToBeGenerated(eventName)) + continue; + + decls += QStringLiteral(" void %1(const QVariant &data = QVariant());\n") + .arg(mangleIdentifier(eventName)); + } + + return decls; +} + +QString CppDumper::generateSlotDefs(const QString &className, + const GeneratedTableData::MetaDataInfo &info) +{ + QString defs; + + foreach (const QString &eventName, info.incomingEvents) { + if (!DocumentModel::isEventToBeGenerated(eventName)) + continue; + + const auto mangledName = mangleIdentifier(eventName); + defs += QStringLiteral("void %1::%2(const QVariant &data)\n").arg(className, mangledName); + defs += QStringLiteral("{ submitEvent(QStringLiteral(\"%1\"), data); }\n\n").arg(eventName); } + + return defs; +} + +QString CppDumper::generateGetterDecls(const GeneratedTableData::MetaDataInfo &info) +{ + QString decls; + + foreach (const QString &stateName, info.stateNames) { + decls += QStringLiteral(" bool %1() const;\n").arg(mangleIdentifier(stateName)); + } + + foreach (const QString &machineName, info.subStateMachineNames) { + decls += QStringLiteral(" QScxmlStateMachine *%1() const;\n") + .arg(mangleIdentifier(machineName)); + } + + return decls; +} + +QString CppDumper::generateGetterDefs(const QString &className, + const GeneratedTableData::MetaDataInfo &info) +{ + QString defs; + + int stateIndex = 0; + foreach (const QString &stateName, info.stateNames) { + defs += QStringLiteral("bool %1::%2() const\n").arg(className, mangleIdentifier(stateName)); + defs += QStringLiteral("{ return isActive(%1); }\n\n").arg(stateIndex); + ++stateIndex; + } + + int machineIndex = 0; + foreach (const QString &machineName, info.subStateMachineNames) { + defs += QStringLiteral("QScxmlStateMachine *%1::%2() const\n") + .arg(className, mangleIdentifier(machineName)); + defs += QStringLiteral("{ return subStateMachine(%1); }\n\n").arg(machineIndex); + ++machineIndex; + } + + return defs; +} + +QString CppDumper::generateMetaObject(const QString &className, + const GeneratedTableData::MetaDataInfo &info, + bool m_qtMode) +{ + ClassDef classDef; + classDef.classname = className.toUtf8(); + classDef.qualified = classDef.classname; + classDef.superclassList << qMakePair(QByteArray("QScxmlStateMachine"), FunctionDef::Public); + classDef.hasQObject = true; + + // stateNames: + int stateIdx = 0; + foreach (const QString &stateName, info.stateNames) { + if (stateName.isEmpty()) + continue; + + QByteArray mangledStateName = stateName.toUtf8(); + + FunctionDef signal; + signal.type.name = "void"; + signal.type.rawName = signal.type.name; + signal.normalizedType = signal.type.name; + signal.name = mangledStateName + "Changed"; + signal.access = FunctionDef::Private; + signal.isSignal = true; + if (!m_qtMode) { + signal.implementation = "QMetaObject::activate(_o, &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(); + prop.type = "bool"; + prop.read = "isActive(" + QByteArray::number(stateIdx++) + ")"; + prop.notify = mangledStateName + "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()); + int machineIdx = 0; + for (const auto &service : info.subStateMachineNames) { + const auto serviceName = service; + const QString fqServiceClass = QStringLiteral("QScxmlStateMachine"); + const QByteArray serviceClass = fqServiceClass.toUtf8(); + knownQObjectClasses.insert(serviceClass, ""); + + const QByteArray mangledServiceName = mangleIdentifier(serviceName).toUtf8(); + + FunctionDef signal; + signal.type.name = "void"; + signal.type.rawName = signal.type.name; + signal.normalizedType = signal.type.name; + signal.name = mangledServiceName + "Changed"; + signal.access = FunctionDef::Private; + signal.isSignal = true; + if (!m_qtMode) { + signal.implementation = "QMetaObject::activate(_o, &staticMetaObject, %d, _a);"; + } + ArgumentDef arg; + arg.type.name = serviceClass + " *"; + arg.type.rawName = arg.type.name; + arg.type.referenceType = Type::Pointer; + arg.normalizedType = serviceClass + "*(*)"; + arg.name = "statemachine"; + arg.typeNameForCast = arg.type.name + "*"; + signal.arguments << arg; + classDef.signalList << signal; + + ++classDef.notifyableProperties; + PropertyDef prop; + prop.name = serviceName.toUtf8(); + prop.type = serviceClass + "*"; + prop.read = "subStateMachine(" + QByteArray::number(machineIdx++) + ")"; + prop.notify = mangledServiceName + "Changed"; + prop.notifyId = classDef.signalList.size() - 1; + prop.gspec = PropertyDef::ValueSpec; + prop.scriptable = "true"; + classDef.propertyList << prop; + } + + // Event signals: + foreach (const QString &signalName, info.outgoingEvents) { + FunctionDef signal; + signal.type.name = "void"; + signal.type.rawName = signal.type.name; + signal.normalizedType = signal.type.name; + signal.name = signalName.toUtf8(); + signal.access = FunctionDef::Public; + signal.isSignal = true; + + ArgumentDef arg; + arg.type.name = "const QVariant &"; + arg.type.rawName = arg.type.name; + arg.normalizedType = "QVariant"; + arg.name = "data"; + arg.typeNameForCast = arg.normalizedType + "*"; + signal.arguments << arg; + + classDef.signalList << signal; + } + + // event slots: + foreach (const QString &eventName, info.incomingEvents) { + if (!DocumentModel::isEventToBeGenerated(eventName)) + continue; + + FunctionDef slot; + slot.type.name = "void"; + slot.type.rawName = slot.type.name; + slot.normalizedType = slot.type.name; + slot.name = eventName.toUtf8(); + slot.access = FunctionDef::Public; + slot.isSlot = true; + + ArgumentDef arg; + arg.type.name = "const QVariant &"; + arg.type.rawName = arg.type.name; + arg.normalizedType = "QVariant"; + arg.name = "data"; + arg.typeNameForCast = arg.normalizedType + "*"; + slot.arguments << arg; + + classDef.slotList << slot; + } + + foreach (const QString &eventName, info.incomingEvents) { + if (!DocumentModel::isEventToBeGenerated(eventName)) + continue; + + FunctionDef slot; + slot.type.name = "void"; + slot.type.rawName = slot.type.name; + slot.normalizedType = slot.type.name; + slot.name = eventName.toUtf8(); + slot.access = FunctionDef::Public; + slot.isSlot = true; + + classDef.slotList << slot; + } + + QBuffer buf; + buf.open(QIODevice::WriteOnly); + Generator(&classDef, QList<QByteArray>(), knownQObjectClasses, + QHash<QByteArray, QByteArray>(), buf).generateCode(); + buf.close(); + return QString::fromUtf8(buf.buffer()); } QT_END_NAMESPACE diff --git a/tools/qscxmlc/scxmlcppdumper.h b/tools/qscxmlc/scxmlcppdumper.h index f02bf2f..97011d0 100644 --- a/tools/qscxmlc/scxmlcppdumper.h +++ b/tools/qscxmlc/scxmlcppdumper.h @@ -32,13 +32,12 @@ #include "qscxmlglobals.h" #include <QtScxml/private/qscxmlparser_p.h> +#include <QtScxml/private/qscxmltabledata_p.h> #include <QTextStream> QT_BEGIN_NAMESPACE -struct ClassDump; - struct TranslationUnit { TranslationUnit() @@ -74,13 +73,30 @@ public: private: void writeHeaderStart(const QString &headerGuard, const QStringList &forwardDecls); - void writeClass(const ClassDump &clazz); + void writeClass(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); void writeHeaderEnd(const QString &headerGuard, const QStringList &metatypeDecls); - void writeImplStart(const QVector<ClassDump> &allClazzes); - void writeImplBody(const ClassDump &clazz); + void writeImplStart(); + void writeImplBody(const QScxmlInternal::GeneratedTableData &table, + const QString &className, + DocumentModel::ScxmlDocument *doc, + const QStringList &factory, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); void writeImplEnd(); + QString mangleIdentifier(const QString &str); private: + QString generateSignalDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateSlotDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateSlotDefs(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateGetterDecls(const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateGetterDefs(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info); + QString generateMetaObject(const QString &className, + const QScxmlInternal::GeneratedTableData::MetaDataInfo &info, + bool m_qtMode); + QTextStream &h; QTextStream &cpp; @@ -88,6 +104,8 @@ private: static QLatin1String l (const char *str) { return QLatin1String(str); } TranslationUnit *m_translationUnit; + + mutable QHash<QString, QString> m_mangledToOriginal; }; QT_END_NAMESPACE diff --git a/tools/qscxmlc/templates.qrc b/tools/qscxmlc/templates.qrc new file mode 100644 index 0000000..6f2ccf6 --- /dev/null +++ b/tools/qscxmlc/templates.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>data.t</file> + <file>decl.t</file> + <file>cppdatamodel.t</file> + </qresource> +</RCC> |