diff options
30 files changed, 3805 insertions, 3900 deletions
diff --git a/src/scxml/qscxmlecmascriptdatamodel.cpp b/src/scxml/qscxmlecmascriptdatamodel.cpp index ceba661..452d781 100644 --- a/src/scxml/qscxmlecmascriptdatamodel.cpp +++ b/src/scxml/qscxmlecmascriptdatamodel.cpp @@ -225,9 +225,6 @@ public: return q->tableData()->string(id); } - QScxmlStateMachine::BindingMethod dataBinding() const - { return stateMachine()->dataBinding(); } - bool hasProperty(const QString &name) const { return dataModel.hasProperty(name); } diff --git a/src/scxml/qscxmlexecutablecontent.cpp b/src/scxml/qscxmlexecutablecontent.cpp index a11b4ca..84b508e 100644 --- a/src/scxml/qscxmlexecutablecontent.cpp +++ b/src/scxml/qscxmlexecutablecontent.cpp @@ -297,8 +297,10 @@ bool QScxmlExecutionEngine::step(Instructions &ip) QString eventName = QStringLiteral("done.state.") + extraData.toString(); QScxmlEventBuilder event(stateMachine, eventName, doneData); + auto e = event(); + e->setEventType(QScxmlEvent::InternalEvent); qCDebug(qscxmlLog) << stateMachine << "submitting event" << eventName; - stateMachine->submitEvent(event()); + stateMachine->submitEvent(e); return true; } @@ -309,359 +311,4 @@ bool QScxmlExecutionEngine::step(Instructions &ip) } #endif // BUILD_QSCXMLC -Builder::Builder() - : m_initialSetup(QScxmlExecutableContent::NoInstruction) - , m_isCppDataModel(false) -{ - m_activeSequences.reserve(4); -} - -bool Builder::visit(DocumentModel::Send *node) -{ - auto instr = m_instructions.add<Send>(Send::calculateExtraSize(node->params.size(), node->namelist.size())); - instr->instructionLocation = createContext(QStringLiteral("send")); - instr->event = addString(node->event); - instr->eventexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("eventexpr"), node->eventexpr); - instr->type = addString(node->type); - instr->typeexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("typeexpr"), node->typeexpr); - instr->target = addString(node->target); - instr->targetexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("targetexpr"), node->targetexpr); - instr->id = addString(node->id); - instr->idLocation = addString(node->idLocation); - instr->delay = addString(node->delay); - instr->delayexpr = createEvaluatorString(QStringLiteral("send"), QStringLiteral("delayexpr"), node->delayexpr); - instr->content = addString(node->content); - generate(&instr->namelist, node->namelist); - generate(instr->params(), node->params); - return false; -} - -void Builder::visit(DocumentModel::Raise *node) -{ - auto instr = m_instructions.add<Raise>(); - instr->event = addString(node->event); -} - -void Builder::visit(DocumentModel::Log *node) -{ - auto instr = m_instructions.add<Log>(); - instr->label = addString(node->label); - instr->expr = createEvaluatorString(QStringLiteral("log"), QStringLiteral("expr"), node->expr); -} - -void Builder::visit(DocumentModel::Script *node) -{ - auto instr = m_instructions.add<JavaScript>(); - instr->go = createEvaluatorVoid(QStringLiteral("script"), QStringLiteral("source"), node->content); -} - -void Builder::visit(DocumentModel::Assign *node) -{ - auto instr = m_instructions.add<Assign>(); - auto ctxt = createContext(QStringLiteral("assign"), QStringLiteral("expr"), node->expr); - instr->expression = addAssignment(node->location, node->expr, ctxt); -} - -bool Builder::visit(DocumentModel::If *node) -{ - auto instr = m_instructions.add<If>(node->conditions.size()); - instr->conditions.count = node->conditions.size(); - auto it = instr->conditions.data(); - QString tag = QStringLiteral("if"); - for (int i = 0, ei = node->conditions.size(); i != ei; ++i) { - *it++ = createEvaluatorBool(tag, QStringLiteral("cond"), node->conditions.at(i)); - if (i == 0) { - tag = QStringLiteral("elif"); - } - } - auto outSequences = m_instructions.add<InstructionSequences>(); - generate(outSequences, node->blocks); - return false; -} - -bool Builder::visit(DocumentModel::Foreach *node) -{ - auto instr = m_instructions.add<Foreach>(); - auto ctxt = createContextString(QStringLiteral("foreach")); - instr->doIt = addForeach(node->array, node->item, node->index, ctxt); - startSequence(&instr->block); - visit(&node->block); - endSequence(); - return false; -} - -void Builder::visit(DocumentModel::Cancel *node) -{ - auto instr = m_instructions.add<Cancel>(); - instr->sendid = addString(node->sendid); - instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"), QStringLiteral("sendidexpr"), node->sendidexpr); -} - -ContainerId Builder::generate(const DocumentModel::DoneData *node) -{ - auto id = m_instructions.newContainerId(); - DoneData *doneData; - if (node) { - doneData = m_instructions.add<DoneData>(node->params.size() * Param::calculateSize()); - doneData->contents = addString(node->contents); - doneData->expr = createEvaluatorString(QStringLiteral("donedata"), QStringLiteral("expr"), node->expr); - generate(&doneData->params, node->params); - } else { - doneData = m_instructions.add<DoneData>(); - doneData->contents = NoString; - doneData->expr = NoEvaluator; - doneData->params.count = 0; - } - doneData->location = createContext(QStringLiteral("final")); - return id; -} - -StringId Builder::createContext(const QString &instrName) -{ - return addString(createContextString(instrName)); -} - -void Builder::generate(const QVector<DocumentModel::DataElement *> &dataElements) -{ - foreach (DocumentModel::DataElement *el, dataElements) { - auto ctxt = createContext(QStringLiteral("data"), QStringLiteral("expr"), el->expr); - auto evaluator = addDataElement(el->id, el->expr, ctxt); - if (evaluator != NoEvaluator) { - auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>(); - instr->expression = evaluator; - } - } -} - -ContainerId Builder::generate(const DocumentModel::InstructionSequences &inSequences) -{ - if (inSequences.isEmpty()) - return NoInstruction; - - auto id = m_instructions.newContainerId(); - auto outSequences = m_instructions.add<InstructionSequences>(); - generate(outSequences, inSequences); - return id; -} - -void Builder::generate(Array<Param> *out, const QVector<DocumentModel::Param *> &in) -{ - out->count = in.size(); - Param *it = out->data(); - foreach (DocumentModel::Param *f, in) { - it->name = addString(f->name); - it->expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), f->expr); - it->location = addString(f->location); - ++it; - } -} - -void Builder::generate(InstructionSequences *outSequences, const DocumentModel::InstructionSequences &inSequences) -{ - int sequencesOffset = m_instructions.offset(outSequences); - int sequenceCount = 0; - int entryCount = 0; - foreach (DocumentModel::InstructionSequence *sequence, inSequences) { - ++sequenceCount; - startNewSequence(); - visit(sequence); - entryCount += endSequence()->size(); - } - outSequences = m_instructions.at<InstructionSequences>(sequencesOffset); - outSequences->sequenceCount = sequenceCount; - outSequences->entryCount = entryCount; -} - -void Builder::generate(Array<StringId> *out, const QStringList &in) -{ - out->count = in.size(); - StringId *it = out->data(); - foreach (const QString &str, in) { - *it++ = addString(str); - } -} - -ContainerId Builder::startNewSequence() -{ - auto id = m_instructions.newContainerId(); - auto sequence = m_instructions.add<InstructionSequence>(); - startSequence(sequence); - return id; -} - -void Builder::startSequence(InstructionSequence *sequence) -{ - SequenceInfo info; - info.location = m_instructions.offset(sequence); - info.entryCount = 0; - m_activeSequences.push_back(info); - m_instructions.setSequenceInfo(&m_activeSequences.last()); - sequence->instructionType = Instruction::Sequence; - sequence->entryCount = -1; // checked in endSequence -} - -InstructionSequence *Builder::endSequence() -{ - SequenceInfo info = m_activeSequences.back(); - m_activeSequences.pop_back(); - m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? Q_NULLPTR : &m_activeSequences.last()); - - auto sequence = m_instructions.at<InstructionSequence>(info.location); - Q_ASSERT(sequence->entryCount == -1); // set in startSequence - sequence->entryCount = info.entryCount; - if (!m_activeSequences.isEmpty()) - m_activeSequences.last().entryCount += info.entryCount; - return sequence; -} - -EvaluatorId Builder::createEvaluatorString(const QString &instrName, const QString &attrName, const QString &expr) -{ - if (!expr.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_stringEvaluators.insert(id, expr); - return id; - } else { - QString loc = createContext(instrName, attrName, expr); - return addEvaluator(expr, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorBool(const QString &instrName, const QString &attrName, const QString &cond) -{ - if (!cond.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_boolEvaluators.insert(id, cond); - return id; - } else { - QString loc = createContext(instrName, attrName, cond); - return addEvaluator(cond, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorVariant(const QString &instrName, const QString &attrName, const QString &expr) -{ - if (!expr.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_variantEvaluators.insert(id, expr); - return id; - } else { - QString loc = createContext(instrName, attrName, expr); - return addEvaluator(expr, loc); - } - } - - return NoEvaluator; -} - -EvaluatorId Builder::createEvaluatorVoid(const QString &instrName, const QString &attrName, const QString &stuff) -{ - if (!stuff.isEmpty()) { - if (isCppDataModel()) { - auto id = m_evaluators.add(EvaluatorInfo(), false); - m_voidEvaluators.insert(id, stuff); - return id; - } else { - QString loc = createContext(instrName, attrName, stuff); - return addEvaluator(stuff, loc); - } - } - - return NoEvaluator; -} - -DynamicTableData *Builder::tableData() -{ - auto td = new DynamicTableData; - td->strings = m_stringTable.data(); - td->theInstructions = m_instructions.data(); - td->theEvaluators = m_evaluators.data(); - td->theAssignments = m_assignments.data(); - td->theForeaches = m_foreaches.data(); - td->theDataNameIds = m_dataIds; - td->theInitialSetup = m_initialSetup; - td->theName = m_name; - return td; -} - -QString DynamicTableData::string(StringId id) const -{ - return id == NoString ? QString() : strings.at(id); -} - -Instructions DynamicTableData::instructions() const -{ - return const_cast<Instructions>(theInstructions.data()); -} - -EvaluatorInfo DynamicTableData::evaluatorInfo(EvaluatorId evaluatorId) const -{ - return theEvaluators[evaluatorId]; -} - -AssignmentInfo DynamicTableData::assignmentInfo(EvaluatorId assignmentId) const -{ - return theAssignments[assignmentId]; -} - -ForeachInfo DynamicTableData::foreachInfo(EvaluatorId foreachId) const -{ - return theForeaches[foreachId]; -} - -StringId *DynamicTableData::dataNames(int *count) const -{ - Q_ASSERT(count); - *count = theDataNameIds.size(); - return const_cast<StringId *>(theDataNameIds.data()); -} - -ContainerId DynamicTableData::initialSetup() const -{ - return theInitialSetup; -} - -QString DynamicTableData::name() const -{ - return theName; -} - -QVector<qint32> DynamicTableData::instructionTable() const -{ - return theInstructions; -} - -QVector<QString> DynamicTableData::stringTable() const -{ - return strings; -} - -QVector<EvaluatorInfo> DynamicTableData::evaluators() const -{ - return theEvaluators; -} - -QVector<AssignmentInfo> DynamicTableData::assignments() const -{ - return theAssignments; -} - -QVector<ForeachInfo> DynamicTableData::foreaches() const -{ - return theForeaches; -} - -StringIds DynamicTableData::allDataNameIds() const -{ - return theDataNameIds; -} - QT_END_NAMESPACE diff --git a/src/scxml/qscxmlexecutablecontent.h b/src/scxml/qscxmlexecutablecontent.h index 4bcfc36..969b1f9 100644 --- a/src/scxml/qscxmlexecutablecontent.h +++ b/src/scxml/qscxmlexecutablecontent.h @@ -83,6 +83,23 @@ struct ForeachInfo { typedef qint32 EvaluatorId; enum { NoEvaluator = -1 }; + +struct Q_SCXML_EXPORT Param +{ + StringId name; + EvaluatorId expr; + StringId location; + + Param(StringId theName, EvaluatorId theExpr, StringId theLocation) + : name(theName), expr(theExpr), location(theLocation) + {} + + Param() : name(NoString), expr(NoInstruction), location(NoString) + {} + + static int calculateSize() { return sizeof(Param) / sizeof(qint32); } +}; + } // QScxmlExecutableContent namespace QT_END_NAMESPACE diff --git a/src/scxml/qscxmlexecutablecontent_p.h b/src/scxml/qscxmlexecutablecontent_p.h index 0d2e286..f7a4696 100644 --- a/src/scxml/qscxmlexecutablecontent_p.h +++ b/src/scxml/qscxmlexecutablecontent_p.h @@ -52,10 +52,9 @@ // #include <QtScxml/qscxmlexecutablecontent.h> -#include <QtScxml/qscxmltabledata.h> +#include <QtScxml/private/qscxmltabledata_p.h> #include <QtScxml/private/qscxmlparser_p.h> -#include <QtCore/qmap.h> -#include <QtCore/qvariant.h> +#include <QTextStream> #ifndef BUILD_QSCXMLC #include <QtScxml/qscxmldatamodel.h> @@ -109,15 +108,6 @@ struct Array int size() const { return sizeof(Array<T>) / sizeof(qint32) + dataSize(); } }; -struct Q_SCXML_EXPORT Param -{ - StringId name; - EvaluatorId expr; - StringId location; - - static int calculateSize() { return sizeof(Param) / sizeof(qint32); } -}; - struct Q_SCXML_EXPORT Instruction { enum InstructionType: qint32 { @@ -277,245 +267,195 @@ struct Q_SCXML_EXPORT Cancel: Instruction int size() const { return sizeof(Cancel) / sizeof(qint32); } }; -#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) -#pragma pack(pop) -#endif - -class Q_SCXML_EXPORT DynamicTableData: -#ifndef BUILD_QSCXMLC - public QObject, -#endif // BUILD_QSCXMLC - public QScxmlTableData -{ -#ifndef BUILD_QSCXMLC - Q_OBJECT -#endif - -public: - QString string(StringId id) const Q_DECL_OVERRIDE; - Instructions instructions() const Q_DECL_OVERRIDE; - EvaluatorInfo evaluatorInfo(EvaluatorId evaluatorId) const Q_DECL_OVERRIDE; - AssignmentInfo assignmentInfo(EvaluatorId assignmentId) const Q_DECL_OVERRIDE; - ForeachInfo foreachInfo(EvaluatorId foreachId) const Q_DECL_OVERRIDE; - StringId *dataNames(int *count) const Q_DECL_OVERRIDE; - ContainerId initialSetup() const Q_DECL_OVERRIDE; - QString name() const Q_DECL_OVERRIDE; - - QVector<qint32> instructionTable() const; - QVector<QString> stringTable() const; - QVector<EvaluatorInfo> evaluators() const; - QVector<AssignmentInfo> assignments() const; - QVector<ForeachInfo> foreaches() const; - StringIds allDataNameIds() const; - -private: - friend class Builder; - QVector<QString> strings; - QVector<qint32> theInstructions; - QVector<EvaluatorInfo> theEvaluators; - QVector<AssignmentInfo> theAssignments; - QVector<ForeachInfo> theForeaches; - StringIds theDataNameIds; - EvaluatorId theInitialSetup; - QString theName; -}; - -class Q_SCXML_EXPORT Builder: public DocumentModel::NodeVisitor -{ -public: - Builder(); - -protected: // visitor - using NodeVisitor::visit; - - bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Raise *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Log *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Script *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Assign *node) Q_DECL_OVERRIDE; - bool visit(DocumentModel::If *node) Q_DECL_OVERRIDE; - bool visit(DocumentModel::Foreach *node) Q_DECL_OVERRIDE; - void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE; - -protected: - ContainerId generate(const DocumentModel::DoneData *node); - StringId createContext(const QString &instrName); - void generate(const QVector<DocumentModel::DataElement *> &dataElements); - ContainerId generate(const DocumentModel::InstructionSequences &inSequences); - void generate(Array<Param> *out, const QVector<DocumentModel::Param *> &in); - void generate(InstructionSequences *outSequences, const DocumentModel::InstructionSequences &inSequences); - void generate(Array<StringId> *out, const QStringList &in); - ContainerId startNewSequence(); - void startSequence(InstructionSequence *sequence); - InstructionSequence *endSequence(); - EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName, const QString &expr); - EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName, const QString &cond); - EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName, const QString &expr); - EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName, const QString &stuff); - - virtual QString createContextString(const QString &instrName) const = 0; - virtual QString createContext(const QString &instrName, const QString &attrName, const QString &attrValue) const = 0; - - DynamicTableData *tableData(); - - StringId addString(const QString &str) - { return str.isEmpty() ? NoString : m_stringTable.add(str); } - - void setInitialSetup(ContainerId id) - { m_initialSetup = id; } - - void setName(const QString &name) - { m_name = name; } +struct StateTable { + int version; + int name; + enum: int { + InvalidDataModel = -1, + NullDataModel = 0, + EcmaScriptDataModel = 1, + CppDataModel = 2 + } dataModel; + int childStates; // offset into offsets + int initialTransition; + int initialSetup; + enum: int { InvalidBinding = -1, EarlyBinding = 0, LateBinding = 1 } binding; + int maxServiceId; + int stateOffset, stateCount; + int transitionOffset, transitionCount; + int arrayOffset, arraySize; + + enum { terminator = 0xc0ff33 }; + enum { InvalidIndex = -1 }; + + struct State { + int name; + int parent; + enum: int { + Invalid = -1, + Normal = 0, + Parallel = 1, + Final = 2, + ShallowHistory = 3, + DeepHistory = 4 + } type; + int initialTransition; + int initInstructions; + int entryInstructions; + int exitInstructions; + int doneData; + int childStates; // offset into arrays + int transitions; // offset into arrays + int serviceFactoryIds; // offset into arrays + + State() + : name(InvalidIndex) + , parent(InvalidIndex) + , type(Invalid) + , initialTransition(InvalidIndex) + , initInstructions(InvalidIndex) + , entryInstructions(InvalidIndex) + , exitInstructions(InvalidIndex) + , doneData(InvalidIndex) + , childStates(InvalidIndex) + , transitions(InvalidIndex) + , serviceFactoryIds(InvalidIndex) + {} - bool isCppDataModel() const - { return m_isCppDataModel; } + bool isAtomic() const + { return childStates == InvalidIndex; } - void setIsCppDataModel(bool onoff) - { m_isCppDataModel = onoff; } + bool isCompound() const + { return type == Normal && childStates != InvalidIndex; } - QMap<EvaluatorId, QString> boolEvaluators() const - { return m_boolEvaluators; } + bool parentIsScxmlElement() const + { return parent == InvalidIndex; } - QMap<EvaluatorId, QString> stringEvaluators() const - { return m_stringEvaluators; } + bool isHistoryState() const + { return type == ShallowHistory || type == DeepHistory; } - QMap<EvaluatorId, QString> variantEvaluators() const - { return m_variantEvaluators; } + bool isParallel() const + { return type == Parallel; } + }; - QMap<EvaluatorId, QString> voidEvaluators() const - { return m_voidEvaluators; } + struct Transition { + int events; // offset into offsets + int condition; + enum: int { + Invalid = -1, + External = 0, + Internal = 1 + } type; + int source; + int targets; // offset into offsets + int transitionInstructions; + + Transition() + : events(InvalidIndex) + , condition(InvalidIndex) + , type(Invalid) + , source(InvalidIndex) + , targets(InvalidIndex) + , transitionInstructions(InvalidIndex) + {} + }; -private: - template <typename T, typename U> - class Table { - QVector<T> elements; - QMap<T, int> indexForElement; - - public: - U add(const T &s, bool uniqueOnly = true) { - int pos = uniqueOnly ? indexForElement.value(s, -1) : -1; - if (pos == -1) { - pos = elements.size(); - elements.append(s); - indexForElement.insert(s, pos); - } - return pos; - } + struct Array { + Array(const int *start): start(start) {} + int size() const { return *start; } + bool isValid() const { return start != nullptr; } - QVector<T> data() { - elements.squeeze(); - return elements; + int operator[](int idx) const { + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < size()); + return *(start + idx + 1); } - }; - Table<QString, StringId> m_stringTable; - struct SequenceInfo { - int location; - qint32 entryCount; // the amount of qint32's that the instructions take up - }; - QVector<SequenceInfo> m_activeSequences; + struct const_iterator: public std::iterator<std::forward_iterator_tag, int, ptrdiff_t, + const int *, const int &> + { + const_iterator(const Array &a, int pos): a(a), pos(pos) {} - class InstructionStorage { - public: - InstructionStorage() - : m_info(Q_NULLPTR) - {} + const_iterator &operator++() { + if (pos < a.size()) ++pos; + return *this; + } - ContainerId newContainerId() const { return m_instr.size(); } + bool operator==(const const_iterator &other) const + { return &other.a == &a && other.pos == pos; } - template <typename T> - T *add(int extra = 0) - { - int pos = m_instr.size(); - int size = sizeof(T) / sizeof(qint32) + extra; - if (m_info) - m_info->entryCount += size; - m_instr.resize(pos + size); - T *instr = at<T>(pos); - Q_ASSERT(instr->instructionType == 0); - instr->instructionType = T::kind(); - return instr; - } + bool operator!=(const StateTable::Array::const_iterator &other) + { return !this->operator==(other); } - int offset(Instruction *instr) const - { - return reinterpret_cast<qint32 *>(instr) - m_instr.data(); - } + int operator*() const { + if (pos < a.size()) + return a[pos]; + else + return -1; + } - template <typename T> - T *at(int offset) - { - return reinterpret_cast<T *>(&m_instr[offset]); - } + private: + const Array &a; + int pos; + }; - QVector<qint32> data() - { - m_instr.squeeze(); - return m_instr; - } + const_iterator begin() const + { return const_iterator(*this, 0); } - void setSequenceInfo(SequenceInfo *info) - { - m_info = info; - } + const_iterator end() const + { return const_iterator(*this, size()); } private: - QVector<qint32> m_instr; - SequenceInfo *m_info; + const int *start; }; - InstructionStorage m_instructions; - - EvaluatorId addEvaluator(const QString &expr, const QString &context) - { - EvaluatorInfo ei; - ei.expr = addString(expr); - ei.context = addString(context); - return m_evaluators.add(ei); - } - EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context) + StateTable() + : version(InvalidIndex) + , name(InvalidIndex) + , dataModel(InvalidDataModel) + , childStates(InvalidIndex) + , initialTransition(InvalidIndex) + , initialSetup(InvalidIndex) + , binding(InvalidBinding) + , maxServiceId(InvalidIndex) + , stateOffset(InvalidIndex), stateCount(InvalidIndex) + , transitionOffset(InvalidIndex), transitionCount(InvalidIndex) + , arrayOffset(InvalidIndex), arraySize(InvalidIndex) + {} + + const State &state(int idx) const { - AssignmentInfo ai; - ai.dest = addString(dest); - ai.expr = addString(expr); - ai.context = addString(context); - return m_assignments.add(ai); + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < stateCount); + return reinterpret_cast<const State *>( + reinterpret_cast<const int *>(this) + stateOffset)[idx]; } - EvaluatorId addForeach(const QString &array, const QString &item, const QString &index, const QString &context) + const Transition &transition(int idx) const { - ForeachInfo fi; - fi.array = addString(array); - fi.item = addString(item); - fi.index = addString(index); - fi.context = addString(context); - return m_foreaches.add(fi); + Q_ASSERT(idx >= 0); + Q_ASSERT(idx < transitionCount); + return reinterpret_cast<const Transition *>( + reinterpret_cast<const int *>(this) + transitionOffset)[idx]; } - EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context) + const Array array(int idx) const { - auto str = addString(id); - if (!m_dataIds.contains(str)) - m_dataIds.append(str); - if (expr.isEmpty()) - return NoEvaluator; - - return addAssignment(id, expr, context); + Q_ASSERT(idx < arraySize); + if (idx >= 0) { + const int *start = reinterpret_cast<const int *>(this) + arrayOffset + idx; + Q_ASSERT(*start + idx < arraySize); + return Array(start); + } else { + return Array(nullptr); + } } - - Table<EvaluatorInfo, EvaluatorId> m_evaluators; - Table<AssignmentInfo, EvaluatorId> m_assignments; - Table<ForeachInfo, EvaluatorId> m_foreaches; - QMap<EvaluatorId, QString> m_boolEvaluators; - QMap<EvaluatorId, QString> m_stringEvaluators; - QMap<EvaluatorId, QString> m_variantEvaluators; - QMap<EvaluatorId, QString> m_voidEvaluators; - StringIds m_dataIds; - ContainerId m_initialSetup; - QString m_name; - bool m_isCppDataModel; }; +#if defined(Q_CC_MSVC) || defined(Q_CC_GNU) +#pragma pack(pop) +#endif + class QScxmlExecutionEngine { Q_DISABLE_COPY(QScxmlExecutionEngine) diff --git a/src/scxml/qscxmlinvokableservice.cpp b/src/scxml/qscxmlinvokableservice.cpp index bb29635..16311ca 100644 --- a/src/scxml/qscxmlinvokableservice.cpp +++ b/src/scxml/qscxmlinvokableservice.cpp @@ -104,7 +104,7 @@ public: QScxmlExecutableContent::StringId idlocation, const QVector<QScxmlExecutableContent::StringId> &namelist, bool autoforward, - const QVector<QScxmlInvokableServiceFactory::Param> ¶ms, + const QVector<QScxmlExecutableContent::Param> ¶ms, QScxmlExecutableContent::ContainerId finalize) : invokeLocation(invokeLocation) , srcexpr(srcexpr) @@ -123,7 +123,7 @@ public: QScxmlExecutableContent::StringId idPrefix; QScxmlExecutableContent::StringId idlocation; QVector<QScxmlExecutableContent::StringId> namelist; - QVector<QScxmlInvokableServiceFactory::Param> params; + QVector<QScxmlExecutableContent::Param> params; QScxmlExecutableContent::ContainerId finalize; bool autoforward; }; @@ -136,7 +136,7 @@ QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory( QScxmlExecutableContent::StringId idlocation, const QVector<QScxmlExecutableContent::StringId> &namelist, bool autoforward, - const QVector<Param> ¶ms, + const QVector<QScxmlExecutableContent::Param> ¶ms, QScxmlExecutableContent::ContainerId finalize) : d(new Data(invokeLocation, srcexpr, @@ -202,7 +202,7 @@ QVariantMap QScxmlInvokableServiceFactory::calculateData(QScxmlStateMachine *par auto dataModel = parent->dataModel(); auto tableData = parent->tableData(); - foreach (const Param ¶m, d->params) { + foreach (const QScxmlExecutableContent::Param ¶m, d->params) { auto name = tableData->string(param.name); if (param.expr != QScxmlExecutableContent::NoEvaluator) { @@ -326,10 +326,10 @@ QScxmlInvokableScxmlServiceFactory::QScxmlInvokableScxmlServiceFactory( QScxmlExecutableContent::StringId idlocation, const QVector<QScxmlExecutableContent::StringId> &namelist, bool doAutoforward, - const QVector<QScxmlInvokableServiceFactory::Param> ¶ms, + const QVector<QScxmlExecutableContent::Param> ¶ms, QScxmlExecutableContent::ContainerId finalize) : QScxmlInvokableServiceFactory(invokeLocation, srcexpr, id, idPrefix, idlocation, namelist, - doAutoforward, params, finalize) + doAutoforward, params, finalize) {} QScxmlInvokableService *QScxmlInvokableScxmlServiceFactory::finishInvoke(QScxmlStateMachine *child, QScxmlStateMachine *parent) @@ -338,4 +338,14 @@ QScxmlInvokableService *QScxmlInvokableScxmlServiceFactory::finishInvoke(QScxmlS return new QScxmlInvokableScxml(this, child, parent); } +QScxmlInvokableService *QScxmlDynamicScxmlFactory::invoke(QScxmlStateMachine *parent) +{ + bool ok = true; + auto srcexpr = calculateSrcexpr(parent, &ok); + if (!ok) + return Q_NULLPTR; + + return loadAndInvokeDynamically(parent, srcexpr); +} + QT_END_NAMESPACE diff --git a/src/scxml/qscxmlinvokableservice.h b/src/scxml/qscxmlinvokableservice.h index 850047a..8323fe2 100644 --- a/src/scxml/qscxmlinvokableservice.h +++ b/src/scxml/qscxmlinvokableservice.h @@ -78,27 +78,6 @@ private: class Q_SCXML_EXPORT QScxmlInvokableServiceFactory { public: - struct Q_SCXML_EXPORT Param - { - QScxmlExecutableContent::StringId name; - QScxmlExecutableContent::EvaluatorId expr; - QScxmlExecutableContent::StringId location; - - Param(QScxmlExecutableContent::StringId theName, - QScxmlExecutableContent::EvaluatorId theExpr, - QScxmlExecutableContent::StringId theLocation) - : name(theName) - , expr(theExpr) - , location(theLocation) - {} - - Param() - : name(QScxmlExecutableContent::NoString) - , expr(QScxmlExecutableContent::NoInstruction) - , location(QScxmlExecutableContent::NoString) - {} - }; - QScxmlInvokableServiceFactory(QScxmlExecutableContent::StringId invokeLocation, QScxmlExecutableContent::EvaluatorId srcexpr, QScxmlExecutableContent::StringId id, @@ -106,7 +85,7 @@ public: QScxmlExecutableContent::StringId idlocation, const QVector<QScxmlExecutableContent::StringId> &namelist, bool autoforward, - const QVector<Param> ¶ms, + const QVector<QScxmlExecutableContent::Param> ¶ms, QScxmlExecutableContent::ContainerId finalizeContent); virtual ~QScxmlInvokableServiceFactory(); @@ -153,7 +132,7 @@ public: QScxmlExecutableContent::StringId idlocation, const QVector<QScxmlExecutableContent::StringId> &namelist, bool doAutoforward, - const QVector<Param> ¶ms, + const QVector<QScxmlExecutableContent::Param> ¶ms, QScxmlExecutableContent::ContainerId finalize); protected: @@ -166,6 +145,48 @@ protected: QScxmlStateMachine *parent); }; +template<class T> +class QScxmlInvokeScxmlFactory: public QScxmlInvokableScxmlServiceFactory +{ +public: + QScxmlInvokeScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, + QScxmlExecutableContent::StringId id, + QScxmlExecutableContent::StringId idPrefix, + QScxmlExecutableContent::StringId idlocation, + const QVector<QScxmlExecutableContent::StringId> &namelist, + bool doAutoforward, + const QVector<QScxmlExecutableContent::Param> ¶ms, + QScxmlExecutableContent::ContainerId finalize) + : QScxmlInvokableScxmlServiceFactory(invokeLocation, QScxmlExecutableContent::NoInstruction, + id, idPrefix, idlocation, namelist, doAutoforward, + params, finalize) + {} + + QScxmlInvokableService *invoke(QScxmlStateMachine *parent) Q_DECL_OVERRIDE + { + return finishInvoke(new T, parent); + } +}; + +class Q_SCXML_EXPORT QScxmlDynamicScxmlFactory: public QScxmlInvokableScxmlServiceFactory +{ +public: + QScxmlDynamicScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, + QScxmlExecutableContent::EvaluatorId srcexpr, + QScxmlExecutableContent::StringId id, + QScxmlExecutableContent::StringId idPrefix, + QScxmlExecutableContent::StringId idlocation, + const QVector<QScxmlExecutableContent::StringId> &namelist, + bool doAutoforward, + const QVector<QScxmlExecutableContent::Param> ¶ms, + QScxmlExecutableContent::ContainerId finalize) + : QScxmlInvokableScxmlServiceFactory(invokeLocation, srcexpr, id, idPrefix, idlocation, + namelist, doAutoforward, params, finalize) + {} + + QScxmlInvokableService *invoke(QScxmlStateMachine *parent) Q_DECL_OVERRIDE; +}; + QT_END_NAMESPACE Q_DECLARE_METATYPE(QScxmlInvokableService *) diff --git a/src/scxml/qscxmlparser.cpp b/src/scxml/qscxmlparser.cpp index 3bb9720..057a04f 100644 --- a/src/scxml/qscxmlparser.cpp +++ b/src/scxml/qscxmlparser.cpp @@ -51,10 +51,11 @@ #ifndef BUILD_QSCXMLC #include "qscxmlnulldatamodel.h" #include "qscxmlecmascriptdatamodel.h" -#include "qscxmlqstates.h" +#include "qscxmlinvokableservice.h" #include "qscxmldatamodel_p.h" #include "qscxmlstatemachine_p.h" #include "qscxmlstatemachine.h" +#include "qscxmltabledata_p.h" #include <QState> #include <QHistoryState> @@ -116,7 +117,6 @@ public: private: bool visit(DocumentModel::Scxml *scxml) Q_DECL_OVERRIDE { - Q_ASSERT(scxml->initialStates.isEmpty()); if (!scxml->name.isEmpty() && !isValidToken(scxml->name, XmlNmtoken)) { error(scxml->xmlLocation, QStringLiteral("scxml name '%1' is not a valid XML Nmtoken").arg(scxml->name)); @@ -124,15 +124,17 @@ private: if (scxml->initial.isEmpty()) { if (auto firstChild = firstAbstractState(scxml)) { - scxml->initialStates.append(firstChild); + scxml->initialTransition = createInitialTransition({firstChild}); } } else { + QVector<DocumentModel::AbstractState *> initialStates; foreach (const QString &initial, scxml->initial) { if (DocumentModel::AbstractState *s = m_stateById.value(initial)) - scxml->initialStates.append(s); + initialStates.append(s); else error(scxml->xmlLocation, QStringLiteral("initial state '%1' not found for <scxml> element").arg(initial)); } + scxml->initialTransition = createInitialTransition(initialStates); } m_parentNodes.append(scxml); @@ -147,28 +149,38 @@ private: bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE { - Q_ASSERT(state->initialStates.isEmpty()); - if (!state->id.isEmpty() && !isValidToken(state->id, XmlNCName)) { error(state->xmlLocation, QStringLiteral("'%1' is not a valid XML ID").arg(state->id)); - } else if (state->type != DocumentModel::State::Initial) { + } else { validateStateName(state->xmlLocation, state->id); } - if (state->initial.isEmpty()) { - if (auto firstChild = firstAbstractState(state)) { - state->initialStates += firstChild; + if (state->initialTransition == nullptr) { + if (state->initial.isEmpty()) { + if (auto firstChild = firstAbstractState(state)) { + state->initialTransition = createInitialTransition({firstChild}); + } + } else { + Q_ASSERT(state->type == DocumentModel::State::Normal); + QVector<DocumentModel::AbstractState *> initialStates; + foreach (const QString &initialState, state->initial) { + if (DocumentModel::AbstractState *s = m_stateById.value(initialState)) { + initialStates.append(s); + } else { + error(state->xmlLocation, + QStringLiteral("undefined initial state '%1' for state '%2'") + .arg(initialState, state->id)); + } + } + state->initialTransition = createInitialTransition(initialStates); } } else { - Q_ASSERT(state->type == DocumentModel::State::Normal); - foreach (const QString &initialState, state->initial) { - if (DocumentModel::AbstractState *s = m_stateById.value(initialState)) { - state->initialStates += s; - } else { - error(state->xmlLocation, - QStringLiteral("undefined initial state '%1' for state '%2'") - .arg(initialState, state->id)); - } + if (state->initial.isEmpty()) { + visit(state->initialTransition); + } else { + error(state->xmlLocation, + QStringLiteral("initial transition and initial attribute for state '%1'") + .arg(state->id)); } } @@ -177,27 +189,8 @@ private: break; case DocumentModel::State::Parallel: if (!state->initial.isEmpty()) { - error(state->xmlLocation, QStringLiteral("parallel states cannot have an initial state")); - } - break; - case DocumentModel::State::Initial: - if (transitionCount(state) != 1) - error(state->xmlLocation, QStringLiteral("an initial state can only have one transition, but has '%1'").arg(transitionCount(state))); - if (DocumentModel::Transition *t = firstTransition(state)) { - if (!t->events.isEmpty() || !t->condition.isNull()) { - error(t->xmlLocation, QStringLiteral("the transition in an initial state cannot have an event or a condition")); - } - if (t->targets.isEmpty()) { - error(t->xmlLocation, QStringLiteral("the transition in an initial state must have at least one target")); - } - } - foreach (DocumentModel::StateOrTransition *child, state->children) { - if (DocumentModel::State *s = child->asState()) { - error(s->xmlLocation, QStringLiteral("substates are not allowed in initial states")); - } - } - if (parentState() == Q_NULLPTR) { - error(state->xmlLocation, QStringLiteral("initial states can only occur in a state")); + error(state->xmlLocation, + QStringLiteral("parallel states cannot have an initial state")); } break; case DocumentModel::State::Final: @@ -296,6 +289,9 @@ private: bool visit(DocumentModel::Invoke *node) Q_DECL_OVERRIDE { + if (!node->srcexpr.isEmpty()) + return false; + if (node->content.isNull()) { error(node->xmlLocation, QStringLiteral("no valid content found in <invoke> tag")); } else { @@ -446,6 +442,18 @@ private: return Q_NULLPTR; } + DocumentModel::Transition *createInitialTransition( + const QVector<DocumentModel::AbstractState *> &states) + { + auto *newTransition = m_doc->newTransition(nullptr, DocumentModel::XmlLocation(-1, -1)); + foreach (auto *s, states) { + newTransition->targets.append(s->id); + } + + newTransition->targetStates = states; + return newTransition; + } + void checkExpr(const DocumentModel::XmlLocation &loc, const QString &tag, const QString &attrName, const QString &attrValue) { if (m_doc->root->dataModel == DocumentModel::Scxml::NullDataModel && !attrValue.isEmpty()) { @@ -735,8 +743,32 @@ private: }; #ifndef BUILD_QSCXMLC -class QStateMachineBuilder; -class DynamicStateMachine: public QScxmlStateMachine, public QScxmlEventFilter +class InvokeDynamicScxmlFactory: public QScxmlInvokableScxmlServiceFactory +{ +public: + InvokeDynamicScxmlFactory(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) + : QScxmlInvokableScxmlServiceFactory(invokeLocation, srcexpr, id, idPrefix, idlocation, + namelist, autoforward, params, finalize) + {} + + void setContent(const QSharedPointer<DocumentModel::ScxmlDocument> &content) + { m_content = content; } + + QScxmlInvokableService *invoke(QScxmlStateMachine *child) Q_DECL_OVERRIDE; + +private: + QSharedPointer<DocumentModel::ScxmlDocument> m_content; +}; + +class DynamicStateMachine: public QScxmlStateMachine, public QScxmlInternal::GeneratedTableData { // Manually expanded from Q_OBJECT macro: public: @@ -764,35 +796,30 @@ public: } private: - static void qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) + Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *_o, QMetaObject::Call _c, + int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); - if (_id >= _t->m_eventNamesByIndex.size() || _id < 0) { + if (_id < 0) { // out of bounds return; } - if (_id >= _t->m_firstSubStateMachineSignal && _id < _t->m_firstSlot) { + if (_id < _t->m_firstSlot) { // these signals are only emitted, not activated by another signal return; } - if (_id >= _t->m_firstStateChangedSignal && _id < _t->m_firstSubStateMachineSignal) { - // re-propagate QAbstractState::activeChanged as stateChanged - QMetaObject::activate(_t, _t->m_metaObject, _id, _a); - return; - } // We have 1 kind of slots: those to submit events. - const QString &event = _t->m_eventNamesByIndex.at(_id); - if (!event.isEmpty()) { - if (_id < _t->m_firstSlotWithoutData) { - QVariant data = *reinterpret_cast< QVariant(*)>(_a[1]); - if (data.canConvert<QJSValue>()) { - data = data.value<QJSValue>().toVariant(); - } - _t->submitEvent(event, data); - } else { - _t->submitEvent(event, QVariant()); + if (_id < _t->m_firstSlotWithoutData) { + const QString &event = _t->m_incomingEvents.at(_id - _t->m_firstSlot); + QVariant data = *reinterpret_cast< QVariant(*)>(_a[1]); + if (data.canConvert<QJSValue>()) { + data = data.value<QJSValue>().toVariant(); } + _t->submitEvent(event, data); + } else { + const QString &event = _t->m_incomingEvents.at(_id - _t->m_firstSlotWithoutData); + _t->submitEvent(event, QVariant()); } } else if (_c == QMetaObject::RegisterPropertyMetaType) { DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); @@ -804,16 +831,14 @@ private: } else if (_c == QMetaObject::ReadProperty) { DynamicStateMachine *_t = static_cast<DynamicStateMachine *>(_o); void *_v = _a[0]; - if (_id >= 0 && _id < _t->m_propertyNamesByIndex.size()) { + if (_id >= 0 && _id < _t->m_propertyCount) { if (_id < _t->m_firstSubStateMachineProperty) { // getter for the state - auto smp = QScxmlStateMachinePrivate::get(_t); - auto name = _t->m_propertyNamesByIndex.at(_id); - *reinterpret_cast<bool*>(_v) = smp->stateByScxmlName(name)->active(); + *reinterpret_cast<bool*>(_v) = _t->isActive(_id); } else { // getter for a child statemachine int idx = _id - _t->m_firstSubStateMachineProperty; - *reinterpret_cast<QScxmlStateMachine **>(_v) = _t->m_subStateMachines.at(idx); + *reinterpret_cast<QScxmlStateMachine **>(_v) = _t->subStateMachine(idx); } } } @@ -821,34 +846,31 @@ private: // end of Q_OBJECT macro private: - friend QStateMachineBuilder; DynamicStateMachine() : m_metaObject(Q_NULLPTR) + , m_propertyCount(0) , m_firstSubStateMachineSignal(0) , m_firstSlot(0) , m_firstSlotWithoutData(0) , m_firstSubStateMachineProperty(0) { - // Temporarily wire up the QMetaObject, because qobject_cast needs it while building MyQStateMachine. + // Temporarily wire up the QMetaObject QMetaObjectBuilder b; b.setClassName("DynamicStateMachine"); b.setSuperClass(&QScxmlStateMachine::staticMetaObject); b.setStaticMetacallFunction(qt_static_metacall); m_metaObject = b.toMetaObject(); - - setScxmlEventFilter(this); } - void initDynamicParts(const QSet<QString> &eventSignals, - const QSet<QString> &eventSlots, - const QList<QString> &stateNames, - const QList<QString> &subStateMachineNames) + void initDynamicParts(const MetaDataInfo &info) { // Release the temporary QMetaObject. Q_ASSERT(m_metaObject); free(m_metaObject); - m_eventNamesByIndex.reserve(eventSignals.size() + subStateMachineNames.size() + eventSlots.size()); + m_incomingEvents = info.incomingEvents; + m_outgoingEvents = info.outgoingEvents; + std::sort(m_outgoingEvents.begin(), m_outgoingEvents.end()); // Build the real one. QMetaObjectBuilder b; @@ -857,77 +879,59 @@ private: b.setStaticMetacallFunction(qt_static_metacall); // signals - foreach (const QString &eventName, eventSignals) { - QByteArray signalName = eventName.toUtf8() + "(const QVariant &)"; - QMetaMethodBuilder signalBuilder = b.addSignal(signalName); - signalBuilder.setParameterNames(init("data")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; - } - - m_firstStateChangedSignal = m_eventNamesByIndex.size(); - foreach (const QString &stateName, stateNames) { + foreach (const QString &stateName, info.stateNames) { auto name = stateName.toUtf8(); - QByteArray signalName = name + "Changed(bool)"; + const QByteArray signalName = name + "Changed(bool)"; QMetaMethodBuilder signalBuilder = b.addSignal(signalName); signalBuilder.setParameterNames(init("active")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); } - m_firstSubStateMachineSignal = m_eventNamesByIndex.size(); - foreach (const QString &machineName, subStateMachineNames) { + m_firstSubStateMachineSignal = info.stateNames.size(); + foreach (const QString &machineName, info.subStateMachineNames) { auto name = machineName.toUtf8(); - QByteArray signalName = name + "Changed(QScxmlStateMachine *)"; + const QByteArray signalName = name + "Changed(QScxmlStateMachine *)"; QMetaMethodBuilder signalBuilder = b.addSignal(signalName); signalBuilder.setParameterNames(init("statemachine")); - int idx = signalBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); + } + + foreach (const QString &eventName, info.outgoingEvents) { + const QByteArray signalName = eventName.toUtf8() + "(const QVariant &)"; + QMetaMethodBuilder signalBuilder = b.addSignal(signalName); + signalBuilder.setParameterNames(init("data")); } // slots - m_firstSlot = m_eventNamesByIndex.size(); - foreach (const QString &eventName, eventSlots) { - QByteArray slotName = eventName.toUtf8() + "(const QVariant &)"; + m_firstSlot = info.stateNames.size() + info.subStateMachineNames.size() + + info.outgoingEvents.size(); + foreach (const QString &eventName, info.incomingEvents) { + const QByteArray slotName = eventName.toUtf8() + "(const QVariant &)"; QMetaMethodBuilder slotBuilder = b.addSlot(slotName); slotBuilder.setParameterNames(init("data")); - int idx = slotBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; } - m_firstSlotWithoutData = m_eventNamesByIndex.size(); - foreach (const QString &eventName, eventSlots) { - QByteArray slotName = eventName.toUtf8() + "()"; - QMetaMethodBuilder slotBuilder = b.addSlot(slotName); - int idx = slotBuilder.index(); - m_eventNamesByIndex.resize(std::max(idx + 1, m_eventNamesByIndex.size())); - m_eventNamesByIndex[idx] = eventName; + m_firstSlotWithoutData = m_firstSlot + info.incomingEvents.size(); + foreach (const QString &eventName, info.incomingEvents) { + const QByteArray slotName = eventName.toUtf8() + "()"; + b.addSlot(slotName); } // properties - int stateNotifier = m_firstStateChangedSignal; - foreach (const QString &stateName, stateNames) { - QMetaPropertyBuilder prop = b.addProperty(stateName.toUtf8(), "bool", stateNotifier); + int notifier = 0; + foreach (const QString &stateName, info.stateNames) { + QMetaPropertyBuilder prop = b.addProperty(stateName.toUtf8(), "bool", notifier); prop.setWritable(false); - int idx = prop.index(); - m_propertyNamesByIndex.resize(std::max(idx + 1, m_propertyNamesByIndex.size())); - m_propertyNamesByIndex[idx] = stateName; - ++stateNotifier; + ++m_propertyCount; + ++notifier; } - m_firstSubStateMachineProperty = m_propertyNamesByIndex.size(); - int notifier = m_firstSubStateMachineSignal; - foreach (const QString &machineName, subStateMachineNames) { - QMetaPropertyBuilder prop = b.addProperty(machineName.toUtf8(), "QScxmlStateMachine *", notifier); + m_firstSubStateMachineProperty = m_propertyCount; + foreach (const QString &machineName, info.subStateMachineNames) { + QMetaPropertyBuilder prop = b.addProperty(machineName.toUtf8(), "QScxmlStateMachine *", + notifier); prop.setWritable(false); - int idx = prop.index(); - m_propertyNamesByIndex.resize(std::max(idx + 1, m_propertyNamesByIndex.size())); - m_propertyNamesByIndex[idx] = machineName; + ++m_propertyCount; ++notifier; } - m_subStateMachines.resize(subStateMachineNames.size()); // And we're done m_metaObject = b.toMetaObject(); @@ -935,48 +939,59 @@ private: public: ~DynamicStateMachine() - { if (m_metaObject) free(m_metaObject); } - - bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) Q_DECL_OVERRIDE { - Q_UNUSED(stateMachine); - - if (event->originType() != QStringLiteral("qt:signal")) { - return true; + { + if (m_metaObject) { + free(m_metaObject); } + } - auto eventName = event->name(); - for (int i = 0; i < m_firstSubStateMachineSignal; ++i) { - if (m_eventNamesByIndex.at(i) == eventName) { - QVariant data = event->data(); - void *argv[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&data)) }; - QMetaObject::activate(this, metaObject(), i, argv); - return false; - } - } + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE Q_DECL_FINAL + { return m_allFactoriesById.at(id); } - return true; + int signalIndexForEvent(const QString &event) const Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto it = std::lower_bound(m_outgoingEvents.begin(), m_outgoingEvents.end(), event); + if (it != m_outgoingEvents.end() && *it == event) { + return int(std::distance(m_outgoingEvents.begin(), it)); + } else { + return -1; + } } -protected: - void setService(const QString &id, QScxmlInvokableService *service) Q_DECL_OVERRIDE + static DynamicStateMachine *build(DocumentModel::ScxmlDocument *doc) { - int idx = -1; - for (int i = m_firstSubStateMachineProperty, ei = m_propertyNamesByIndex.size(); i != ei; ++i) { - if (m_propertyNamesByIndex.at(i) == id) { - idx = i - m_firstSubStateMachineProperty; - break; - } - } - if (idx < 0) - return; - auto scxml = service ? dynamic_cast<QScxmlInvokableScxml *>(service) : Q_NULLPTR; - auto machine = scxml ? scxml->stateMachine() : Q_NULLPTR; - if (m_subStateMachines.at(idx) != machine) { - m_subStateMachines[idx] = machine; - // emit changed signal: - void *argv[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&machine)) }; - QMetaObject::activate(this, metaObject(), m_firstSubStateMachineSignal + idx, argv); - } + auto stateMachine = new DynamicStateMachine; + MetaDataInfo info; + DataModelInfo dm; + auto factoryIdCreator = [stateMachine](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 { + auto factory = new InvokeDynamicScxmlFactory(invokeLocation, + srcexpr, + id, + idPrefix, + idlocation, + namelist, + autoforward, + params, + finalize); + factory->setContent(content); + stateMachine->m_allFactoriesById.append(factory); + return stateMachine->m_allFactoriesById.size() - 1; + }; + + GeneratedTableData::build(doc, stateMachine, &info, &dm, factoryIdCreator); + stateMachine->setTableData(stateMachine); + stateMachine->initDynamicParts(info); + + return stateMachine; } private: @@ -990,389 +1005,17 @@ private: } private: + QVector<QScxmlInvokableServiceFactory *> m_allFactoriesById; QMetaObject *m_metaObject; - QVector<QString> m_eventNamesByIndex; - QVector<QString> m_propertyNamesByIndex; - QVector<QScxmlStateMachine *> m_subStateMachines; + QStringList m_incomingEvents; + QStringList m_outgoingEvents; + int m_propertyCount; int m_firstSubStateMachineSignal; - int m_firstStateChangedSignal; int m_firstSlot; int m_firstSlotWithoutData; int m_firstSubStateMachineProperty; }; -class InvokeDynamicScxmlFactory: public QScxmlInvokableScxmlServiceFactory -{ -public: - InvokeDynamicScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::EvaluatorId srcexpr, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool autoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : QScxmlInvokableScxmlServiceFactory(invokeLocation, - srcexpr, - id, - idPrefix, - idlocation, - namelist, - autoforward, - params, - finalize) - {} - - void setContent(const QSharedPointer<DocumentModel::ScxmlDocument> &content) - { m_content = content; } - - QScxmlInvokableService *invoke(QScxmlStateMachine *child) Q_DECL_OVERRIDE; - -private: - QSharedPointer<DocumentModel::ScxmlDocument> m_content; -}; - -class QStateMachineBuilder: public QScxmlExecutableContent::Builder -{ -public: - QStateMachineBuilder() - : m_stateMachine(Q_NULLPTR) - , m_currentTransition(Q_NULLPTR) - , m_bindLate(false) - , m_qtMode(false) - {} - - QScxmlStateMachine *build(DocumentModel::ScxmlDocument *doc) - { - m_stateMachine = Q_NULLPTR; - m_parents.reserve(32); - m_allTransitions.reserve(doc->allTransitions.size()); - m_docStatesToQStates.reserve(doc->allStates.size()); - m_qtMode = doc->qtMode; - - doc->root->accept(this); - wireTransitions(); - applyInitialStates(); - - QScxmlExecutableContent::DynamicTableData *td = tableData(); - td->setParent(m_stateMachine); - m_stateMachine->setTableData(td); - m_stateMachine->initDynamicParts(m_eventSignals, m_eventSlots, m_stateNames.keys(), m_subStateMachineNames.toList()); - - const auto signalCode = QByteArray::number(QSIGNAL_CODE); - for (auto it = m_stateNames.constBegin(), eit = m_stateNames.constEnd(); it != eit; ++it) { - QByteArray signal = signalCode + it.key().toUtf8() + "Changed(bool)"; - QObject::connect(it.value(), SIGNAL(activeChanged(bool)), m_stateMachine, signal.constData()); - } - - m_parents.clear(); - m_allTransitions.clear(); - m_docStatesToQStates.clear(); - m_currentTransition = Q_NULLPTR; - - return m_stateMachine; - } - -private: - using NodeVisitor::visit; - using QScxmlExecutableContent::Builder::createContext; - - bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE - { - m_stateMachine = new DynamicStateMachine; - - switch (node->binding) { - case DocumentModel::Scxml::EarlyBinding: - m_stateMachine->setDataBinding(QScxmlStateMachine::EarlyBinding); - break; - case DocumentModel::Scxml::LateBinding: - m_stateMachine->setDataBinding(QScxmlStateMachine::LateBinding); - m_bindLate = true; - break; - default: - Q_UNREACHABLE(); - } - - setName(node->name); - - m_parents.append(QScxmlStateMachinePrivate::get(m_stateMachine)->m_qStateMachine); - visit(node->children); - - m_dataElements.append(node->dataElements); - if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { - setInitialSetup(startNewSequence()); - generate(m_dataElements); - if (node->script) { - node->script->accept(this); - } - visit(&node->initialSetup); - endSequence(); - } - - m_parents.removeLast(); - - foreach (auto initialState, node->initialStates) { - Q_ASSERT(initialState); - m_initialStates.append(qMakePair(QScxmlStateMachinePrivate::get(m_stateMachine)->m_qStateMachine, initialState)); - } - - return false; - } - - bool visit(DocumentModel::State *node) Q_DECL_OVERRIDE - { - QAbstractState *newState = Q_NULLPTR; - switch (node->type) { - case DocumentModel::State::Normal: { - auto s = new QScxmlState(currentParent()); - newState = s; - foreach (DocumentModel::AbstractState *initialState, node->initialStates) { - m_initialStates.append(qMakePair(s, initialState)); - } - } break; - case DocumentModel::State::Parallel: { - auto s = new QScxmlState(currentParent()); - s->setChildMode(QState::ParallelStates); - newState = s; - } break; - case DocumentModel::State::Initial: { - auto s = new QScxmlState(currentParent()); - currentParent()->setInitialState(s); - newState = s; - } break; - case DocumentModel::State::Final: { - auto s = new QScxmlFinalState(currentParent()); - newState = s; - s->setDoneData(generate(node->doneData)); - } break; - default: - Q_UNREACHABLE(); - } - - newState->setObjectName(node->id); - m_stateNames.insert(node->id, newState); - - m_docStatesToQStates.insert(node, newState); - m_parents.append(newState); - - if (!node->dataElements.isEmpty()) { - if (m_bindLate) { - qobject_cast<QScxmlState *>(newState)->setInitInstructions(startNewSequence()); - generate(node->dataElements); - endSequence(); - } else { - m_dataElements.append(node->dataElements); - } - } - - QScxmlExecutableContent::ContainerId onEntry = generate(node->onEntry); - QScxmlExecutableContent::ContainerId onExit = generate(node->onExit); - if (QScxmlState *s = qobject_cast<QScxmlState *>(newState)) { - s->setOnEntryInstructions(onEntry); - s->setOnExitInstructions(onExit); - if (!node->invokes.isEmpty()) { - QVector<QScxmlInvokableServiceFactory *> factories; - foreach (DocumentModel::Invoke *invoke, node->invokes) { - auto ctxt = createContext(QStringLiteral("invoke")); - QVector<QScxmlExecutableContent::StringId> namelist; - foreach (const QString &name, invoke->namelist) - namelist += addString(name); - QVector<QScxmlInvokableServiceFactory::Param> params; - foreach (DocumentModel::Param *param, invoke->params) { - QScxmlInvokableServiceFactory::Param p; - p.name = addString(param->name); - p.expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), param->expr); - p.location = addString(param->location); - params.append(p); - } - QScxmlExecutableContent::ContainerId finalize = QScxmlExecutableContent::NoInstruction; - if (!invoke->finalize.isEmpty()) { - finalize = startNewSequence(); - visit(&invoke->finalize); - endSequence(); - } - auto factory = new InvokeDynamicScxmlFactory(ctxt, - createEvaluatorString(QStringLiteral("invoke"), - QStringLiteral("srcexpr"), - invoke->srcexpr), - addString(invoke->id), - addString(node->id + QStringLiteral(".session-")), - addString(invoke->idLocation), - namelist, - invoke->autoforward, - params, - finalize); - factory->setContent(invoke->content); - factories.append(factory); - QString name = invoke->content->root->name; - if (!name.isEmpty()) { - m_subStateMachineNames.insert(name); - } - } - s->setInvokableServiceFactories(factories); - } - } else if (QScxmlFinalState *f = qobject_cast<QScxmlFinalState *>(newState)) { - f->setOnEntryInstructions(onEntry); - f->setOnExitInstructions(onExit); - } else { - Q_UNREACHABLE(); - } - - visit(node->children); - - m_parents.removeLast(); - return false; - } - - bool visit(DocumentModel::Transition *node) Q_DECL_OVERRIDE - { - if (m_qtMode) { - m_eventSlots.unite(node->events.toSet()); - } - - auto newTransition = new QScxmlTransition(node->events); - if (QHistoryState *parent = qobject_cast<QHistoryState*>(m_parents.last())) { - parent->setDefaultTransition(newTransition); - } else { - currentParent()->addTransition(newTransition); - } - - if (node->condition) { - auto cond = createEvaluatorBool(QStringLiteral("transition"), QStringLiteral("cond"), *node->condition.data()); - newTransition->setConditionalExpression(cond); - } - - switch (node->type) { - case DocumentModel::Transition::External: - newTransition->setTransitionType(QAbstractTransition::ExternalTransition); - break; - case DocumentModel::Transition::Internal: - newTransition->setTransitionType(QAbstractTransition::InternalTransition); - break; - default: - Q_UNREACHABLE(); - } - - m_allTransitions.insert(newTransition, node); - if (!node->instructionsOnTransition.isEmpty()) { - m_currentTransition = newTransition; - newTransition->setInstructionsOnTransition(startNewSequence()); - visit(&node->instructionsOnTransition); - endSequence(); - m_currentTransition = 0; - } - Q_ASSERT(newTransition->stateMachine()); - return false; - } - - bool visit(DocumentModel::HistoryState *state) Q_DECL_OVERRIDE - { - QHistoryState *newState = new QScxmlHistoryState(currentParent()); - switch (state->type) { - case DocumentModel::HistoryState::Shallow: - newState->setHistoryType(QHistoryState::ShallowHistory); - break; - case DocumentModel::HistoryState::Deep: - newState->setHistoryType(QHistoryState::DeepHistory); - break; - default: - Q_UNREACHABLE(); - } - - newState->setObjectName(state->id); - m_docStatesToQStates.insert(state, newState); - m_parents.append(newState); - return true; - } - - void endVisit(DocumentModel::HistoryState *) Q_DECL_OVERRIDE - { - m_parents.removeLast(); - } - - bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE - { - if (m_qtMode && node->type == QStringLiteral("qt:signal")) { - m_eventSignals.insert(node->event); - } - - return QScxmlExecutableContent::Builder::visit(node); - } - -private: // Utility methods - QState *currentParent() const - { - if (m_parents.isEmpty()) - return Q_NULLPTR; - - QState *parent = qobject_cast<QState*>(m_parents.last()); - Q_ASSERT(parent); - return parent; - } - - void wireTransitions() - { - for (QHash<QAbstractTransition *, DocumentModel::Transition*>::const_iterator i = m_allTransitions.begin(), ei = m_allTransitions.end(); i != ei; ++i) { - QList<QAbstractState *> targets; - targets.reserve(i.value()->targets.size()); - foreach (DocumentModel::AbstractState *targetState, i.value()->targetStates) { - QAbstractState *target = m_docStatesToQStates.value(targetState); - Q_ASSERT(target); - targets.append(target); - } - i.key()->setTargetStates(targets); - - if (DebugHelper_NameTransitions) - i.key()->setObjectName(QStringLiteral("%1 -> %2").arg(i.key()->parent()->objectName(), i.value()->targets.join(QStringLiteral(",")))); - } - } - - void applyInitialStates() - { - foreach (const auto &init, m_initialStates) { - Q_ASSERT(init.second); - auto initialState = m_docStatesToQStates.value(init.second); - Q_ASSERT(initialState); - init.first->setInitialState(initialState); - } - } - - QString createContextString(const QString &instrName) const Q_DECL_OVERRIDE - { - if (m_currentTransition) { - QString state; - if (QState *s = m_currentTransition->sourceState()) { - state = QStringLiteral(" of state '%1'").arg(s->objectName()); - } - return QStringLiteral("%1 instruction in transition %2 %3").arg(instrName, m_currentTransition->objectName(), state); - } else { - return QStringLiteral("%1 instruction in state %2").arg(instrName, m_parents.last()->objectName()); - } - } - - 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); - } - -private: - DynamicStateMachine *m_stateMachine; - QVector<QAbstractState *> m_parents; - QHash<QAbstractTransition *, DocumentModel::Transition*> m_allTransitions; - QHash<DocumentModel::AbstractState *, QAbstractState *> m_docStatesToQStates; - QAbstractTransition *m_currentTransition; - QVector<QPair<QState *, DocumentModel::AbstractState *>> m_initialStates; - bool m_bindLate; - bool m_qtMode; - QVector<DocumentModel::DataElement *> m_dataElements; - QSet<QString> m_eventSignals; - QSet<QString> m_eventSlots; - QHash<QString, QAbstractState *> m_stateNames; - QSet<QString> m_subStateMachineNames; -}; - inline QScxmlInvokableService *InvokeDynamicScxmlFactory::invoke(QScxmlStateMachine *parent) { bool ok = true; @@ -1383,7 +1026,7 @@ inline QScxmlInvokableService *InvokeDynamicScxmlFactory::invoke(QScxmlStateMach if (!srcexpr.isEmpty()) return loadAndInvokeDynamically(parent, srcexpr); - auto child = QStateMachineBuilder().build(m_content.data()); + auto child = DynamicStateMachine::build(m_content.data()); auto dm = QScxmlDataModelPrivate::instantiateDataModel(m_content->root->dataModel); dm->setParent(child); @@ -1401,9 +1044,9 @@ QScxmlInvokableService *QScxmlInvokableScxmlServiceFactory::loadAndInvokeDynamic { QScxmlParser::Loader *loader = parent->loader(); + const QString baseDir = sourceUrl.isEmpty() ? QString() : QFileInfo(sourceUrl).path(); QStringList errs; - const QByteArray data = loader->load(sourceUrl, sourceUrl.isEmpty() ? - QString() : QFileInfo(sourceUrl).path(), &errs); + const QByteArray data = loader->load(sourceUrl, baseDir, &errs); if (!errs.isEmpty()) { qWarning() << errs; @@ -1429,7 +1072,7 @@ QScxmlInvokableService *QScxmlInvokableScxmlServiceFactory::loadAndInvokeDynamic return Q_NULLPTR; } - auto child = QStateMachineBuilder().build(mainDoc); + auto child = DynamicStateMachine::build(mainDoc); auto dm = QScxmlDataModelPrivate::instantiateDataModel(mainDoc->root->dataModel); dm->setParent(child); @@ -1564,7 +1207,7 @@ QScxmlStateMachine *QScxmlParser::instantiateStateMachine() const #else // BUILD_QSCXMLC DocumentModel::ScxmlDocument *doc = d->scxmlDocument(); if (doc && doc->root) { - return QStateMachineBuilder().build(doc); + return DynamicStateMachine::build(doc); } else { class InvalidStateMachine: public QScxmlStateMachine { public: @@ -2286,15 +1929,36 @@ bool QScxmlParserPrivate::preReadElementInitial() addError(QStringLiteral("Explicit initial state for parallel states not supported (only implicitly through the initial states of its substates)")); return false; } - auto newState = m_doc->newState(m_currentState, DocumentModel::State::Initial, xmlLocation()); - m_currentState = newState; return true; } bool QScxmlParserPrivate::preReadElementTransition() { + // Parser stack at this point: + // <transition> + // <initial> + // <state> or <scxml> + // + // Or: + // <transition> + // <state> or <scxml> + + DocumentModel::Transition *transition = nullptr; + if (previous().kind == ParserState::Initial) { + transition = m_doc->newTransition(nullptr, xmlLocation()); + const auto &initialParentState = m_stack.at(m_stack.size() - 3); + if (initialParentState.kind == ParserState::Scxml) { + m_currentState->asScxml()->initialTransition = transition; + } else if (initialParentState.kind == ParserState::State) { + m_currentState->asState()->initialTransition = transition; + } else { + Q_UNREACHABLE(); + } + } else { + transition = m_doc->newTransition(m_currentState, xmlLocation()); + } + const QXmlStreamAttributes attributes = m_reader->attributes(); - auto transition = m_doc->newTransition(m_currentState, xmlLocation()); transition->events = attributes.value(QLatin1String("event")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); transition->targets = attributes.value(QLatin1String("target")).toString().split(QLatin1Char(' '), QString::SkipEmptyParts); if (attributes.hasAttribute(QStringLiteral("cond"))) @@ -2664,7 +2328,6 @@ bool QScxmlParserPrivate::postReadElementParallel() bool QScxmlParserPrivate::postReadElementInitial() { - currentStateUp(); return true; } @@ -2859,11 +2522,6 @@ bool QScxmlParserPrivate::postReadElementInvoke() QXmlStreamReader reader(data); parseSubDocument(i, &reader, fileName); } - } else { // invoke always expects sub document - DocumentModel::ScxmlDocument *doc = new DocumentModel::ScxmlDocument(m_fileName); - doc->root = new DocumentModel::Scxml(DocumentModel::XmlLocation(0, 0)); - i->content.reset(doc); - m_doc->allSubDocuments.append(i->content.data()); } } else if (!fileName.isEmpty()) { addError(QStringLiteral("both src and content given to invoke")); diff --git a/src/scxml/qscxmlparser_p.h b/src/scxml/qscxmlparser_p.h index 42b61f8..6738f65 100644 --- a/src/scxml/qscxmlparser_p.h +++ b/src/scxml/qscxmlparser_p.h @@ -281,7 +281,7 @@ struct AbstractState: public StateContainer struct State: public AbstractState, public StateOrTransition { - enum Type { Normal, Parallel, Initial, Final }; + enum Type { Normal, Parallel, Final }; QStringList initial; QVector<DataElement *> dataElements; @@ -292,12 +292,13 @@ struct State: public AbstractState, public StateOrTransition QVector<Invoke *> invokes; Type type; - QVector<AbstractState *> initialStates; // filled during verification + Transition *initialTransition; // when not set, it is filled during verification State(const XmlLocation &xmlLocation) : StateOrTransition(xmlLocation) , doneData(Q_NULLPTR) , type(Normal) + , initialTransition(Q_NULLPTR) {} void add(StateOrTransition *s) Q_DECL_OVERRIDE @@ -320,7 +321,7 @@ struct Transition: public StateOrTransition InstructionSequence instructionsOnTransition; Type type; - QVector<AbstractState *> targetStates; // filled during verification + QVector<AbstractState *> targetStates; // when not set, it is filled during verification Transition(const XmlLocation &xmlLocation) : StateOrTransition(xmlLocation) @@ -379,7 +380,7 @@ struct Scxml: public StateContainer, public Node QScopedPointer<Script> script; InstructionSequence initialSetup; - QVector<AbstractState *> initialStates; // filled during verification + Transition *initialTransition; Scxml(const XmlLocation &xmlLocation) : Node(xmlLocation) @@ -449,7 +450,9 @@ struct ScxmlDocument { Transition *t = newNode<Transition>(xmlLocation); allTransitions.append(t); - parent->add(t); + if (parent != nullptr) { + parent->add(t); + } return t; } diff --git a/src/scxml/qscxmlqstates.cpp b/src/scxml/qscxmlqstates.cpp deleted file mode 100644 index d40ef20..0000000 --- a/src/scxml/qscxmlqstates.cpp +++ /dev/null @@ -1,410 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtScxml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qscxmlglobals_p.h" -#include "qscxmlqstates_p.h" -#include "qscxmlstatemachine_p.h" - -#undef DUMP_EVENT -#ifdef DUMP_EVENT -#include <QJSEngine> -#include "qscxmlecmascriptdatamodel.h" -#endif - -QT_BEGIN_NAMESPACE - -static QStringList filterEmpty(const QStringList &events) { - QStringList res; - int oldI = 0; - for (int i = 0; i < events.size(); ++i) { - if (events.at(i).isEmpty()) { - res.append(events.mid(oldI, i - oldI)); - oldI = i + 1; - } - } - if (oldI > 0) { - res.append(events.mid(oldI)); - return res; - } - return events; -} - -QScxmlStatePrivate::QScxmlStatePrivate() - : initInstructions(QScxmlExecutableContent::NoInstruction) - , onEntryInstructions(QScxmlExecutableContent::NoInstruction) - , onExitInstructions(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlStatePrivate::~QScxmlStatePrivate() -{ - qDeleteAll(invokableServiceFactories); -} - -QScxmlState::QScxmlState(QState *parent) - : QState(*new QScxmlStatePrivate, parent) -{} - -QScxmlState::QScxmlState(QScxmlStateMachine *parent) - : QState(*new QScxmlStatePrivate, QScxmlStateMachinePrivate::get(parent)->m_qStateMachine) -{} - -QScxmlState::~QScxmlState() -{} - -void QScxmlState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlState::stateMachine() const { - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QString QScxmlState::stateLocation() const -{ - return QStringLiteral("State %1").arg(objectName()); -} - -void QScxmlState::setInitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->initInstructions = instructions; -} - -void QScxmlState::setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->onEntryInstructions = instructions; -} - -void QScxmlState::setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlState); - d->onExitInstructions = instructions; -} - -void QScxmlState::setInvokableServiceFactories(const QVector<QScxmlInvokableServiceFactory *> &factories) -{ - Q_D(QScxmlState); - d->invokableServiceFactories = factories; -} - -void QScxmlState::onEntry(QEvent *event) -{ - Q_D(QScxmlState); - - auto sp = QScxmlStateMachinePrivate::get(stateMachine()); - if (d->initInstructions != QScxmlExecutableContent::NoInstruction) { - sp->m_executionEngine->execute(d->initInstructions); - d->initInstructions = QScxmlExecutableContent::NoInstruction; - } - QState::onEntry(event); - auto sm = stateMachine(); - QScxmlStateMachinePrivate::get(sm)->m_executionEngine->execute(d->onEntryInstructions); - foreach (QScxmlInvokableServiceFactory *f, d->invokableServiceFactories) { - if (auto service = f->invoke(stateMachine())) { - d->invokedServices.append(service); - d->servicesWaitingToStart.append(service); - sp->addService(service); - } - } - emit didEnter(); -} - -void QScxmlState::onExit(QEvent *event) -{ - Q_D(QScxmlState); - - emit willExit(); - auto sm = stateMachine(); - QScxmlStateMachinePrivate::get(sm)->m_executionEngine->execute(d->onExitInstructions); - QState::onExit(event); -} - -QScxmlFinalStatePrivate::QScxmlFinalStatePrivate() - : doneData(QScxmlExecutableContent::NoInstruction) - , onEntryInstructions(QScxmlExecutableContent::NoInstruction) - , onExitInstructions(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlFinalStatePrivate::~QScxmlFinalStatePrivate() -{} - -QScxmlFinalState::QScxmlFinalState(QState *parent) - : QFinalState(*new QScxmlFinalStatePrivate, parent) -{} - -QScxmlFinalState::QScxmlFinalState(QScxmlStateMachine *parent) - : QFinalState(*new QScxmlFinalStatePrivate, QScxmlStateMachinePrivate::get(parent)->m_qStateMachine) -{} - -QScxmlFinalState::~QScxmlFinalState() -{} - -void QScxmlFinalState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlFinalState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlFinalState::stateMachine() const { - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QScxmlHistoryState::QScxmlHistoryState(QState *parent) - : QHistoryState(parent) -{ -} - -QScxmlHistoryState::~QScxmlHistoryState() -{ -} - -void QScxmlHistoryState::setAsInitialStateFor(QScxmlState *state) -{ - state->setInitialState(this); -} - -void QScxmlHistoryState::setAsInitialStateFor(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->setInitialState(this); -} - -QScxmlStateMachine *QScxmlHistoryState::stateMachine() const -{ - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -QScxmlExecutableContent::ContainerId QScxmlFinalState::doneData() const -{ - Q_D(const QScxmlFinalState); - return d->doneData; -} - -void QScxmlFinalState::setDoneData(QScxmlExecutableContent::ContainerId doneData) -{ - Q_D(QScxmlFinalState); - d->doneData = doneData; -} - -void QScxmlFinalState::setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlFinalState); - d->onEntryInstructions = instructions; -} - -void QScxmlFinalState::setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlFinalState); - d->onExitInstructions = instructions; -} - -void QScxmlFinalState::onEntry(QEvent *event) -{ - Q_D(QScxmlFinalState); - - QFinalState::onEntry(event); - auto smp = QScxmlStateMachinePrivate::get(stateMachine()); - smp->m_executionEngine->execute(d->onEntryInstructions); -} - -void QScxmlFinalState::onExit(QEvent *event) -{ - Q_D(QScxmlFinalState); - - QFinalState::onExit(event); - QScxmlStateMachinePrivate::get(stateMachine())->m_executionEngine->execute(d->onExitInstructions); -} - -QScxmlBaseTransition::QScxmlBaseTransition(QState *sourceState, const QStringList &eventSelector) - : QAbstractTransition(*new QScxmlBaseTransitionPrivate, sourceState) -{ - Q_D(QScxmlBaseTransition); - d->eventSelector = eventSelector; -} - -QScxmlBaseTransition::QScxmlBaseTransition(QScxmlBaseTransitionPrivate &dd, QState *parent, - const QStringList &eventSelector) - : QAbstractTransition(dd, parent) -{ - Q_D(QScxmlBaseTransition); - d->eventSelector = eventSelector; -} - -QScxmlBaseTransition::~QScxmlBaseTransition() -{} - -QScxmlStateMachine *QScxmlBaseTransition::stateMachine() const { - if (QScxmlInternal::WrappedQStateMachine *t = qobject_cast<QScxmlInternal::WrappedQStateMachine *>(parent())) - return t->stateMachine(); - if (QState *s = sourceState()) - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(s->machine())->stateMachine(); - qCWarning(qscxmlLog) << "could not find Scxml::StateMachine in " << transitionLocation(); - return 0; -} - -QString QScxmlBaseTransition::transitionLocation() const { - if (QState *state = sourceState()) { - QString stateName = state->objectName(); - int transitionIndex = state->transitions().indexOf(const_cast<QScxmlBaseTransition *>(this)); - return QStringLiteral("transition #%1 in state %2").arg(transitionIndex).arg(stateName); - } - return QStringLiteral("unbound transition @%1").arg(reinterpret_cast<quintptr>(this)); -} - -bool QScxmlBaseTransition::eventTest(QEvent *event) -{ - Q_D(QScxmlBaseTransition); - - if (d->eventSelector.isEmpty()) - return true; - if (event->type() == QEvent::None) - return false; - Q_ASSERT(stateMachine()); - QString eventName = QScxmlStateMachinePrivate::get(stateMachine())->m_event.name(); - bool selected = false; - foreach (QString eventStr, d->eventSelector) { - if (eventStr == QStringLiteral("*")) { - selected = true; - break; - } - if (eventStr.endsWith(QStringLiteral(".*"))) - eventStr.chop(2); - if (eventName.startsWith(eventStr)) { - QChar nextC = QLatin1Char('.'); - if (eventName.size() > eventStr.size()) - nextC = eventName.at(eventStr.size()); - if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) { - selected = true; - break; - } - } - } - return selected; -} - -void QScxmlBaseTransition::onTransition(QEvent *event) -{ - Q_UNUSED(event); -} - -QScxmlTransitionPrivate::QScxmlTransitionPrivate() - : conditionalExp(QScxmlExecutableContent::NoEvaluator) - , instructionsOnTransition(QScxmlExecutableContent::NoInstruction) -{} - -QScxmlTransitionPrivate::~QScxmlTransitionPrivate() -{} - -QScxmlTransition::QScxmlTransition(QState *sourceState, const QStringList &eventSelector) - : QScxmlBaseTransition(*new QScxmlTransitionPrivate, sourceState, filterEmpty(eventSelector)) -{} - -QScxmlTransition::QScxmlTransition(const QStringList &eventSelector) - : QScxmlBaseTransition(*new QScxmlTransitionPrivate, Q_NULLPTR, filterEmpty(eventSelector)) -{} - -QScxmlTransition::~QScxmlTransition() -{} - -void QScxmlTransition::addTransitionTo(QScxmlState *state) -{ - state->addTransition(this); -} - -void QScxmlTransition::addTransitionTo(QScxmlStateMachine *stateMachine) -{ - QScxmlStateMachinePrivate::get(stateMachine)->m_qStateMachine->addTransition(this); -} - -bool QScxmlTransition::eventTest(QEvent *event) -{ - Q_D(QScxmlTransition); - -#ifdef DUMP_EVENT - if (auto edm = dynamic_cast<QScxmlEcmaScriptDataModel *>(stateMachine()->dataModel())) - qCDebug(qscxmlLog) << qPrintable(edm->engine()->evaluate(QLatin1String("JSON.stringify(_event)")).toString()); -#endif - - if (QScxmlBaseTransition::eventTest(event)) { - bool ok = true; - if (d->conditionalExp != QScxmlExecutableContent::NoEvaluator) - return stateMachine()->dataModel()->evaluateToBool(d->conditionalExp, &ok) && ok; - return true; - } - - return false; -} - -void QScxmlTransition::onTransition(QEvent *) -{ - Q_D(QScxmlTransition); - - QScxmlStateMachinePrivate::get(stateMachine())->m_executionEngine->execute(d->instructionsOnTransition); -} - -QScxmlStateMachine *QScxmlTransition::stateMachine() const { - // work around a bug in QStateMachine - if (QScxmlInternal::WrappedQStateMachine *t = qobject_cast<QScxmlInternal::WrappedQStateMachine *>(sourceState())) - return t->stateMachine(); - return qobject_cast<QScxmlInternal::WrappedQStateMachine *>(machine())->stateMachine(); -} - -void QScxmlTransition::setInstructionsOnTransition(QScxmlExecutableContent::ContainerId instructions) -{ - Q_D(QScxmlTransition); - d->instructionsOnTransition = instructions; -} - -void QScxmlTransition::setConditionalExpression(QScxmlExecutableContent::EvaluatorId evaluator) -{ - Q_D(QScxmlTransition); - d->conditionalExp = evaluator; -} - -QT_END_NAMESPACE diff --git a/src/scxml/qscxmlqstates.h b/src/scxml/qscxmlqstates.h deleted file mode 100644 index 7014861..0000000 --- a/src/scxml/qscxmlqstates.h +++ /dev/null @@ -1,222 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtScxml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SCXMLQSTATES_H -#define SCXMLQSTATES_H - -#include <QtScxml/qscxmlstatemachine.h> -#include <QtScxml/qscxmlinvokableservice.h> - -#include <QAbstractTransition> -#include <QFinalState> -#include <QHistoryState> -#include <QState> - -QT_BEGIN_NAMESPACE - -template<class T> -class QScxmlInvokeScxmlFactory: public QScxmlInvokableScxmlServiceFactory -{ -public: - QScxmlInvokeScxmlFactory(QScxmlExecutableContent::StringId invokeLocation, - QScxmlExecutableContent::EvaluatorId srcexpr, - QScxmlExecutableContent::StringId id, - QScxmlExecutableContent::StringId idPrefix, - QScxmlExecutableContent::StringId idlocation, - const QVector<QScxmlExecutableContent::StringId> &namelist, - bool doAutoforward, - const QVector<Param> ¶ms, - QScxmlExecutableContent::ContainerId finalize) - : QScxmlInvokableScxmlServiceFactory(invokeLocation, - srcexpr, - id, - idPrefix, - idlocation, - namelist, - doAutoforward, - params, - finalize) - {} - - QScxmlInvokableService *invoke(QScxmlStateMachine *parent) Q_DECL_OVERRIDE - { - bool ok = true; - auto srcexpr = calculateSrcexpr(parent, &ok); - if (!ok) - return Q_NULLPTR; - - if (!srcexpr.isEmpty()) - return loadAndInvokeDynamically(parent, srcexpr); - - return finishInvoke(new T, parent); - } -}; - -class QScxmlStatePrivate; -class Q_SCXML_EXPORT QScxmlState: public QState -{ - Q_OBJECT - -public: - QScxmlState(QState *parent = Q_NULLPTR); - QScxmlState(QScxmlStateMachine *parent); - ~QScxmlState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; - QString stateLocation() const; - - void setInitInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions); - void setInvokableServiceFactories(const QVector<QScxmlInvokableServiceFactory *>& factories); - -Q_SIGNALS: - void didEnter(); // TODO: REMOVE! - void willExit(); // TODO: REMOVE! - -protected: - void onEntry(QEvent * event) Q_DECL_OVERRIDE; - void onExit(QEvent * event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlState) -}; - -class QScxmlFinalStatePrivate; -class Q_SCXML_EXPORT QScxmlFinalState: public QFinalState -{ - Q_OBJECT - -public: - QScxmlFinalState(QState *parent = Q_NULLPTR); - QScxmlFinalState(QScxmlStateMachine *parent); - ~QScxmlFinalState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; - - QScxmlExecutableContent::ContainerId doneData() const; - void setDoneData(QScxmlExecutableContent::ContainerId doneData); - - void setOnEntryInstructions(QScxmlExecutableContent::ContainerId instructions); - void setOnExitInstructions(QScxmlExecutableContent::ContainerId instructions); - -protected: - void onEntry(QEvent * event) Q_DECL_OVERRIDE; - void onExit(QEvent * event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlFinalState) -}; - -class Q_SCXML_EXPORT QScxmlHistoryState: public QHistoryState -{ - Q_OBJECT - -public: - QScxmlHistoryState(QState *parent = Q_NULLPTR); - ~QScxmlHistoryState(); - - void setAsInitialStateFor(QScxmlState *state); - void setAsInitialStateFor(QScxmlStateMachine *stateMachine); - - QScxmlStateMachine *stateMachine() const; -}; - -class QScxmlBaseTransitionPrivate; -class Q_SCXML_EXPORT QScxmlBaseTransition: public QAbstractTransition -{ - Q_OBJECT - class Data; - -public: - QScxmlBaseTransition(QState * sourceState = Q_NULLPTR, - const QStringList &eventSelector = QStringList()); - ~QScxmlBaseTransition(); - - QScxmlStateMachine *stateMachine() const; - QString transitionLocation() const; - - bool eventTest(QEvent *event) Q_DECL_OVERRIDE; - -protected: - void onTransition(QEvent *event) Q_DECL_OVERRIDE; - - QScxmlBaseTransition(QScxmlBaseTransitionPrivate &dd, QState *parent, - const QStringList &eventSelector = QStringList()); - -private: - Q_DECLARE_PRIVATE(QScxmlBaseTransition) -}; - -class QScxmlTransitionPrivate; -class Q_SCXML_EXPORT QScxmlTransition: public QScxmlBaseTransition -{ - Q_OBJECT - -public: - QScxmlTransition(QState * sourceState = Q_NULLPTR, - const QStringList &eventSelector = QStringList()); - QScxmlTransition(const QStringList &eventSelector); - ~QScxmlTransition(); - - void addTransitionTo(QScxmlState *state); - void addTransitionTo(QScxmlStateMachine *stateMachine); - - bool eventTest(QEvent *event) Q_DECL_OVERRIDE; - QScxmlStateMachine *stateMachine() const; - - void setInstructionsOnTransition(QScxmlExecutableContent::ContainerId instructions); - void setConditionalExpression(QScxmlExecutableContent::EvaluatorId evaluator); - -protected: - void onTransition(QEvent *event) Q_DECL_OVERRIDE; - -private: - Q_DECLARE_PRIVATE(QScxmlTransition) -}; - -QT_END_NAMESPACE - -#endif // SCXMLQSTATES_H diff --git a/src/scxml/qscxmlqstates_p.h b/src/scxml/qscxmlqstates_p.h deleted file mode 100644 index 02b69b3..0000000 --- a/src/scxml/qscxmlqstates_p.h +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtScxml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SCXMLQSTATE_P_H -#define SCXMLQSTATE_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtScxml/qscxmlqstates.h> -#include <QtCore/private/qabstracttransition_p.h> -#include <QtCore/private/qstate_p.h> -#include <QtCore/private/qfinalstate_p.h> - -QT_BEGIN_NAMESPACE - -class QScxmlStatePrivate: public QStatePrivate -{ - Q_DECLARE_PUBLIC(QScxmlState) - -public: - static QScxmlStatePrivate *get(QScxmlState *s) { return s ? s->d_func() : nullptr; } - - QScxmlStatePrivate(); - ~QScxmlStatePrivate(); - - QScxmlExecutableContent::ContainerId initInstructions; - QScxmlExecutableContent::ContainerId onEntryInstructions; - QScxmlExecutableContent::ContainerId onExitInstructions; - QVector<QScxmlInvokableServiceFactory *> invokableServiceFactories; - QVector<QScxmlInvokableService *> invokedServices; - QVector<QScxmlInvokableService *> servicesWaitingToStart; -}; - -class QScxmlFinalStatePrivate: public QFinalStatePrivate -{ - Q_DECLARE_PUBLIC(QScxmlFinalState) - -public: - static QScxmlFinalStatePrivate *get(QScxmlFinalState *s) { return s ? s->d_func() : nullptr; } - - QScxmlFinalStatePrivate(); - ~QScxmlFinalStatePrivate(); - - QScxmlExecutableContent::ContainerId doneData; - QScxmlExecutableContent::ContainerId onEntryInstructions; - QScxmlExecutableContent::ContainerId onExitInstructions; -}; - -class QScxmlBaseTransitionPrivate: public QAbstractTransitionPrivate -{ - Q_DECLARE_PUBLIC(QScxmlBaseTransition) - -public: - QStringList eventSelector; -}; - -class QScxmlTransitionPrivate: public QScxmlBaseTransitionPrivate -{ -public: - QScxmlTransitionPrivate(); - ~QScxmlTransitionPrivate(); - - QScxmlExecutableContent::EvaluatorId conditionalExp; - QScxmlExecutableContent::ContainerId instructionsOnTransition; -}; - -QT_END_NAMESPACE - -#endif // SCXMLQSTATE_P_H diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index f945c55..31e620c 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -41,7 +41,6 @@ #include "qscxmlexecutablecontent_p.h" #include "qscxmlevent_p.h" #include "qscxmlinvokableservice.h" -#include "qscxmlqstates_p.h" #include "qscxmldatamodel_p.h" #include <QAbstractState> @@ -50,11 +49,9 @@ #include <QHash> #include <QJSEngine> #include <QLoggingCategory> -#include <QState> #include <QString> #include <QTimer> - -#include <QtCore/private/qstatemachine_p.h> +#include <QThread> #include <functional> @@ -63,115 +60,6 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qscxmlLog, "qt.scxml.statemachine") Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine") -namespace QScxmlInternal { -class WrappedQStateMachinePrivate: public QStateMachinePrivate -{ - Q_DECLARE_PUBLIC(WrappedQStateMachine) - -public: - WrappedQStateMachinePrivate(QScxmlStateMachine *stateMachine) - : m_stateMachine(stateMachine) - , m_queuedEvents(Q_NULLPTR) - {} - ~WrappedQStateMachinePrivate() - { - if (m_queuedEvents) { - foreach (const QueuedEvent &qt, *m_queuedEvents) { - delete qt.event; - } - - delete m_queuedEvents; - } - } - - int eventIdForDelayedEvent(const QString &sendId); - - QScxmlStateMachine *stateMachine() const - { return m_stateMachine; } - - QScxmlStateMachinePrivate *stateMachinePrivate() const - { return QScxmlStateMachinePrivate::get(stateMachine()); } - -protected: // overrides for QStateMachinePrivate: - void noMicrostep() Q_DECL_OVERRIDE; - void processedPendingEvents(bool didChange) Q_DECL_OVERRIDE; - void beginMacrostep() Q_DECL_OVERRIDE; - void endMacrostep(bool didChange) Q_DECL_OVERRIDE; - - void enterStates(QEvent *event, const QList<QAbstractState*> &exitedStates_sorted, - const QList<QAbstractState*> &statesToEnter_sorted, - const QSet<QAbstractState*> &statesForDefaultEntry, - QHash<QAbstractState *, QVector<QPropertyAssignment> > &propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , const QList<QAbstractAnimation*> &selectedAnimations -# endif - ) Q_DECL_OVERRIDE; - void exitStates(QEvent *event, const QList<QAbstractState *> &statesToExit_sorted, - const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) Q_DECL_OVERRIDE; - - void exitInterpreter() Q_DECL_OVERRIDE; - - void emitStateFinished(QState *forState, QFinalState *guiltyState) Q_DECL_OVERRIDE; - void startupHook() Q_DECL_OVERRIDE; - -public: // fields - QScxmlStateMachine *m_stateMachine; - - struct QueuedEvent - { - QueuedEvent(QEvent *event = Q_NULLPTR, QStateMachine::EventPriority priority = QStateMachine::NormalPriority) - : event(event) - , priority(priority) - {} - - QEvent *event; - QStateMachine::EventPriority priority; - }; - QVector<QueuedEvent> *m_queuedEvents; -}; - -WrappedQStateMachine::WrappedQStateMachine(QScxmlStateMachine *parent) - : QStateMachine(*new WrappedQStateMachinePrivate(parent), parent) -{} - -WrappedQStateMachine::WrappedQStateMachine(WrappedQStateMachinePrivate &dd, QScxmlStateMachine *parent) - : QStateMachine(dd, parent) -{} - -QScxmlStateMachine *WrappedQStateMachine::stateMachine() const -{ - Q_D(const WrappedQStateMachine); - - return d->stateMachine(); -} - -QScxmlStateMachinePrivate *WrappedQStateMachine::stateMachinePrivate() -{ - Q_D(const WrappedQStateMachine); - - return d->stateMachinePrivate(); -} -} // Internal namespace - -/*! - * \class QScxmlEventFilter - * \brief The QScxmlEventFilter class is an event filter for an SCXML state machine. - * \since 5.7 - * \inmodule QtScxml - * - * An event filter can be used to intercept events generated by an SCXML state machine. By default, - * the QScxmlStateMachine will have an event filter that will intercept events that are marked as - * external and that have the type \c qt:signal to emit signals. - * - * \sa QScxmlStateMachine - */ - -/*! - * Destroys the SCXML event filter. - */ -QScxmlEventFilter::~QScxmlEventFilter() -{} - /*! * \fn QScxmlEventFilter::handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) * @@ -210,7 +98,7 @@ QScxmlEventFilter::~QScxmlEventFilter() \fn QScxmlStateMachine::eventOccurred(const QScxmlEvent &event) This signal is emitted when the SCXML event \a event occurs. This signal is - emitted for all events. + emitted for all external events. \sa externalEventOccurred() */ @@ -225,97 +113,108 @@ QScxmlEventFilter::~QScxmlEventFilter() \sa eventOccurred() */ -QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0); +void QScxmlInternal::EventLoopHook::queueProcessEvents() +{ + if (smp->m_isProcessingEvents) + return; + + QMetaObject::invokeMethod(this, "doProcessEvents", Qt::QueuedConnection); +} + +void QScxmlInternal::EventLoopHook::doProcessEvents() +{ + smp->processEvents(); +} + +void QScxmlInternal::EventLoopHook::timerEvent(QTimerEvent *timerEvent) +{ + const int timerId = timerEvent->timerId(); + for (auto it = smp->m_delayedEvents.begin(), eit = smp->m_delayedEvents.end(); it != eit; ++it) { + if (it->first == timerId) { + QScxmlEvent *scxmlEvent = it->second; + smp->m_delayedEvents.erase(it); + smp->routeEvent(scxmlEvent); + return; + } + } +} +QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0); QScxmlStateMachinePrivate::QScxmlStateMachinePrivate() : QObjectPrivate() , m_sessionId(QScxmlStateMachine::generateSessionId(QStringLiteral("session-"))) , m_isInvoked(false) , m_isInitialized(false) + , m_isProcessingEvents(false) , m_dataModel(Q_NULLPTR) , m_loader(&m_defaultLoader) - , m_dataBinding(QScxmlStateMachine::EarlyBinding) , m_executionEngine(Q_NULLPTR) , m_tableData(Q_NULLPTR) - , m_qStateMachine(Q_NULLPTR) - , m_eventFilter(Q_NULLPTR) , m_parentStateMachine(Q_NULLPTR) + , m_eventLoopHook(this) {} QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate() { - qDeleteAll(m_invokedServices); + for (const InvokedService &invokedService : m_invokedServices) + delete invokedService.service; + qDeleteAll(m_cachedFactories); delete m_executionEngine; } -void QScxmlStateMachinePrivate::init() +QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData() { - Q_Q(QScxmlStateMachine); - m_executionEngine = new QScxmlExecutableContent::QScxmlExecutionEngine(q); - setQStateMachine(new QScxmlInternal::WrappedQStateMachine(q)); - QObject::connect(m_qStateMachine, &QStateMachine::runningChanged, - q, &QScxmlStateMachine::runningChanged); - QObject::connect(m_qStateMachine, &QStateMachine::finished, - q, &QScxmlStateMachine::finished); - - // The final state is also a stable state. - QObject::connect(m_qStateMachine, &QStateMachine::finished, - q, &QScxmlStateMachine::reachedStableState); + if (m_parserData.isNull()) + m_parserData.reset(new ParserData); + return m_parserData.data(); } -void QScxmlStateMachinePrivate::setQStateMachine(QScxmlInternal::WrappedQStateMachine *stateMachine) +void QScxmlStateMachinePrivate::addService(int invokingState) { - m_qStateMachine = stateMachine; -} + Q_Q(QScxmlStateMachine); -static QAbstractState *findState(const QString &scxmlName, QStateMachine *parent) -{ - QList<QObject *> worklist; - worklist.reserve(parent->children().size() + parent->configuration().size()); - worklist.append(parent); + const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds; + if (arrayId == StateTable::InvalidIndex) + return; - while (!worklist.isEmpty()) { - QObject *obj = worklist.takeLast(); - if (QAbstractState *state = qobject_cast<QAbstractState *>(obj)) { - if (state->objectName() == scxmlName) - return state; - } - worklist.append(obj->children()); + const auto &ids = m_stateTable->array(arrayId); + for (int id : ids) { + auto factory = serviceFactory(id); + auto service = factory->invoke(q); + if (service == nullptr) + continue; // service failed to start + const QString serviceName = service->name(); + m_invokedServices[size_t(id)] = { invokingState, service, serviceName }; + emitServiceChanged(id, service); + service->start(); } - - return Q_NULLPTR; } -QAbstractState *QScxmlStateMachinePrivate::stateByScxmlName(const QString &scxmlName) +void QScxmlStateMachinePrivate::removeService(int invokingState) { - return findState(scxmlName, m_qStateMachine); -} - -QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData() -{ - if (m_parserData.isNull()) - m_parserData.reset(new ParserData); - return m_parserData.data(); -} + const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds; + if (arrayId == StateTable::InvalidIndex) + return; -void QScxmlStateMachinePrivate::addService(QScxmlInvokableService *service) -{ - Q_Q(QScxmlStateMachine); - Q_ASSERT(!m_invokedServices.contains(service)); - m_invokedServices.append(service); - q->setService(service->name(), service); + for (size_t i = 0, ei = m_invokedServices.size(); i != ei; ++i) { + auto &it = m_invokedServices[i]; + QScxmlInvokableService *service = it.service; + if (it.invokingState == invokingState && service != nullptr) { + it.service = nullptr; + delete service; + emitServiceChanged(int(i), nullptr); + } + } } -bool QScxmlStateMachinePrivate::removeService(QScxmlInvokableService *service) +QScxmlInvokableServiceFactory *QScxmlStateMachinePrivate::serviceFactory(int id) { - Q_Q(QScxmlStateMachine); - Q_ASSERT(m_invokedServices.contains(service)); - if (m_invokedServices.removeOne(service)) { - q->setService(service->name(), Q_NULLPTR); - return true; - } - return false; + Q_ASSERT(id <= m_stateTable->maxServiceId && id >= 0); + QScxmlInvokableServiceFactory *& factory = m_cachedFactories[size_t(id)]; + if (factory == nullptr) + factory = m_tableData->serviceFactory(id); + return factory; } bool QScxmlStateMachinePrivate::executeInitialSetup() @@ -342,11 +241,14 @@ void QScxmlStateMachinePrivate::routeEvent(QScxmlEvent *event) } else if (origin.startsWith(QStringLiteral("#_")) && origin != QStringLiteral("#_internal")) { // route to children auto originId = origin.midRef(2); - foreach (QScxmlInvokableService *service, m_invokedServices) { + for (auto invokedService : m_invokedServices) { + auto service = invokedService.service; + if (service == nullptr) + continue; if (service->id() == originId) { qCDebug(qscxmlLog) << q << "routing event" << event->name() - << "from" << q->name() - << "to parent" << service->id(); + << "from" << q->name() + << "to child" << service->id(); service->postEvent(new QScxmlEvent(*event)); } } @@ -360,17 +262,67 @@ void QScxmlStateMachinePrivate::postEvent(QScxmlEvent *event) { Q_Q(QScxmlStateMachine); - QStateMachine::EventPriority priority = - event->eventType() == QScxmlEvent::ExternalEvent ? QStateMachine::NormalPriority - : QStateMachine::HighPriority; + if (!event->name().startsWith(QStringLiteral("done.invoke."))) { + for (auto invokedService : m_invokedServices) { + auto service = invokedService.service; + if (service == nullptr) + continue; + if (event->invokeId() == service->id()) { + setEvent(event); + service->finalize(); + resetEvent(); + } + if (service->autoforward()) { + qCDebug(qscxmlLog) << q << "auto-forwarding event" << event->name() + << "from" << q->name() + << "to child" << service->id(); + service->postEvent(new QScxmlEvent(*event)); + } + } + } + + if (event->eventType() == QScxmlEvent::ExternalEvent) + emit q->eventOccurred(*event); + + if (event->originType() == QLatin1String("qt:signal")) + emit q->externalEventOccurred(*event); + + const int signalIndex = m_tableData->signalIndexForEvent(event->name()); + if (signalIndex != -1) { + emitSignalForEvent(signalIndex, event->data()); + delete event; + return; + } - if (m_qStateMachine->isRunning()) { - qCDebug(qscxmlLog) << q << "posting event" << event->name(); - m_qStateMachine->postEvent(event, priority); + if (event->eventType() == QScxmlEvent::ExternalEvent) { + qCDebug(qscxmlLog) << q << "posting external event" << event->name(); + m_externalQueue.enqueue(event); } else { - qCDebug(qscxmlLog) << q << "queueing event" << event->name(); - m_qStateMachine->queueEvent(event, priority); + qCDebug(qscxmlLog) << q << "posting internal event" << event->name(); + m_internalQueue.enqueue(event); + } + + m_eventLoopHook.queueProcessEvents(); +} + +void QScxmlStateMachinePrivate::submitDelayedEvent(QScxmlEvent *event) +{ + Q_ASSERT(event); + Q_ASSERT(event->delay() > 0); + + const int timerId = m_eventLoopHook.startTimer(event->delay()); + if (timerId == 0) { + qWarning("QScxmlStateMachinePrivate::submitDelayedEvent: " + "failed to start timer for event '%s' (%p)", + qPrintable(event->name()), event); + delete event; + return; } + m_delayedEvents.push_back(std::make_pair(timerId, event)); + + qCDebug(qscxmlLog) << q_func() + << ": delayed event" << event->name() + << "(" << event << ") got id:" << timerId; } /*! @@ -391,6 +343,781 @@ void QScxmlStateMachinePrivate::submitError(const QString &type, const QString & q->submitEvent(QScxmlEventBuilder::errorEvent(q, type, msg, sendid)); } +void QScxmlStateMachinePrivate::start() +{ + if (m_stateTable->binding == StateTable::LateBinding) + m_isFirstStateEntry.resize(m_stateTable->stateCount, true); + + m_runningState = Starting; + Q_ASSERT(m_stateTable->initialTransition != StateTable::InvalidIndex); +} + +void QScxmlStateMachinePrivate::pause() +{ + if (isRunnable() && !isPaused()) + m_runningState = Paused; +} + +void QScxmlStateMachinePrivate::processEvents() +{ + if (m_isProcessingEvents || (!isRunnable() && !isPaused())) + return; + + m_isProcessingEvents = true; + + Q_Q(QScxmlStateMachine); + qCDebug(qscxmlLog) << q_func() << "starting macrostep"; + + while (isRunnable() && !isPaused()) { + if (m_runningState == Starting) { + enterStates({m_stateTable->initialTransition}); + if (m_runningState == Starting) + m_runningState = Running; + continue; + } + + OrderedSet enabledTransitions; + std::vector<int> configurationInDocumentOrder = m_configuration.list(); + std::sort(configurationInDocumentOrder.begin(), configurationInDocumentOrder.end()); + selectTransitions(enabledTransitions, configurationInDocumentOrder, nullptr); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } else if (!m_internalQueue.isEmpty()) { + auto event = m_internalQueue.dequeue(); + setEvent(event); + selectTransitions(enabledTransitions, configurationInDocumentOrder, event); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } + resetEvent(); + delete event; + } else if (!m_externalQueue.isEmpty()) { + auto event = m_externalQueue.dequeue(); + setEvent(event); + selectTransitions(enabledTransitions, configurationInDocumentOrder, event); + if (!enabledTransitions.isEmpty()) { + microstep(enabledTransitions); + } + resetEvent(); + delete event; + } else { + // nothing to do, so: + break; + } + } + + if (!m_statesToInvoke.empty()) { + for (int stateId : m_statesToInvoke) + addService(stateId); + m_statesToInvoke.clear(); + } + + qCDebug(qscxmlLog) << q_func() + << "finished macrostep, runnable:" << isRunnable() + << "paused:" << isPaused(); + emit q->reachedStableState(); + if (!isRunnable() && !isPaused()) { + exitInterpreter(); + emit q->finished(); + } + + m_isProcessingEvents = false; +} + +void QScxmlStateMachinePrivate::setEvent(QScxmlEvent *event) +{ + Q_ASSERT(event); + m_dataModel->setScxmlEvent(*event); +} + +void QScxmlStateMachinePrivate::resetEvent() +{ + m_dataModel->setScxmlEvent(QScxmlEvent()); +} + +void QScxmlStateMachinePrivate::emitStateActive(int stateIndex, bool active) +{ + Q_Q(QScxmlStateMachine); + void *args[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&active)) }; + QMetaObject::activate(q, q->metaObject(), stateIndex, args); +} + +void QScxmlStateMachinePrivate::emitServiceChanged(int machineIndex, + QScxmlInvokableService *service) +{ + Q_Q(QScxmlStateMachine); + void *args[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&service)) }; + QMetaObject::activate(q, q->metaObject(), machineIndex + m_stateTable->stateCount, args); +} + +void QScxmlStateMachinePrivate::emitSignalForEvent(int signalIndex, const QVariant &data) +{ + Q_Q(QScxmlStateMachine); + void *args[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&data)) }; + QMetaObject::activate(q, q->metaObject(), + signalIndex + m_stateTable->stateCount + m_stateTable->maxServiceId + 1, + args); +} + +QStringList QScxmlStateMachinePrivate::stateNames(const std::vector<int> &stateIndexes) const +{ + QStringList names; + for (int idx : stateIndexes) + names.append(m_tableData->string(m_stateTable->state(idx).name)); + return names; +} + +std::vector<int> QScxmlStateMachinePrivate::historyStates(int stateIdx) const { + const StateTable::Array kids = m_stateTable->array(m_stateTable->state(stateIdx).childStates); + std::vector<int> res; + if (!kids.isValid()) return res; + for (int k : kids) { + if (m_stateTable->state(k).isHistoryState()) + res.push_back(k); + } + return res; +} + +void QScxmlStateMachinePrivate::exitInterpreter() +{ + qCDebug(qscxmlLog) << q_func() << "exiting SCXML processing"; + + for (auto it : m_delayedEvents) { + m_eventLoopHook.killTimer(it.first); + delete it.second; + } + m_delayedEvents.clear(); + + auto statesToExitSorted = m_configuration.list(); + std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>()); + for (int stateIndex : statesToExitSorted) { + const auto &state = m_stateTable->state(stateIndex); + if (state.exitInstructions != StateTable::InvalidIndex) { + m_executionEngine->execute(state.exitInstructions); + } + removeService(stateIndex); + if (state.type == StateTable::State::Final && state.parentIsScxmlElement()) { + returnDoneEvent(state.doneData); + } + } +} + +void QScxmlStateMachinePrivate::returnDoneEvent(QScxmlExecutableContent::ContainerId doneData) +{ + Q_Q(QScxmlStateMachine); + + m_executionEngine->execute(doneData, QVariant()); + if (m_isInvoked) { + auto e = new QScxmlEvent; + e->setName(QStringLiteral("done.invoke.") + q->sessionId()); + e->setInvokeId(q->sessionId()); + QScxmlStateMachinePrivate::get(m_parentStateMachine)->postEvent(e); + } +} + +bool QScxmlStateMachinePrivate::nameMatch(const StateTable::Array &patterns, + QScxmlEvent *event) const +{ + if (event->type() == QEvent::None) + return false; + + const QString eventName = event->name(); + bool selected = false; + for (int eventSelectorIter = 0; eventSelectorIter < patterns.size(); ++eventSelectorIter) { + QString eventStr = m_tableData->string(patterns[eventSelectorIter]); + if (eventStr == QStringLiteral("*")) { + selected = true; + break; + } + if (eventStr.endsWith(QStringLiteral(".*"))) + eventStr.chop(2); + if (eventName.startsWith(eventStr)) { + QChar nextC = QLatin1Char('.'); + if (eventName.size() > eventStr.size()) + nextC = eventName.at(eventStr.size()); + if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) { + selected = true; + break; + } + } + } + return selected; +} + +void QScxmlStateMachinePrivate::selectTransitions(OrderedSet &enabledTransitions, + const std::vector<int> &configInDocumentOrder, + QScxmlEvent *event) const +{ + if (event == nullptr) { + qCDebug(qscxmlLog) << q_func() << "selectEventlessTransitions"; + } else { + qCDebug(qscxmlLog) << q_func() << "selectTransitions with event" + << QScxmlEventPrivate::debugString(event).constData(); + } + + std::vector<int> states; + states.reserve(16); + for (int configStateIdx : configInDocumentOrder) { + if (m_stateTable->state(configStateIdx).isAtomic()) { + states.clear(); + states.push_back(configStateIdx); + getProperAncestors(&states, configStateIdx, -1); + for (int stateIdx : states) { + bool finishedWithThisConfigState = false; + + if (stateIdx == -1) { + // the state machine has no transitions (other than the initial one, which has + // already been taken at this point) + continue; + } + const auto &state = m_stateTable->state(stateIdx); + const StateTable::Array transitions = m_stateTable->array(state.transitions); + if (!transitions.isValid()) + continue; + std::vector<int> sortedTransitions(transitions.size(), -1); + std::copy(transitions.begin(), transitions.end(), sortedTransitions.begin()); + for (int transitionIndex : sortedTransitions) { + const StateTable::Transition &t = m_stateTable->transition(transitionIndex); + bool enabled = false; + if (event == nullptr) { + if (t.events == -1) { + if (t.condition == -1) { + enabled = true; + } else { + bool ok = false; + enabled = m_dataModel->evaluateToBool(t.condition, &ok) && ok; + } + } + } else { + if (t.events != -1 && nameMatch(m_stateTable->array(t.events), event)) { + if (t.condition == -1) { + enabled = true; + } else { + bool ok = false; + enabled = m_dataModel->evaluateToBool(t.condition, &ok) && ok; + } + } + } + if (enabled) { + enabledTransitions.add(transitionIndex); + finishedWithThisConfigState = true; + break; // stop iterating over transitions + } + } + + if (finishedWithThisConfigState) + break; // stop iterating over ancestors + } + } + } + if (!enabledTransitions.isEmpty()) + removeConflictingTransitions(&enabledTransitions); +} + +void QScxmlStateMachinePrivate::removeConflictingTransitions(OrderedSet *enabledTransitions) const +{ + Q_ASSERT(enabledTransitions); + + auto sortedTransitions = enabledTransitions->takeList(); + std::sort(sortedTransitions.begin(), sortedTransitions.end(), [this](int t1, int t2) -> bool { + auto descendantDepth = [this](int state, int ancestor)->int { + int depth = 0; + for (int it = state; it != -1; it = m_stateTable->state(it).parent) { + if (it == ancestor) + break; + ++depth; + } + return depth; + }; + + const auto &s1 = m_stateTable->transition(t1).source; + const auto &s2 = m_stateTable->transition(t2).source; + if (s1 == s2) { + return t1 < t2; + } else if (isDescendant(s1, s2)) { + return true; + } else if (isDescendant(s2, s1)) { + return false; + } else { + const int lcca = findLCCA({ s1, s2 }); + const int s1Depth = descendantDepth(s1, lcca); + const int s2Depth = descendantDepth(s2, lcca); + if (s1Depth == s2Depth) + return s1 < s2; + else + return s1Depth > s2Depth; + } + }); + + OrderedSet filteredTransitions; + for (int t1 : sortedTransitions) { + OrderedSet transitionsToRemove; + bool t1Preempted = false; + OrderedSet exitSetT1; + computeExitSet({t1}, exitSetT1); + const int source1 = m_stateTable->transition(t1).source; + for (int t2 : filteredTransitions) { + OrderedSet exitSetT2; + computeExitSet({t2}, exitSetT2); + if (exitSetT1.intersectsWith(exitSetT2)) { + const int source2 = m_stateTable->transition(t2).source; + if (isDescendant(source1, source2)) { + transitionsToRemove.add(t2); + } else { + t1Preempted = true; + break; + } + } + } + if (!t1Preempted) { + for (int t3 : transitionsToRemove) { + filteredTransitions.remove(t3); + } + filteredTransitions.add(t1); + } + } + *enabledTransitions = filteredTransitions; +} + +void QScxmlStateMachinePrivate::getProperAncestors(std::vector<int> *ancestors, int state1, + int state2) const +{ + Q_ASSERT(ancestors); + + if (state1 == -1) { + return; + } + + int parent = state1; + do { + parent = m_stateTable->state(parent).parent; + if (parent == state2) { + break; + } + ancestors->push_back(parent); + } while (parent != -1); +} + +void QScxmlStateMachinePrivate::microstep(const OrderedSet &enabledTransitions) +{ + if (qscxmlLog().isDebugEnabled()) { + qCDebug(qscxmlLog) << q_func() + << "starting microstep, configuration:" + << stateNames(m_configuration.list()); + qCDebug(qscxmlLog) << q_func() << "enabled transitions:"; + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + QString from = QStringLiteral("(none)"); + if (transition.source != StateTable::InvalidIndex) + from = m_tableData->string(m_stateTable->state(transition.source).name); + QStringList to; + if (transition.targets == StateTable::InvalidIndex) { + to.append(QStringLiteral("(none)")); + } else { + for (int t : m_stateTable->array(transition.targets)) + to.append(m_tableData->string(m_stateTable->state(t).name)); + } + qCDebug(qscxmlLog) << q_func() << "\t" << t << ":" << from << "->" + << to.join(QLatin1Char(',')); + } + } + + exitStates(enabledTransitions); + executeTransitionContent(enabledTransitions); + enterStates(enabledTransitions); + + qCDebug(qscxmlLog) << q_func() << "finished microstep, configuration:" + << stateNames(m_configuration.list()); +} + +void QScxmlStateMachinePrivate::exitStates(const OrderedSet &enabledTransitions) +{ + OrderedSet statesToExit; + computeExitSet(enabledTransitions, statesToExit); + auto statesToExitSorted = statesToExit.takeList(); + std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>()); + qCDebug(qscxmlLog) << q_func() << "exiting states" << stateNames(statesToExitSorted); + for (int s : statesToExitSorted) { + const auto &state = m_stateTable->state(s); + if (state.serviceFactoryIds != StateTable::InvalidIndex) + m_statesToInvoke.remove(s); + } + for (int s : statesToExitSorted) { + for (int h : historyStates(s)) { + const auto &hState = m_stateTable->state(h); + QVector<int> history; + + for (int s0 : m_configuration) { + const auto &s0State = m_stateTable->state(s0); + if (hState.type == StateTable::State::DeepHistory) { + if (s0State.isAtomic() && isDescendant(s0, s)) + history.append(s0); + } else { + if (s0State.parent == s) + history.append(s0); + } + } + + m_historyValue[h] = history; + } + } + for (int s : statesToExitSorted) { + const auto &state = m_stateTable->state(s); + if (state.exitInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.exitInstructions); + m_configuration.remove(s); + emitStateActive(s, false); + removeService(s); + } +} + +void QScxmlStateMachinePrivate::computeExitSet(const OrderedSet &enabledTransitions, + OrderedSet &statesToExit) const +{ + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.targets == StateTable::InvalidIndex) { + // nothing to do here: there is no exit set + } else { + const int domain = getTransitionDomain(t); + for (int s : m_configuration) { + if (isDescendant(s, domain)) + statesToExit.add(s); + } + } + } +} + +void QScxmlStateMachinePrivate::executeTransitionContent(const OrderedSet &enabledTransitions) +{ + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.transitionInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(transition.transitionInstructions); + } +} + +void QScxmlStateMachinePrivate::enterStates(const OrderedSet &enabledTransitions) +{ + Q_Q(QScxmlStateMachine); + + OrderedSet statesToEnter, statesForDefaultEntry; + HistoryContent defaultHistoryContent; + computeEntrySet(enabledTransitions, &statesToEnter, &statesForDefaultEntry, + &defaultHistoryContent); + auto sortedStates = statesToEnter.takeList(); + std::sort(sortedStates.begin(), sortedStates.end()); + qCDebug(qscxmlLog) << q_func() << "entering states" << stateNames(sortedStates); + for (int s : sortedStates) { + const auto &state = m_stateTable->state(s); + m_configuration.add(s); + if (state.serviceFactoryIds != StateTable::InvalidIndex) + m_statesToInvoke.insert(s); + if (m_stateTable->binding == StateTable::LateBinding && m_isFirstStateEntry[s]) { + if (state.initInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.initInstructions); + m_isFirstStateEntry[s] = false; + } + if (state.entryInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(state.entryInstructions); + if (statesForDefaultEntry.contains(s)) { + const auto &initialTransition = m_stateTable->transition(state.initialTransition); + if (initialTransition.transitionInstructions != StateTable::InvalidIndex) + m_executionEngine->execute(initialTransition.transitionInstructions); + } + const int dhc = defaultHistoryContent.value(s); + if (dhc != StateTable::InvalidIndex) + m_executionEngine->execute(dhc); + if (state.type == StateTable::State::Final) { + if (state.parentIsScxmlElement()) { + m_runningState = Finished; + } else { + const auto &parent = m_stateTable->state(state.parent); + m_executionEngine->execute(state.doneData, m_tableData->string(parent.name)); + if (parent.parent != StateTable::InvalidIndex) { + const auto &grandParent = m_stateTable->state(parent.parent); + if (grandParent.isParallel()) { + if (allInFinalStates(getChildStates(grandParent))) { + auto e = new QScxmlEvent; + e->setEventType(QScxmlEvent::InternalEvent); + e->setName(QStringLiteral("done.state.") + + m_tableData->string(grandParent.name)); + q->submitEvent(e); + } + } + } + } + } + } + for (int s : sortedStates) + emitStateActive(s, true); +} + +void QScxmlStateMachinePrivate::computeEntrySet(const OrderedSet &enabledTransitions, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + for (int t : enabledTransitions) { + const auto &transition = m_stateTable->transition(t); + if (transition.targets == StateTable::InvalidIndex) + // targetless transition, so nothing to do + continue; + for (int s : m_stateTable->array(transition.targets)) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + auto ancestor = getTransitionDomain(t); + OrderedSet targets; + getEffectiveTargetStates(&targets, t); + for (auto s : targets) + addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } +} + +void QScxmlStateMachinePrivate::addDescendantStatesToEnter( + int stateIndex, OrderedSet *statesToEnter, OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + const auto &state = m_stateTable->state(stateIndex); + if (state.isHistoryState()) { + HistoryValues::const_iterator historyValueIter = m_historyValue.find(stateIndex); + if (historyValueIter != m_historyValue.end()) { + auto historyValue = historyValueIter.value(); + for (int s : historyValue) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + for (int s : historyValue) + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } else { + const auto transitionIdx = m_stateTable->array(state.transitions)[0]; + const auto &defaultHistoryTransition = m_stateTable->transition(transitionIdx); + defaultHistoryContent->operator[](state.parent) = + defaultHistoryTransition.transitionInstructions; + StateTable::Array targetStates = m_stateTable->array(defaultHistoryTransition.targets); + for (int s : targetStates) + addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + for (int s : targetStates) + addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } else { + statesToEnter->add(stateIndex); + if (state.isCompound()) { + statesForDefaultEntry->add(stateIndex); + if (state.initialTransition != StateTable::InvalidIndex) { + auto initialTransition = m_stateTable->transition(state.initialTransition); + auto initialTransitionTargets = m_stateTable->array(initialTransition.targets); + for (int targetStateIndex : initialTransitionTargets) + addDescendantStatesToEnter(targetStateIndex, statesToEnter, + statesForDefaultEntry, defaultHistoryContent); + for (int targetStateIndex : initialTransitionTargets) + addAncestorStatesToEnter(targetStateIndex, stateIndex, statesToEnter, + statesForDefaultEntry, defaultHistoryContent); + } + } else { + if (state.isParallel()) { + for (int child : getChildStates(state)) { + if (!hasDescendant(*statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } + } + } +} + +void QScxmlStateMachinePrivate::addAncestorStatesToEnter( + int stateIndex, int ancestorIndex, OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, HistoryContent *defaultHistoryContent) const +{ + Q_ASSERT(statesToEnter); + Q_ASSERT(statesForDefaultEntry); + Q_ASSERT(defaultHistoryContent); + + std::vector<int> ancestors; + getProperAncestors(&ancestors, stateIndex, ancestorIndex); + for (int anc : ancestors) { + if (anc == -1) { + // we can't enter the state machine itself, so: + continue; + } + statesToEnter->add(anc); + const auto &ancState = m_stateTable->state(anc); + if (ancState.isParallel()) { + for (int child : getChildStates(ancState)) { + if (!hasDescendant(*statesToEnter, child)) + addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry, + defaultHistoryContent); + } + } + } +} + +std::vector<int> QScxmlStateMachinePrivate::getChildStates( + const QScxmlExecutableContent::StateTable::State &state) const +{ + std::vector<int> childStates; + auto kids = m_stateTable->array(state.childStates); + if (kids.isValid()) { + childStates.reserve(kids.size()); + for (int kiddo : kids) { + switch (m_stateTable->state(kiddo).type) { + case StateTable::State::Normal: + case StateTable::State::Final: + case StateTable::State::Parallel: + childStates.push_back(kiddo); + break; + default: + break; + } + } + } + return childStates; +} + +bool QScxmlStateMachinePrivate::hasDescendant(const OrderedSet &statesToEnter, int childIdx) const +{ + for (int s : statesToEnter) { + if (isDescendant(s, childIdx)) + return true; + } + return false; +} + +bool QScxmlStateMachinePrivate::allDescendants(const OrderedSet &statesToEnter, int childdx) const +{ + for (int s : statesToEnter) { + if (!isDescendant(s, childdx)) + return false; + } + return true; +} + +bool QScxmlStateMachinePrivate::isDescendant(int state1, int state2) const +{ + int parent = state1; + do { + parent = m_stateTable->state(parent).parent; + if (parent == state2) + return true; + } while (parent != -1); + return false; +} + +bool QScxmlStateMachinePrivate::allInFinalStates(const std::vector<int> &states) const +{ + if (states.empty()) + return false; + + for (int idx : states) { + if (!isInFinalState(idx)) + return false; + } + + return true; +} + +bool QScxmlStateMachinePrivate::someInFinalStates(const std::vector<int> &states) const +{ + for (int stateIndex : states) { + const auto &state = m_stateTable->state(stateIndex); + if (state.type == StateTable::State::Final && m_configuration.contains(stateIndex)) + return true; + } + return false; +} + +bool QScxmlStateMachinePrivate::isInFinalState(int stateIndex) const +{ + const auto &state = m_stateTable->state(stateIndex); + if (state.isCompound()) + return someInFinalStates(getChildStates(state)) && m_configuration.contains(stateIndex); + else if (state.isParallel()) + return allInFinalStates(getChildStates(state)); + else + return false; +} + +int QScxmlStateMachinePrivate::getTransitionDomain(int transitionIndex) const +{ + const auto &transition = m_stateTable->transition(transitionIndex); + if (transition.source == -1) + //oooh, we have the initial transition of the state machine. + return -1; + + OrderedSet tstates; + getEffectiveTargetStates(&tstates, transitionIndex); + if (tstates.isEmpty()) { + return StateTable::InvalidIndex; + } else { + const auto &sourceState = m_stateTable->state(transition.source); + if (transition.type == StateTable::Transition::Internal + && sourceState.isCompound() + && allDescendants(tstates, transition.source)) { + return transition.source; + } else { + tstates.add(transition.source); + return findLCCA(std::move(tstates)); + } + } +} + +int QScxmlStateMachinePrivate::findLCCA(OrderedSet &&states) const +{ + std::vector<int> ancestors; + const int head = *states.begin(); + OrderedSet tail(std::move(states)); + tail.removeHead(); + + getProperAncestors(&ancestors, head, StateTable::InvalidIndex); + for (int anc : ancestors) { + if (anc != -1) { // the state machine itself is always compound + const auto &ancState = m_stateTable->state(anc); + if (!ancState.isCompound()) + continue; + } + + if (allDescendants(tail, anc)) + return anc; + } + + return StateTable::InvalidIndex; +} + +void QScxmlStateMachinePrivate::getEffectiveTargetStates(OrderedSet *targets, + int transitionIndex) const +{ + Q_ASSERT(targets); + + const auto &transition = m_stateTable->transition(transitionIndex); + for (int s : m_stateTable->array(transition.targets)) { + const auto &state = m_stateTable->state(s); + if (state.isHistoryState()) { + HistoryValues::const_iterator historyValueIter = m_historyValue.find(s); + if (historyValueIter != m_historyValue.end()) { + foreach (int historyState, historyValueIter.value()) { + targets->add(historyState); + } + } else { + getEffectiveTargetStates(targets, m_stateTable->array(state.transitions)[0]); + } + } else { + targets->add(s); + } + } +} + /*! * Creates a state machine from the SCXML file specified by \a fileName. * @@ -449,13 +1176,14 @@ QScxmlStateMachine::QScxmlStateMachine(QObject *parent) : QObject(*new QScxmlStateMachinePrivate, parent) { Q_D(QScxmlStateMachine); - d->init(); + d->m_executionEngine = new QScxmlExecutableContent::QScxmlExecutionEngine(this); } QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent) : QObject(dd, parent) { - dd.init(); + Q_D(QScxmlStateMachine); + d->m_executionEngine = new QScxmlExecutableContent::QScxmlExecutionEngine(this); } /*! @@ -626,27 +1354,6 @@ QScxmlParser::Loader *QScxmlStateMachine::loader() const /*! * \internal - * Sets the binding method to the specified value. - */ -void QScxmlStateMachine::setDataBinding(QScxmlStateMachine::BindingMethod bindingMethod) -{ - Q_D(QScxmlStateMachine); - - d->m_dataBinding = bindingMethod; -} - -/*! - * Returns the binding method used by the state machine. - */ -QScxmlStateMachine::BindingMethod QScxmlStateMachine::dataBinding() const -{ - Q_D(const QScxmlStateMachine); - - return d->m_dataBinding; -} - -/*! - * \internal * * This is used internally in order to execute the executable content. * @@ -672,271 +1379,23 @@ void QScxmlStateMachine::setTableData(QScxmlTableData *tableData) Q_ASSERT(tableData); d->m_tableData = tableData; + d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>( + tableData->stateMachineTable()); if (objectName().isEmpty()) { setObjectName(tableData->name()); } -} - -void QScxmlInternal::WrappedQStateMachine::beginSelectTransitions(QEvent *event) -{ - Q_D(WrappedQStateMachine); - - if (event && event->type() == QScxmlEvent::scxmlEventType) { - stateMachinePrivate()->m_event = *static_cast<QScxmlEvent *>(event); - d->stateMachine()->dataModel()->setScxmlEvent(stateMachinePrivate()->m_event); - - auto scxmlEvent = static_cast<QScxmlEvent *>(event); - auto smp = stateMachinePrivate(); - - foreach (QScxmlInvokableService *service, smp->invokedServices()) { - if (scxmlEvent->invokeId() == service->id()) { - service->finalize(); - } - if (service->autoforward()) { - qCDebug(qscxmlLog) << this << "auto-forwarding event" << scxmlEvent->name() - << "from" << stateMachine()->name() << "to service" << service->id(); - service->postEvent(new QScxmlEvent(*scxmlEvent)); - } - } - - if (scxmlEvent->eventType() == QScxmlEvent::ExternalEvent) { - emit d->stateMachine()->eventOccurred(*scxmlEvent); - } - - if (scxmlEvent->originType() == QLatin1String("qt:signal")) { - emit d->stateMachine()->externalEventOccurred(*scxmlEvent); - } - - if (smp->m_eventFilter && !smp->m_eventFilter->handle(scxmlEvent, d->stateMachine())) { - scxmlEvent->makeIgnorable(); - scxmlEvent->clear(); - smp->m_event.clear(); - return; - } - } else { - stateMachinePrivate()->m_event.clear(); - d->stateMachine()->dataModel()->setScxmlEvent(stateMachinePrivate()->m_event); + if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) { + const size_t serviceCount = size_t(d->m_stateTable->maxServiceId + 1); + d->m_invokedServices.resize(serviceCount, { -1, nullptr, QString() }); + d->m_cachedFactories.resize(serviceCount, nullptr); } -} - -void QScxmlInternal::WrappedQStateMachine::beginMicrostep(QEvent *event) -{ - Q_D(WrappedQStateMachine); - qCDebug(qscxmlLog) << d->m_stateMachine - << "started microstep from state" << d->m_stateMachine->activeStateNames() - << "with event" << stateMachinePrivate()->m_event.name() - << "and event type" << event->type(); -} - -void QScxmlInternal::WrappedQStateMachine::endMicrostep(QEvent *event) -{ - Q_D(WrappedQStateMachine); - Q_UNUSED(event); - - qCDebug(qscxmlLog) << d->m_stateMachine - << "finished microstep in state (" << d->m_stateMachine->activeStateNames() << ")"; -} - -// This is a slightly modified copy of QStateMachinePrivate::event() -// Instead of postExternalEvent and processEvents -// we route event first to the appropriate state machine instance. -bool QScxmlInternal::WrappedQStateMachine::event(QEvent *e) -{ - Q_D(QScxmlInternal::WrappedQStateMachine); - if (e->type() == QEvent::Timer) { - QTimerEvent *te = static_cast<QTimerEvent*>(e); - int tid = te->timerId(); - if (d->state != QStateMachinePrivate::Running) { - // This event has been cancelled already - QMutexLocker locker(&d->delayedEventsMutex); - Q_ASSERT(!d->timerIdToDelayedEventId.contains(tid)); - return true; - } - d->delayedEventsMutex.lock(); - int id = d->timerIdToDelayedEventId.take(tid); - QStateMachinePrivate::DelayedEvent ee = d->delayedEvents.take(id); - if (ee.event != 0) { - Q_ASSERT(ee.timerId == tid); -// killTimer(tid); -// d->delayedEventIdFreeList.release(id); - d->delayedEventsMutex.unlock(); - d->_q_killDelayedEventTimer(id, tid); - // route here - if (ee.event->type() == QScxmlEvent::scxmlEventType) - QScxmlStateMachinePrivate::get(stateMachine())->routeEvent(static_cast<QScxmlEvent *>(ee.event)); -// d->postExternalEvent(ee.event); -// d->processEvents(QStateMachinePrivate::DirectProcessing); - return true; - } else { - d->delayedEventsMutex.unlock(); - } - } - return QState::event(e); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::noMicrostep() -{ - qCDebug(qscxmlLog) << m_stateMachine - << "had no transition, stays in state (" << m_stateMachine->activeStateNames() << ")"; -} - -void QScxmlInternal::WrappedQStateMachinePrivate::processedPendingEvents(bool didChange) -{ - qCDebug(qscxmlLog) << m_stateMachine << "finishedPendingEvents" << didChange << "in state (" - << m_stateMachine->activeStateNames() << ")"; - emit m_stateMachine->reachedStableState(); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::beginMacrostep() -{ -} - -void QScxmlInternal::WrappedQStateMachinePrivate::endMacrostep(bool didChange) -{ - qCDebug(qscxmlLog) << m_stateMachine << "endMacrostep" << didChange - << "in state (" << m_stateMachine->activeStateNames() << ")"; - - { // handle <invoke>s - QVector<QScxmlState*> &sti = stateMachinePrivate()->m_statesToInvoke; - std::sort(sti.begin(), sti.end(), WrappedQStateMachinePrivate::stateEntryLessThan); - foreach (QScxmlState *s, sti) { - auto sp = QScxmlStatePrivate::get(s); - foreach (QScxmlInvokableService *s, sp->servicesWaitingToStart) { - s->start(); - } - sp->servicesWaitingToStart.clear(); - } - sti.clear(); - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::enterStates( - QEvent *event, - const QList<QAbstractState*> &exitedStates_sorted, - const QList<QAbstractState*> &statesToEnter_sorted, - const QSet<QAbstractState*> &statesForDefaultEntry, - QHash<QAbstractState *, QVector<QPropertyAssignment> > &propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , const QList<QAbstractAnimation*> &selectedAnimations -# endif - ) -{ - QStateMachinePrivate::enterStates(event, exitedStates_sorted, statesToEnter_sorted, - statesForDefaultEntry, propertyAssignmentsForState -# ifndef QT_NO_ANIMATION - , selectedAnimations -# endif - ); - foreach (QAbstractState *s, statesToEnter_sorted) { - if (QScxmlState *qss = qobject_cast<QScxmlState *>(s)) { - if (!QScxmlStatePrivate::get(qss)->invokableServiceFactories.isEmpty()) { - if (!stateMachinePrivate()->m_statesToInvoke.contains(qss)) { - stateMachinePrivate()->m_statesToInvoke.append(qss); - } - } - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::exitStates( - QEvent *event, - const QList<QAbstractState *> &statesToExit_sorted, - const QHash<QAbstractState*, QVector<QPropertyAssignment> > &assignmentsForEnteredStates) -{ - QStateMachinePrivate::exitStates(event, statesToExit_sorted, assignmentsForEnteredStates); - - auto smp = stateMachinePrivate(); - for (int i = 0; i < smp->m_statesToInvoke.size(); ) { - if (statesToExit_sorted.contains(smp->m_statesToInvoke.at(i))) { - smp->m_statesToInvoke.removeAt(i); - } else { - ++i; - } - } - - foreach (QAbstractState *s, statesToExit_sorted) { - if (QScxmlState *qss = qobject_cast<QScxmlState *>(s)) { - auto ssp = QScxmlStatePrivate::get(qss); - ssp->servicesWaitingToStart.clear(); - QVector<QScxmlInvokableService *> &services = ssp->invokedServices; - foreach (QScxmlInvokableService *service, services) { - qCDebug(qscxmlLog) << stateMachine() << "schedule service cancellation" << service->id(); - QMetaObject::invokeMethod(q_func(), - "removeAndDestroyService", - Qt::QueuedConnection, - Q_ARG(QScxmlInvokableService *,service)); - } - services.clear(); - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::exitInterpreter() -{ - Q_Q(WrappedQStateMachine); - - foreach (QAbstractState *s, configuration) { - QScxmlExecutableContent::ContainerId onExitInstructions = QScxmlExecutableContent::NoInstruction; - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(s)) { - stateMachinePrivate()->m_executionEngine->execute(finalState->doneData(), QVariant()); - onExitInstructions = QScxmlFinalStatePrivate::get(finalState)->onExitInstructions; - } else if (QScxmlState *state = qobject_cast<QScxmlState *>(s)) { - onExitInstructions = QScxmlStatePrivate::get(state)->onExitInstructions; - } - - if (onExitInstructions != QScxmlExecutableContent::NoInstruction) { - stateMachinePrivate()->m_executionEngine->execute(onExitInstructions); - } - - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(s)) { - if (finalState->parent() == q) { - if (auto psm = stateMachinePrivate()->m_parentStateMachine) { - auto done = new QScxmlEvent; - done->setName(QStringLiteral("done.invoke.") + m_stateMachine->sessionId()); - done->setInvokeId(m_stateMachine->sessionId()); - qCDebug(qscxmlLog) << "submitting event" << done->name() << "to" << psm->name(); - psm->submitEvent(done); - } - } - } - } -} - -void QScxmlInternal::WrappedQStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState) -{ - Q_Q(WrappedQStateMachine); - - if (QScxmlFinalState *finalState = qobject_cast<QScxmlFinalState *>(guiltyState)) { - if (!q->isRunning()) - return; - stateMachinePrivate()->m_executionEngine->execute(finalState->doneData(), forState->objectName()); - } - - QStateMachinePrivate::emitStateFinished(forState, guiltyState); -} - -void QScxmlInternal::WrappedQStateMachinePrivate::startupHook() -{ - Q_Q(WrappedQStateMachine); - - q->submitQueuedEvents(); -} - -int QScxmlInternal::WrappedQStateMachinePrivate::eventIdForDelayedEvent(const QString &sendId) -{ - QMutexLocker locker(&delayedEventsMutex); - - QHash<int, DelayedEvent>::const_iterator it; - for (it = delayedEvents.constBegin(); it != delayedEvents.constEnd(); ++it) { - if (QScxmlEvent *e = dynamic_cast<QScxmlEvent *>(it->event)) { - if (e->sendId() == sendId) { - return it.key(); - } - } - } - - return -1; + if (d->m_stateTable->version != Q_QSCXMLC_OUTPUT_REVISION) + qFatal("Cannot mix incompatible state table (version 0x%x) with this library (version 0x%x)", + d->m_stateTable->version, Q_QSCXMLC_OUTPUT_REVISION); + Q_ASSERT(tableData->stateMachineTable()[d->m_stateTable->arrayOffset + + d->m_stateTable->arraySize] + == QScxmlExecutableContent::StateTable::terminator); } /*! @@ -947,26 +1406,21 @@ int QScxmlInternal::WrappedQStateMachinePrivate::eventIdForDelayedEvent(const QS * When it is \c false, the full list of all states will be returned. * * The returned list does not contain the states of possible nested state machines. + * + * \note The order of the state names in the list is the order in which the states occurred in + * the SCXML document. */ QStringList QScxmlStateMachine::stateNames(bool compress) const { Q_D(const QScxmlStateMachine); - QList<QObject *> worklist; - worklist.reserve(d->m_qStateMachine->children().size() + d->m_qStateMachine->configuration().size()); - worklist.append(d->m_qStateMachine->children()); - - QStringList res; - while (!worklist.isEmpty()) { - QObject *obj = worklist.takeLast(); - if (QAbstractState *state = qobject_cast<QAbstractState *>(obj)) { - if (!compress || !obj->children().count()) - res.append(state->objectName()); - worklist.append(obj->children()); - } + QStringList names; + for (int i = 0; i < d->m_stateTable->stateCount; ++i) { + const auto &state = d->m_stateTable->state(i); + if (!compress || state.isAtomic()) + names.append(d->m_tableData->string(state.name)); } - std::sort(res.begin(), res.end()); - return res; + return names; } /*! @@ -980,19 +1434,13 @@ QStringList QScxmlStateMachine::activeStateNames(bool compress) const { Q_D(const QScxmlStateMachine); - QSet<QAbstractState *> config = QStateMachinePrivate::get(d->m_qStateMachine)->configuration; - if (compress) - foreach (const QAbstractState *s, config) - config.remove(s->parentState()); - QStringList res; - foreach (const QAbstractState *s, config) { - QString id = s->objectName(); - if (!id.isEmpty()) { - res.append(id); - } + QStringList result; + for (int stateIdx : d->m_configuration) { + const auto &state = d->m_stateTable->state(stateIdx); + if (state.isAtomic() || !compress) + result.append(d->m_tableData->string(state.name)); } - std::sort(res.begin(), res.end()); - return res; + return result; } /*! @@ -1001,20 +1449,20 @@ QStringList QScxmlStateMachine::activeStateNames(bool compress) const bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const { Q_D(const QScxmlStateMachine); - QSet<QAbstractState *> config = QStateMachinePrivate::get(d->m_qStateMachine)->configuration; - foreach (QAbstractState *s, config) { - if (s->objectName() == scxmlStateName) { + + for (int stateIndex : d->m_configuration) { + const auto &state = d->m_stateTable->state(stateIndex); + if (d->m_tableData->string(state.name) == scxmlStateName) return true; - } } + return false; } /*! * Creates a connection of the given \a type from the state identified by \a scxmlStateName * to the \a method in the \a receiver object. The receiver's \a method - * may contain a boolean argument that indicates whether the state connected - * became active or inactive. + * may contain a boolean argument that indicates whether the state connected * became active or inactive. * * Returns a handle to the connection, which can be used later to disconnect. */ @@ -1022,27 +1470,9 @@ QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlS const QObject *receiver, const char *method, Qt::ConnectionType type) { - Q_D(QScxmlStateMachine); - QAbstractState *state = findState(scxmlStateName, d->m_qStateMachine); - return QObject::connect(state, SIGNAL(activeChanged(bool)), receiver, method, type); -} - -/*! - * Returns the SCXML event filter if one is set, otherwise returns null. - */ -QScxmlEventFilter *QScxmlStateMachine::scxmlEventFilter() const -{ - Q_D(const QScxmlStateMachine); - return d->m_eventFilter; -} - -/*! - * Sets the \a newFilter as the SCXML event filter. Passing null will remove the current filter. - */ -void QScxmlStateMachine::setScxmlEventFilter(QScxmlEventFilter *newFilter) -{ - Q_D(QScxmlStateMachine); - d->m_eventFilter = newFilter; + QByteArray signalName = QByteArray::number(QSIGNAL_CODE) + scxmlStateName.toUtf8() + + "Changed(bool)"; + return QObject::connect(this, signalName.constData(), receiver, method, type); } /*! @@ -1085,7 +1515,7 @@ bool QScxmlStateMachine::isRunning() const { Q_D(const QScxmlStateMachine); - return d->m_qStateMachine->isRunning(); + return d->isRunnable() && !d->isPaused(); } /*! @@ -1143,9 +1573,7 @@ void QScxmlStateMachine::submitEvent(QScxmlEvent *event) << QScxmlEventPrivate::debugString(event).constData(); Q_ASSERT(event->eventType() == QScxmlEvent::ExternalEvent); - int id = d->m_qStateMachine->postDelayedEvent(event, event->delay()); - - qCDebug(qscxmlLog) << this << ": delayed event" << event->name() << "(" << event << ") got id:" << id; + d->submitDelayedEvent(event); } else { qCDebug(qscxmlLog) << this << "submitting event" << event->name() << ":" << QScxmlEventPrivate::debugString(event).constData(); @@ -1173,9 +1601,8 @@ void QScxmlStateMachine::submitEvent(const QString &eventName) void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &data) { QVariant incomingData = data; - if (incomingData.canConvert<QJSValue>()) { + if (incomingData.canConvert<QJSValue>()) incomingData = incomingData.value<QJSValue>().toVariant(); - } QScxmlEvent *e = new QScxmlEvent; e->setName(eventName); @@ -1191,49 +1618,16 @@ void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId) { Q_D(QScxmlStateMachine); - int id = d->m_qStateMachine->eventIdForDelayedEvent(sendId); - - qCDebug(qscxmlLog) << this << "canceling event" << sendId << "with id" << id; - - if (id != -1) - d->m_qStateMachine->cancelDelayedEvent(id); -} - -void QScxmlInternal::WrappedQStateMachine::queueEvent(QScxmlEvent *event, EventPriority priority) -{ - Q_D(WrappedQStateMachine); - - if (!d->m_queuedEvents) - d->m_queuedEvents = new QVector<WrappedQStateMachinePrivate::QueuedEvent>(); - d->m_queuedEvents->append(WrappedQStateMachinePrivate::QueuedEvent(event, priority)); -} - -void QScxmlInternal::WrappedQStateMachine::submitQueuedEvents() -{ - Q_D(WrappedQStateMachine); - - qCDebug(qscxmlLog) << d->m_stateMachine << ": submitting queued events"; - - if (d->m_queuedEvents) { - foreach (const WrappedQStateMachinePrivate::QueuedEvent &e, *d->m_queuedEvents) - postEvent(e.event, e.priority); - delete d->m_queuedEvents; - d->m_queuedEvents = Q_NULLPTR; - } -} - -int QScxmlInternal::WrappedQStateMachine::eventIdForDelayedEvent(const QString &sendId) -{ - Q_D(WrappedQStateMachine); - return d->eventIdForDelayedEvent(sendId); -} - -void QScxmlInternal::WrappedQStateMachine::removeAndDestroyService(QScxmlInvokableService *service) -{ - Q_D(WrappedQStateMachine); - qCDebug(qscxmlLog) << stateMachine() << "canceling service" << service->id(); - if (d->stateMachinePrivate()->removeService(service)) { - delete service; + for (auto it = d->m_delayedEvents.begin(), eit = d->m_delayedEvents.end(); it != eit; ++it) { + if (it->second->sendId() == sendId) { + qCDebug(qscxmlLog) << this + << "canceling event" << sendId + << "with timer id" << it->first; + d->m_eventLoopHook.killTimer(it->first); + delete it->second; + d->m_delayedEvents.erase(it); + return; + } } } @@ -1262,10 +1656,9 @@ bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const if (target.startsWith(QStringLiteral("#_"))) { QStringRef targetId = target.midRef(2); - foreach (QScxmlInvokableService *service, d->m_invokedServices) { - if (service->id() == targetId) { + for (auto invokedService : d->m_invokedServices) { + if (invokedService.service->id() == targetId) return true; - } } } @@ -1324,7 +1717,8 @@ void QScxmlStateMachine::start() if (!isInitialized() && !init()) qCDebug(qscxmlLog) << this << "cannot be initialized on start(). Starting anyway ..."; - d->m_qStateMachine->start(); + d->start(); + d->m_eventLoopHook.queueProcessEvents(); } /*! @@ -1336,16 +1730,23 @@ void QScxmlStateMachine::start() void QScxmlStateMachine::stop() { Q_D(QScxmlStateMachine); - d->m_qStateMachine->stop(); + d->pause(); } -/*! - * \internal - */ -void QScxmlStateMachine::setService(const QString &id, QScxmlInvokableService *service) +bool QScxmlStateMachine::isActive(int stateIndex) const { - Q_UNUSED(id); - Q_UNUSED(service); + Q_D(const QScxmlStateMachine); + return d->m_configuration.contains(stateIndex); +} + +QScxmlStateMachine *QScxmlStateMachine::subStateMachine(int index) const +{ + Q_D(const QScxmlStateMachine); + auto invokedService = d->m_invokedServices[size_t(index)].service; + if (auto scxmlService = dynamic_cast<QScxmlInvokableScxml *>(invokedService)) + return scxmlService->stateMachine(); + else + return nullptr; } QT_END_NAMESPACE diff --git a/src/scxml/qscxmlstatemachine.h b/src/scxml/qscxmlstatemachine.h index 2ab7880..7a95f82 100644 --- a/src/scxml/qscxmlstatemachine.h +++ b/src/scxml/qscxmlstatemachine.h @@ -61,13 +61,6 @@ class QScxmlInvokableService; class QScxmlStateMachine; class QScxmlTableData; -class Q_SCXML_EXPORT QScxmlEventFilter -{ -public: - virtual ~QScxmlEventFilter(); - virtual bool handle(QScxmlEvent *event, QScxmlStateMachine *stateMachine) = 0; -}; - class QScxmlStateMachinePrivate; class Q_SCXML_EXPORT QScxmlStateMachine: public QObject { @@ -86,11 +79,6 @@ protected: #endif // Q_QDOC public: - enum BindingMethod { - EarlyBinding, - LateBinding - }; - static QScxmlStateMachine *fromFile(const QString &fileName); static QScxmlStateMachine *fromData(QIODevice *data, const QString &fileName = QString()); QVector<QScxmlError> parseErrors() const; @@ -108,8 +96,6 @@ public: void setLoader(QScxmlParser::Loader *loader); QScxmlParser::Loader *loader() const; - BindingMethod dataBinding() const; - bool isRunning() const; void setRunning(bool running); @@ -125,9 +111,6 @@ public: const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection); - QScxmlEventFilter *scxmlEventFilter() const; - void setScxmlEventFilter(QScxmlEventFilter *newFilter); - Q_INVOKABLE void submitEvent(QScxmlEvent *event); Q_INVOKABLE void submitEvent(const QString &eventName); Q_INVOKABLE void submitEvent(const QString &eventName, const QVariant &data); @@ -158,9 +141,9 @@ protected: // methods for friends: friend QScxmlExecutableContent::QScxmlExecutionEngine; #ifndef Q_QDOC - void setDataBinding(BindingMethod bindingMethod); - virtual void setService(const QString &id, QScxmlInvokableService *service); - + // The methods below are used by the compiled state machines. + bool isActive(int stateIndex) const; + QScxmlStateMachine *subStateMachine(int index) const; QScxmlTableData *tableData() const; void setTableData(QScxmlTableData *tableData); #endif // Q_QDOC diff --git a/src/scxml/qscxmlstatemachine_p.h b/src/scxml/qscxmlstatemachine_p.h index ae68ce5..d739e53 100644 --- a/src/scxml/qscxmlstatemachine_p.h +++ b/src/scxml/qscxmlstatemachine_p.h @@ -53,44 +53,32 @@ #include <QtScxml/private/qscxmlexecutablecontent_p.h> #include <QtScxml/qscxmlstatemachine.h> - -#include <QStateMachine> -#include <QtCore/private/qstatemachine_p.h> +#include <QtCore/private/qobject_p.h> QT_BEGIN_NAMESPACE namespace QScxmlInternal { -class WrappedQStateMachinePrivate; -class WrappedQStateMachine: public QStateMachine +class EventLoopHook: public QObject { Q_OBJECT - Q_DECLARE_PRIVATE(WrappedQStateMachine) -public: - WrappedQStateMachine(QScxmlStateMachine *parent); - WrappedQStateMachine(WrappedQStateMachinePrivate &dd, QScxmlStateMachine *parent); + QScxmlStateMachinePrivate *smp; - QScxmlStateMachine *stateMachine() const; +public: + EventLoopHook(QScxmlStateMachinePrivate *smp) + : smp(smp) + {} - void queueEvent(QScxmlEvent *event, QStateMachine::EventPriority priority); - void submitQueuedEvents(); - int eventIdForDelayedEvent(const QString &sendId); + void queueProcessEvents(); - Q_INVOKABLE void removeAndDestroyService(QScxmlInvokableService *service); + Q_INVOKABLE void doProcessEvents(); protected: - void beginSelectTransitions(QEvent *event) Q_DECL_OVERRIDE; - void beginMicrostep(QEvent *event) Q_DECL_OVERRIDE; - void endMicrostep(QEvent *event) Q_DECL_OVERRIDE; - bool event(QEvent *e) Q_DECL_OVERRIDE; - -private: - QScxmlStateMachinePrivate *stateMachinePrivate(); + void timerEvent(QTimerEvent *timerEvent) Q_DECL_OVERRIDE; }; -} // Internal namespace +} // QScxmlInternal namespace class QScxmlInvokableService; -class QScxmlState; class QScxmlStateMachinePrivate: public QObjectPrivate { Q_DECLARE_PUBLIC(QScxmlStateMachine) @@ -98,6 +86,27 @@ class QScxmlStateMachinePrivate: public QObjectPrivate static QAtomicInt m_sessionIdCounter; public: // types + typedef QScxmlExecutableContent::StateTable StateTable; + + class HistoryContent + { + QHash<int, int> storage; + + public: + HistoryContent() { storage.reserve(4); } + + int &operator[](int idx) { + QHash<int, int>::Iterator i = storage.find(idx); + return (i == storage.end()) ? storage.insert(idx, StateTable::InvalidIndex).value() : + i.value(); + } + + int value(int idx) const { + QHash<int, int>::ConstIterator i = storage.constFind(idx); + return (i == storage.constEnd()) ? StateTable::InvalidIndex : i.value(); + } + }; + class ParserData { public: @@ -105,57 +114,221 @@ public: // types QVector<QScxmlError> m_errors; }; + // The OrderedSet is a set where it elements are in insertion order. See + // http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation under Algorithm, Datatypes. It + // is used to keep lists of states and transitions in document order. + class OrderedSet + { + std::vector<int> storage; + + public: + OrderedSet(){} + OrderedSet(std::initializer_list<int> l): storage(l) {} + + std::vector<int> takeList() const + { return std::move(storage); } + + const std::vector<int> &list() const + { return storage; } + + bool contains(int i) const + { + return std::find(storage.cbegin(), storage.cend(), i) != storage.cend(); + } + + bool remove(int i) + { + std::vector<int>::iterator it = std::find(storage.begin(), storage.end(), i); + if (it == storage.end()) { + return false; + } + storage.erase(it); + return true; + } + + void removeHead() + { if (!isEmpty()) storage.erase(storage.begin()); } + + bool isEmpty() const + { return storage.empty(); } + + void add(int i) + { if (!contains(i)) storage.push_back(i); } + + bool intersectsWith(const OrderedSet &other) const + { + for (auto i : storage) { + if (other.contains(i)) { + return true; + } + } + return false; + } + + void clear() + { storage.clear(); } + + typedef std::vector<int>::const_iterator const_iterator; + const_iterator begin() const { return storage.cbegin(); } + const_iterator end() const { return storage.cend(); } + }; + + class Queue + { + QVector<QScxmlEvent *> storage; + + public: + Queue() + { storage.reserve(4); } + + ~Queue() + { qDeleteAll(storage); } + + void enqueue(QScxmlEvent *e) + { storage.append(e); } + + bool isEmpty() const + { return storage.empty(); } + + QScxmlEvent *dequeue() + { + Q_ASSERT(!isEmpty()); + QScxmlEvent *e = storage.first(); + storage.pop_front(); + int sz = storage.size(); + if (Q_UNLIKELY(sz > 4 && sz * 8 < storage.capacity())) { + storage.squeeze(); + } + return e; + } + }; + public: QScxmlStateMachinePrivate(); ~QScxmlStateMachinePrivate(); - void init(); - static QScxmlStateMachinePrivate *get(QScxmlStateMachine *t) { return t->d_func(); } - void setQStateMachine(QScxmlInternal::WrappedQStateMachine *stateMachine); - - QAbstractState *stateByScxmlName(const QString &scxmlName); - ParserData *parserData(); void setIsInvoked(bool invoked) { m_isInvoked = invoked; } - const QVector<QScxmlInvokableService *> &invokedServices() const - { return m_invokedServices; } - - void addService(QScxmlInvokableService *service); - - bool removeService(QScxmlInvokableService *service); + void addService(int invokingState); + void removeService(int invokingState); + QScxmlInvokableServiceFactory *serviceFactory(int id); bool executeInitialSetup(); void routeEvent(QScxmlEvent *event); void postEvent(QScxmlEvent *event); + void submitDelayedEvent(QScxmlEvent *event); void submitError(const QString &type, const QString &msg, const QString &sendid = QString()); + void start(); + void pause(); + void processEvents(); + + void setEvent(QScxmlEvent *event); + void resetEvent(); + + void emitStateActive(int stateIndex, bool active); + void emitServiceChanged(int machineIndex, QScxmlInvokableService *service); + void emitSignalForEvent(int signalIndex, const QVariant &data); + +private: + QStringList stateNames(const std::vector<int> &stateIndexes) const; + std::vector<int> historyStates(int stateIdx) const; + + void exitInterpreter(); + void returnDoneEvent(QScxmlExecutableContent::ContainerId doneData); + bool nameMatch(const StateTable::Array &patterns, QScxmlEvent *event) const; + void selectTransitions(OrderedSet &enabledTransitions, + const std::vector<int> &configInDocumentOrder, + QScxmlEvent *event) const; + void removeConflictingTransitions(OrderedSet *enabledTransitions) const; + void getProperAncestors(std::vector<int> *ancestors, int state1, int state2) const; + void microstep(const OrderedSet &enabledTransitions); + void exitStates(const OrderedSet &enabledTransitions); + void computeExitSet(const OrderedSet &enabledTransitions, OrderedSet &statesToExit) const; + void executeTransitionContent(const OrderedSet &enabledTransitions); + void enterStates(const OrderedSet &enabledTransitions); + void computeEntrySet(const OrderedSet &enabledTransitions, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + void addDescendantStatesToEnter(int stateIndex, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + void addAncestorStatesToEnter(int stateIndex, + int ancestorIndex, + OrderedSet *statesToEnter, + OrderedSet *statesForDefaultEntry, + HistoryContent *defaultHistoryContent) const; + std::vector<int> getChildStates(const StateTable::State &state) const; + bool hasDescendant(const OrderedSet &statesToEnter, int childIdx) const; + bool allDescendants(const OrderedSet &statesToEnter, int childdx) const; + bool isDescendant(int state1, int state2) const; + bool allInFinalStates(const std::vector<int> &states) const; + bool someInFinalStates(const std::vector<int> &states) const; + bool isInFinalState(int stateIndex) const; + int getTransitionDomain(int transitionIndex) const; + int findLCCA(OrderedSet &&states) const; + void getEffectiveTargetStates(OrderedSet *targets, int transitionIndex) const; + public: // types & data fields: QString m_sessionId; bool m_isInvoked; bool m_isInitialized; + bool m_isProcessingEvents; QVariantMap m_initialValues; QScxmlDataModel *m_dataModel; QScxmlParserPrivate::DefaultLoader m_defaultLoader; QScxmlParser::Loader *m_loader; - QScxmlStateMachine::BindingMethod m_dataBinding; QScxmlExecutableContent::QScxmlExecutionEngine *m_executionEngine; QScxmlTableData *m_tableData; - QScxmlEvent m_event; - QScxmlInternal::WrappedQStateMachine *m_qStateMachine; - QScxmlEventFilter *m_eventFilter; - QVector<QScxmlState*> m_statesToInvoke; + const StateTable *m_stateTable; QScxmlStateMachine *m_parentStateMachine; + QScxmlInternal::EventLoopHook m_eventLoopHook; + typedef std::vector<std::pair<int, QScxmlEvent *>> DelayedQueue; + DelayedQueue m_delayedEvents; private: - QVector<QScxmlInvokableService *> m_invokedServices; QScopedPointer<ParserData> m_parserData; // used when created by StateMachine::fromFile. + typedef QHash<int, QVector<int>> HistoryValues; + struct InvokedService { + int invokingState; + QScxmlInvokableService *service; + QString serviceName; + }; + + // TODO: move the stuff below to a struct that can be reset + HistoryValues m_historyValue; + OrderedSet m_configuration; + Queue m_internalQueue; + Queue m_externalQueue; + QSet<int> m_statesToInvoke; + std::vector<InvokedService> m_invokedServices; + std::vector<bool> m_isFirstStateEntry; + std::vector<QScxmlInvokableServiceFactory *> m_cachedFactories; + enum { Invalid = 0, Starting, Running, Paused, Finished } m_runningState = Invalid; + bool isRunnable() const { + switch (m_runningState) { + case Starting: + case Running: + case Paused: + return true; + case Invalid: + case Finished: + return false; + } + + return false; // Dead code, but many dumb compilers cannot (or are unwilling to) detect that. + } + + bool isPaused() const { return m_runningState == Paused; } }; QT_END_NAMESPACE diff --git a/src/scxml/qscxmltabledata.cpp b/src/scxml/qscxmltabledata.cpp index 75469f9..33f3103 100644 --- a/src/scxml/qscxmltabledata.cpp +++ b/src/scxml/qscxmltabledata.cpp @@ -37,11 +37,1019 @@ ** ****************************************************************************/ -#include "qscxmltabledata.h" +#include "qscxmltabledata_p.h" +#include "qscxmlparser_p.h" +#include "qscxmlexecutablecontent_p.h" -QT_BEGIN_NAMESPACE +QT_USE_NAMESPACE + +using namespace QScxmlInternal; + +namespace { +using namespace QScxmlExecutableContent; + +class TableDataBuilder: public DocumentModel::NodeVisitor +{ +public: + TableDataBuilder(GeneratedTableData &tableData, + GeneratedTableData::MetaDataInfo &metaDataInfo, + GeneratedTableData::DataModelInfo &dataModelInfo, + GeneratedTableData::CreateFactoryId func) + : createFactoryId(func) + , m_tableData(tableData) + , m_dataModelInfo(dataModelInfo) + , m_stringTable(tableData.theStrings) + , m_instructions(tableData.theInstructions) + , m_evaluators(tableData.theEvaluators) + , m_assignments(tableData.theAssignments) + , m_foreaches(tableData.theForeaches) + , m_dataIds(tableData.theDataNameIds) + , m_outgoingEvents(metaDataInfo.outgoingEvents) + , m_stateNames(metaDataInfo.stateNames) + , m_subStateMachineNames(metaDataInfo.subStateMachineNames) + , m_incomingEvents(metaDataInfo.incomingEvents) + + { + m_activeSequences.reserve(4); + tableData.theInitialSetup = QScxmlExecutableContent::NoInstruction; + } + + void buildTableData(DocumentModel::ScxmlDocument *doc) + { + m_qtMode = doc->qtMode; + m_isCppDataModel = doc->root->dataModel == DocumentModel::Scxml::CppDataModel; + m_parents.reserve(32); + m_allTransitions.resize(doc->allTransitions.size()); + m_docTransitionIndices.reserve(doc->allTransitions.size()); + foreach (auto *t, doc->allTransitions) { + m_docTransitionIndices.insert(t, m_docTransitionIndices.size()); + } + m_docStatesIndices.reserve(doc->allStates.size()); + m_transitionsForState.resize(doc->allStates.size()); + m_allStates.resize(doc->allStates.size()); + foreach (DocumentModel::AbstractState *s, doc->allStates) { + m_docStatesIndices.insert(s, m_docStatesIndices.size()); + } + + doc->root->accept(this); + m_stateTable.version = Q_QSCXMLC_OUTPUT_REVISION; + generateStateMachineData(); + + m_tableData.theInstructions.squeeze(); + } + + void generateStateMachineData() + { + const int tableSize = sizeof(StateTable) / sizeof(qint32); + const int stateSize = qint32(sizeof(StateTable::State) / sizeof(qint32)); + const int transitionSize = qint32(sizeof(StateTable::Transition) / sizeof(qint32)); + + m_stateTable.stateOffset = tableSize; + m_stateTable.stateCount = m_allStates.size(); + m_stateTable.transitionOffset = m_stateTable.stateOffset + + m_stateTable.stateCount * stateSize; + m_stateTable.transitionCount = m_allTransitions.size(); + m_stateTable.arrayOffset = m_stateTable.transitionOffset + + m_stateTable.transitionCount * transitionSize; + m_stateTable.arraySize = m_arrays.size(); + + const qint32 dataSize = qint32(tableSize) + + (m_allStates.size() * stateSize) + + (m_allTransitions.size() * transitionSize) + + m_arrays.size() + + 1; + QVector<qint32> data(dataSize, -1); + qint32 *ptr = data.data(); + + memcpy(ptr, &m_stateTable, sizeof(m_stateTable)); + ptr += tableSize; + + Q_ASSERT(ptr == data.constData() + m_stateTable.stateOffset); + memcpy(ptr, m_allStates.constData(), + sizeof(StateTable::State) * size_t(m_allStates.size())); + ptr += stateSize * size_t(m_allStates.size()); + + Q_ASSERT(ptr == data.constData() + m_stateTable.transitionOffset); + memcpy(ptr, m_allTransitions.constData(), + sizeof(StateTable::Transition) * size_t(m_allTransitions.size())); + ptr += transitionSize * size_t(m_allTransitions.size()); + + Q_ASSERT(ptr == data.constData() + m_stateTable.arrayOffset); + memcpy(ptr, m_arrays.constData(), sizeof(qint32) * size_t(m_arrays.size())); + ptr += m_arrays.size(); + + *ptr++ = StateTable::terminator; + + Q_ASSERT(ptr == data.constData() + dataSize); + + m_tableData.theStateMachineTable = data; + } + +protected: // visitor + using NodeVisitor::visit; + + bool visit(DocumentModel::Scxml *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + setName(node->name); + + switch (node->dataModel) { + case DocumentModel::Scxml::NullDataModel: + m_stateTable.dataModel = StateTable::NullDataModel; + break; + case DocumentModel::Scxml::JSDataModel: + m_stateTable.dataModel = StateTable::EcmaScriptDataModel; + break; + case DocumentModel::Scxml::CppDataModel: + m_stateTable.dataModel = StateTable::CppDataModel; + break; + default: + m_stateTable.dataModel = StateTable::InvalidDataModel; + break; + } + + switch (node->binding) { + case DocumentModel::Scxml::EarlyBinding: + m_stateTable.binding = StateTable::EarlyBinding; + break; + case DocumentModel::Scxml::LateBinding: + m_stateTable.binding = StateTable::LateBinding; + m_bindLate = true; + break; + default: + Q_UNREACHABLE(); + } + + m_stateTable.name = addString(node->name); + + m_parents.append(-1); + visit(node->children); + + m_dataElements.append(node->dataElements); + if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { + setInitialSetup(startNewSequence()); + generate(m_dataElements); + if (node->script) { + node->script->accept(this); + } + visit(&node->initialSetup); + endSequence(); + } + + QVector<DocumentModel::AbstractState *> childStates; + foreach (DocumentModel::StateOrTransition *sot, node->children) { + if (DocumentModel::AbstractState *s = dynamic_cast<DocumentModel::AbstractState *>(sot)) { + childStates.append(s); + } + } + addStates(&m_stateTable.childStates, childStates); + if (node->initialTransition) { + visit(node->initialTransition); + const int transitionIndex = m_docTransitionIndices.value(node->initialTransition, -1); + Q_ASSERT(transitionIndex != -1); + m_stateTable.initialTransition = transitionIndex; + } + m_parents.removeLast(); + + return false; + } + + bool visit(DocumentModel::State *state) Q_DECL_OVERRIDE Q_DECL_FINAL + { + m_stateNames.add(state->id); + const int stateIndex = m_docStatesIndices.value(state, -1); + Q_ASSERT(stateIndex != -1); + StateTable::State &newState = m_allStates[stateIndex]; + newState.name = addString(state->id); + newState.parent = currentParent(); + + switch (state->type) { + case DocumentModel::State::Normal: + newState.type = StateTable::State::Normal; + break; + case DocumentModel::State::Parallel: + newState.type = StateTable::State::Parallel; + break; + case DocumentModel::State::Final: + newState.type = StateTable::State::Final; + newState.doneData = generate(state->doneData); + break; + default: + Q_UNREACHABLE(); + } + + m_parents.append(stateIndex); + + if (!state->dataElements.isEmpty()) { + if (m_bindLate) { + newState.initInstructions = startNewSequence(); + generate(state->dataElements); + endSequence(); + } else { + m_dataElements.append(state->dataElements); + } + } + + newState.entryInstructions = generate(state->onEntry); + newState.exitInstructions = generate(state->onExit); + if (!state->invokes.isEmpty()) { + QVector<int> factoryIds; + foreach (DocumentModel::Invoke *invoke, state->invokes) { + auto ctxt = createContext(QStringLiteral("invoke")); + QVector<QScxmlExecutableContent::StringId> namelist; + foreach (const QString &name, invoke->namelist) + namelist += addString(name); + QVector<QScxmlExecutableContent::Param> params; + foreach (DocumentModel::Param *param, invoke->params) { + QScxmlExecutableContent::Param p; + p.name = addString(param->name); + p.expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), + param->expr); + p.location = addString(param->location); + params.append(p); + } + QScxmlExecutableContent::ContainerId finalize = + QScxmlExecutableContent::NoInstruction; + if (!invoke->finalize.isEmpty()) { + finalize = startNewSequence(); + visit(&invoke->finalize); + endSequence(); + } + auto srcexpr = createEvaluatorString(QStringLiteral("invoke"), + QStringLiteral("srcexpr"), + invoke->srcexpr); + const int factoryId = createFactoryId( + ctxt, srcexpr, addString(invoke->id), + addString(state->id + QStringLiteral(".session-")), + addString(invoke->idLocation), namelist, invoke->autoforward, params, + finalize, invoke->content); + Q_ASSERT(factoryId >= 0); + factoryIds.append(factoryId); + m_stateTable.maxServiceId = std::max(m_stateTable.maxServiceId, factoryId); + if (invoke->content && invoke->content->root) { + QString name = invoke->content->root->name; + if (!name.isEmpty()) + m_subStateMachineNames.add(name); + } + } + addArray(&newState.serviceFactoryIds, factoryIds); + } + + visit(state->children); + + QVector<DocumentModel::AbstractState *> childStates; + foreach (DocumentModel::StateOrTransition *sot, state->children) { + if (auto s = dynamic_cast<DocumentModel::AbstractState *>(sot)) { + childStates.append(s); + } + } + addStates(&newState.childStates, childStates); + addArray(&newState.transitions, m_transitionsForState.at(stateIndex)); + if (state->initialTransition) { + visit(state->initialTransition); + newState.initialTransition = m_transitionsForState.at(stateIndex).last(); + } + m_parents.removeLast(); + + return false; + } + + bool visit(DocumentModel::Transition *transition) Q_DECL_OVERRIDE Q_DECL_FINAL + { + const int transitionIndex = m_docTransitionIndices.value(transition, -1); + Q_ASSERT(transitionIndex != -1); + StateTable::Transition &newTransition = m_allTransitions[transitionIndex]; + const int parentIndex = currentParent(); + if (parentIndex != -1) { + m_transitionsForState[parentIndex].append(transitionIndex); + } + newTransition.source = parentIndex; + + if (transition->condition) { + newTransition.condition = createEvaluatorBool(QStringLiteral("transition"), + QStringLiteral("cond"), + *transition->condition.data()); + } + + switch (transition->type) { + case DocumentModel::Transition::External: + newTransition.type = StateTable::Transition::External; + break; + case DocumentModel::Transition::Internal: + newTransition.type = StateTable::Transition::Internal; + break; + default: + Q_UNREACHABLE(); + } + + if (!transition->instructionsOnTransition.isEmpty()) { + m_currentTransition = transitionIndex; + newTransition.transitionInstructions = startNewSequence(); + visit(&transition->instructionsOnTransition); + endSequence(); + m_currentTransition = -1; + } + + addStates(&newTransition.targets, transition->targetStates); + + QVector<int> eventIds; + foreach (const QString &event, transition->events) { + eventIds.push_back(addString(event)); + if (m_qtMode) { + m_incomingEvents.add(event); + } + } + addArray(&newTransition.events, eventIds); + + return false; + } + + bool visit(DocumentModel::HistoryState *historyState) Q_DECL_OVERRIDE Q_DECL_FINAL + { + const int stateIndex = m_docStatesIndices.value(historyState, -1); + Q_ASSERT(stateIndex != -1); + StateTable::State &newState = m_allStates[stateIndex]; + newState.name = addString(historyState->id); + newState.parent = currentParent(); + + switch (historyState->type) { + case DocumentModel::HistoryState::Shallow: + newState.type = StateTable::State::ShallowHistory; + break; + case DocumentModel::HistoryState::Deep: + newState.type = StateTable::State::DeepHistory; + break; + default: + Q_UNREACHABLE(); + } + + m_parents.append(stateIndex); + visit(historyState->children); + m_parents.removeLast(); + addArray(&newState.transitions, m_transitionsForState.at(stateIndex)); + return false; + } + + bool visit(DocumentModel::Send *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + if (m_qtMode && node->type == QStringLiteral("qt:signal")) { + m_outgoingEvents.add(m_stringTable.item(addString(node->event))); + } + + auto instr = m_instructions.add<Send>(Send::calculateExtraSize(node->params.size(), + node->namelist.size())); + instr->instructionLocation = createContext(QStringLiteral("send")); + instr->event = addString(node->event); + instr->eventexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("eventexpr"), + node->eventexpr); + instr->type = addString(node->type); + instr->typeexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("typeexpr"), + node->typeexpr); + instr->target = addString(node->target); + instr->targetexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("targetexpr"), + node->targetexpr); + instr->id = addString(node->id); + instr->idLocation = addString(node->idLocation); + instr->delay = addString(node->delay); + instr->delayexpr = createEvaluatorString(QStringLiteral("send"), + QStringLiteral("delayexpr"), + node->delayexpr); + instr->content = addString(node->content); + generate(&instr->namelist, node->namelist); + generate(instr->params(), node->params); + return false; + } + + void visit(DocumentModel::Raise *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Raise>(); + instr->event = addString(node->event); + } + + void visit(DocumentModel::Log *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Log>(); + instr->label = addString(node->label); + instr->expr = createEvaluatorString(QStringLiteral("log"), + QStringLiteral("expr"), + node->expr); + } + + void visit(DocumentModel::Script *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<JavaScript>(); + instr->go = createEvaluatorVoid(QStringLiteral("script"), + QStringLiteral("source"), + node->content); + } + + void visit(DocumentModel::Assign *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Assign>(); + auto ctxt = createContext(QStringLiteral("assign"), QStringLiteral("expr"), node->expr); + instr->expression = addAssignment(node->location, node->expr, ctxt); + } + + bool visit(DocumentModel::If *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<If>(node->conditions.size()); + instr->conditions.count = node->conditions.size(); + auto it = instr->conditions.data(); + QString tag = QStringLiteral("if"); + for (int i = 0, ei = node->conditions.size(); i != ei; ++i) { + *it++ = createEvaluatorBool(tag, QStringLiteral("cond"), node->conditions.at(i)); + if (i == 0) { + tag = QStringLiteral("elif"); + } + } + auto outSequences = m_instructions.add<InstructionSequences>(); + generate(outSequences, node->blocks); + return false; + } + + bool visit(DocumentModel::Foreach *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Foreach>(); + auto ctxt = createContextString(QStringLiteral("foreach")); + instr->doIt = addForeach(node->array, node->item, node->index, ctxt); + startSequence(&instr->block); + visit(&node->block); + endSequence(); + return false; + } + + void visit(DocumentModel::Cancel *node) Q_DECL_OVERRIDE Q_DECL_FINAL + { + auto instr = m_instructions.add<Cancel>(); + instr->sendid = addString(node->sendid); + instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"), + QStringLiteral("sendidexpr"), + node->sendidexpr); + } + +protected: + ContainerId generate(const DocumentModel::DoneData *node) + { + auto id = m_instructions.newContainerId(); + DoneData *doneData; + if (node) { + doneData = m_instructions.add<DoneData>(node->params.size() * Param::calculateSize()); + doneData->contents = addString(node->contents); + doneData->expr = createEvaluatorString(QStringLiteral("donedata"), + QStringLiteral("expr"), + node->expr); + generate(&doneData->params, node->params); + } else { + doneData = m_instructions.add<DoneData>(); + doneData->contents = NoString; + doneData->expr = NoEvaluator; + doneData->params.count = 0; + } + doneData->location = createContext(QStringLiteral("final")); + return id; + } + + StringId createContext(const QString &instrName) + { + return addString(createContextString(instrName)); + } + + void generate(const QVector<DocumentModel::DataElement *> &dataElements) + { + foreach (DocumentModel::DataElement *el, dataElements) { + auto ctxt = createContext(QStringLiteral("data"), QStringLiteral("expr"), el->expr); + auto evaluator = addDataElement(el->id, el->expr, ctxt); + if (evaluator != NoEvaluator) { + auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>(); + instr->expression = evaluator; + } + } + } + + ContainerId generate(const DocumentModel::InstructionSequences &inSequences) + { + if (inSequences.isEmpty()) + return NoInstruction; + + auto id = m_instructions.newContainerId(); + auto outSequences = m_instructions.add<InstructionSequences>(); + generate(outSequences, inSequences); + return id; + } + + void generate(Array<Param> *out, const QVector<DocumentModel::Param *> &in) + { + out->count = in.size(); + Param *it = out->data(); + foreach (DocumentModel::Param *f, in) { + it->name = addString(f->name); + it->expr = createEvaluatorVariant(QStringLiteral("param"), QStringLiteral("expr"), + f->expr); + it->location = addString(f->location); + ++it; + } + } + + void generate(InstructionSequences *outSequences, + const DocumentModel::InstructionSequences &inSequences) + { + int sequencesOffset = m_instructions.offset(outSequences); + int sequenceCount = 0; + int entryCount = 0; + foreach (DocumentModel::InstructionSequence *sequence, inSequences) { + ++sequenceCount; + startNewSequence(); + visit(sequence); + entryCount += endSequence()->size(); + } + outSequences = m_instructions.at<InstructionSequences>(sequencesOffset); + outSequences->sequenceCount = sequenceCount; + outSequences->entryCount = entryCount; + } + + void generate(Array<StringId> *out, const QStringList &in) + { + out->count = in.size(); + StringId *it = out->data(); + foreach (const QString &str, in) { + *it++ = addString(str); + } + } + + ContainerId startNewSequence() + { + auto id = m_instructions.newContainerId(); + auto sequence = m_instructions.add<InstructionSequence>(); + startSequence(sequence); + return id; + } + + void startSequence(InstructionSequence *sequence) + { + SequenceInfo info; + info.location = m_instructions.offset(sequence); + info.entryCount = 0; + m_activeSequences.push_back(info); + m_instructions.setSequenceInfo(&m_activeSequences.last()); + sequence->instructionType = Instruction::Sequence; + sequence->entryCount = -1; // checked in endSequence + } + + InstructionSequence *endSequence() + { + SequenceInfo info = m_activeSequences.back(); + m_activeSequences.pop_back(); + m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? Q_NULLPTR : + &m_activeSequences.last()); + + auto sequence = m_instructions.at<InstructionSequence>(info.location); + Q_ASSERT(sequence->entryCount == -1); // set in startSequence + sequence->entryCount = info.entryCount; + if (!m_activeSequences.isEmpty()) + m_activeSequences.last().entryCount += info.entryCount; + return sequence; + } + + EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName, + const QString &expr) + { + if (!expr.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.stringEvaluators.insert(id, expr); + return id; + } else { + return addEvaluator(expr, createContext(instrName, attrName, expr)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName, + const QString &cond) + { + if (!cond.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.boolEvaluators.insert(id, cond); + return id; + } else { + return addEvaluator(cond, createContext(instrName, attrName, cond)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName, + const QString &expr) + { + if (!expr.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.variantEvaluators.insert(id, expr); + return id; + } else { + return addEvaluator(expr, createContext(instrName, attrName, expr)); + } + } + + return NoEvaluator; + } + + EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName, + const QString &stuff) + { + if (!stuff.isEmpty()) { + if (isCppDataModel()) { + auto id = m_evaluators.add(EvaluatorInfo(), false); + m_dataModelInfo.voidEvaluators.insert(id, stuff); + return id; + } else { + return addEvaluator(stuff, createContext(instrName, attrName, stuff)); + } + } + + return NoEvaluator; + } + + GeneratedTableData *tableData(const QVector<int> &stateMachineTable); + + StringId addString(const QString &str) + { return str.isEmpty() ? NoString : m_stringTable.add(str); } + + void setInitialSetup(ContainerId id) + { m_tableData.theInitialSetup = id; } + + void setName(const QString &name) + { m_tableData.theName = addString(name); } + + bool isCppDataModel() const + { return m_isCppDataModel; } + + void addStates(int *idx, const QVector<DocumentModel::AbstractState *> &states) + { + QVector<int> array; + foreach (auto *s, states) { + int si = m_docStatesIndices.value(s, -1); + Q_ASSERT(si != -1); + array.push_back(si); + } + + addArray(idx, array); + } + + void addArray(int *idx, const QVector<int> &array) + { + if (array.isEmpty()) { + *idx = -1; + return; + } + + *idx = m_arrays.size(); + m_arrays.push_back(array.size()); + m_arrays.append(array); + } + + int currentParent() const + { + return m_parents.last(); + } + + QString createContextString(const QString &instrName) const + { + if (m_currentTransition != -1) { + QString state; + int parent = m_allTransitions.at(m_currentTransition).source; + if (parent != -1) { + QString parentName = QStringLiteral("(none)"); + int name = m_allStates.at(parent).name; + if (name != -1) { + parentName = m_stringTable.item(name); + } + state = QStringLiteral(" of state '%1'").arg(parentName); + } + return QStringLiteral("%1 instruction in transition %3").arg(instrName, state); + } else { + QString parentName = QStringLiteral("(none)"); + const int parent = currentParent(); + if (parent != -1) { + const int name = m_allStates.at(parent).name; + if (name != -1) { + parentName = m_stringTable.item(name); + } + } + return QStringLiteral("%1 instruction in state %2").arg(instrName, parentName); + } + } + + QString createContext(const QString &instrName, const QString &attrName, + const QString &attrValue) const + { + const QString location = createContextString(instrName); + return QStringLiteral("%1 with %2=\"%3\"").arg(location, attrName, attrValue); + } + + EvaluatorId addEvaluator(const QString &expr, const QString &context) + { + EvaluatorInfo ei; + ei.expr = addString(expr); + ei.context = addString(context); + return m_evaluators.add(ei); + } + + EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context) + { + AssignmentInfo ai; + ai.dest = addString(dest); + ai.expr = addString(expr); + ai.context = addString(context); + return m_assignments.add(ai); + } + + EvaluatorId addForeach(const QString &array, const QString &item, const QString &index, + const QString &context) + { + ForeachInfo fi; + fi.array = addString(array); + fi.item = addString(item); + fi.index = addString(index); + fi.context = addString(context); + return m_foreaches.add(fi); + } + + EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context) + { + auto str = addString(id); + if (!m_dataIds.contains(str)) + m_dataIds.append(str); + if (expr.isEmpty()) + return NoEvaluator; + + return addAssignment(id, expr, context); + } + +private: + template <class Container, typename T, typename U> + class Table { + Container &elements; + QMap<T, int> indexForElement; + + public: + Table(Container &storage) + : elements(storage) + {} + + U add(const T &s, bool uniqueOnly = true) { + int pos = uniqueOnly ? indexForElement.value(s, -1) : -1; + if (pos == -1) { + pos = elements.size(); + elements.append(s); + indexForElement.insert(s, pos); + } + return pos; + } + + Container data() { + return elements; + } + + const T &item(U pos) const { + return elements.at(pos); + } + }; + + struct SequenceInfo { + int location; + qint32 entryCount; // the amount of qint32's that the instructions take up + }; + + class InstructionStorage { + public: + InstructionStorage(QVector<qint32> &storage) + : m_instr(storage) + , m_info(Q_NULLPTR) + {} + + ContainerId newContainerId() const { return m_instr.size(); } + + template <typename T> + T *add(int extra = 0) + { + int pos = m_instr.size(); + int size = sizeof(T) / sizeof(qint32) + extra; + if (m_info) + m_info->entryCount += size; + m_instr.resize(pos + size); + T *instr = at<T>(pos); + Q_ASSERT(instr->instructionType == 0); + instr->instructionType = T::kind(); + return instr; + } + + int offset(Instruction *instr) const + { + return reinterpret_cast<qint32 *>(instr) - m_instr.data(); + } + + template <typename T> + T *at(int offset) + { + return reinterpret_cast<T *>(&m_instr[offset]); + } + + void setSequenceInfo(SequenceInfo *info) + { + m_info = info; + } + + private: + QVector<qint32> &m_instr; + SequenceInfo *m_info; + }; + + QVector<SequenceInfo> m_activeSequences; + + GeneratedTableData::CreateFactoryId createFactoryId; + GeneratedTableData &m_tableData; + GeneratedTableData::DataModelInfo &m_dataModelInfo; + Table<QStringList, QString, StringId> m_stringTable; + InstructionStorage m_instructions; + Table<QVector<EvaluatorInfo>, EvaluatorInfo, EvaluatorId> m_evaluators; + Table<QVector<AssignmentInfo>, AssignmentInfo, EvaluatorId> m_assignments; + Table<QVector<ForeachInfo>, ForeachInfo, EvaluatorId> m_foreaches; + StringIds &m_dataIds; + bool m_isCppDataModel = false; + + StateTable m_stateTable; + QVector<int> m_parents; + QVector<qint32> m_arrays; + + QVector<StateTable::Transition> m_allTransitions; + QHash<DocumentModel::Transition *, int> m_docTransitionIndices; + QVector<StateTable::State> m_allStates; + QHash<DocumentModel::AbstractState *, int> m_docStatesIndices; + QVector<QVector<int>> m_transitionsForState; + + int m_currentTransition = StateTable::InvalidIndex; + bool m_bindLate = false; + bool m_qtMode = false; + QVector<DocumentModel::DataElement *> m_dataElements; + Table<QStringList, QString, int> m_outgoingEvents; + Table<QStringList, QString, int> m_stateNames; + Table<QStringList, QString, int> m_subStateMachineNames; + Table<QStringList, QString, int> m_incomingEvents; +}; + +} // anonymous namespace QScxmlTableData::~QScxmlTableData() {} -QT_END_NAMESPACE +void GeneratedTableData::build(DocumentModel::ScxmlDocument *doc, + GeneratedTableData *table, + MetaDataInfo *metaDataInfo, + DataModelInfo *dataModelInfo, + GeneratedTableData::CreateFactoryId func) +{ + TableDataBuilder builder(*table, *metaDataInfo, *dataModelInfo, func); + builder.buildTableData(doc); +} + +QString GeneratedTableData::toString(const int *stateMachineTable) +{ + QString result; + QTextStream out(&result); + + const StateTable *st = reinterpret_cast<const StateTable *>(stateMachineTable); + + out << "{" << endl + << "\t0x" << hex << st->version << dec << ", // version" << endl + << "\t" << st->name << ", // name" << endl + << "\t" << st->dataModel << ", // data-model" << endl + << "\t" << st->childStates << ", // child states array offset" << endl + << "\t" << st->initialTransition << ", // transition to initial states" << endl + << "\t" << st->initialSetup << ", // initial setup" << endl + << "\t" << st->binding << ", // binding" << endl + << "\t" << st->maxServiceId << ", // maxServiceId" << endl + << "\t" << st->stateOffset << ", " << st->stateCount + << ", // state offset and count" << endl + << "\t" << st->transitionOffset << ", " << st->transitionCount + << ", // transition offset and count" << endl + << "\t" << st->arrayOffset << ", " << st->arraySize << ", // array offset and size" << endl + << endl; + + out << "\t// States:" << endl; + for (int i = 0; i < st->stateCount; ++i) { + const StateTable::State &s = st->state(i); + out << "\t" + << s.name << ", " + << s.parent << ", " + << s.type << ", " + << s.initialTransition << ", " + << s.initInstructions << ", " + << s.entryInstructions << ", " + << s.exitInstructions << ", " + << s.doneData << ", " + << s.childStates << ", " + << s.transitions << ", " + << s.serviceFactoryIds << "," + << endl; + } + + out << endl + << "\t// Transitions:" << endl; + for (int i = 0; i < st->transitionCount; ++i) { + auto t = st->transition(i); + out << "\t" + << t.events << ", " + << t.condition << ", " + << t.type << ", " + << t.source << ", " + << t.targets << ", " + << t.transitionInstructions << ", " + << endl ; + } + + out << endl + << "\t// Arrays:" << endl; + int nextStart = 0; + while (nextStart < st->arraySize) { + const StateTable::Array a = st->array(nextStart); + out << "\t" << a.size() << ", "; + for (int j = 0; j < a.size(); ++j) { + out << a[j] << ", "; + } + out << endl; + nextStart += a.size() + 1; + } + + out << hex; + out << endl + << "\t0x" << StateTable::terminator << " // terminator" << endl + << "}"; + + return result; +} + +QString GeneratedTableData::string(StringId id) const +{ + return id == NoString ? QString() : theStrings.at(id); +} + +Instructions GeneratedTableData::instructions() const +{ + return const_cast<Instructions>(theInstructions.data()); +} + +EvaluatorInfo GeneratedTableData::evaluatorInfo(EvaluatorId evaluatorId) const +{ + return theEvaluators[evaluatorId]; +} + +AssignmentInfo GeneratedTableData::assignmentInfo(EvaluatorId assignmentId) const +{ + return theAssignments[assignmentId]; +} + +ForeachInfo GeneratedTableData::foreachInfo(EvaluatorId foreachId) const +{ + return theForeaches[foreachId]; +} + +StringId *GeneratedTableData::dataNames(int *count) const +{ + Q_ASSERT(count); + *count = theDataNameIds.size(); + return const_cast<StringId *>(theDataNameIds.data()); +} + +ContainerId GeneratedTableData::initialSetup() const +{ + return theInitialSetup; +} + +QString GeneratedTableData::name() const +{ + return string(theName); +} + +const qint32 *GeneratedTableData::stateMachineTable() const +{ + return theStateMachineTable.constData(); +} + +QScxmlInvokableServiceFactory *GeneratedTableData::serviceFactory(int id) const +{ + Q_UNUSED(id); + return nullptr; +} + +int GeneratedTableData::signalIndexForEvent(const QString &event) const +{ + Q_UNUSED(event); + return -1; +} diff --git a/src/scxml/qscxmltabledata.h b/src/scxml/qscxmltabledata.h index 59fb105..9f025bf 100644 --- a/src/scxml/qscxmltabledata.h +++ b/src/scxml/qscxmltabledata.h @@ -37,8 +37,8 @@ ** ****************************************************************************/ -#ifndef QSCXMLTABLEDATA_P -#define QSCXMLTABLEDATA_P +#ifndef QSCXMLTABLEDATA_H +#define QSCXMLTABLEDATA_H #include <QtScxml/qscxmlexecutablecontent.h> #include <QString> @@ -49,6 +49,8 @@ QT_BEGIN_NAMESPACE +class QScxmlInvokableServiceFactory; + class Q_SCXML_EXPORT QScxmlTableData { public: @@ -63,8 +65,12 @@ public: virtual QScxmlExecutableContent::ContainerId initialSetup() const = 0; virtual QString name() const = 0; + + virtual const qint32 *stateMachineTable() const = 0; + virtual QScxmlInvokableServiceFactory *serviceFactory(int id) const = 0; + virtual int signalIndexForEvent(const QString &event) const = 0; }; QT_END_NAMESPACE -#endif // QSCXMLTABLEDATA_P +#endif // QSCXMLTABLEDATA_H diff --git a/src/scxml/qscxmltabledata_p.h b/src/scxml/qscxmltabledata_p.h new file mode 100644 index 0000000..85d3145 --- /dev/null +++ b/src/scxml/qscxmltabledata_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtScxml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCXMLTABLEDATA_P_H +#define QSCXMLTABLEDATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtScxml/qscxmltabledata.h> +#include <QtCore/qsharedpointer.h> +#include <QtCore/qhash.h> + +#include <functional> + +QT_BEGIN_NAMESPACE +class QTextStream; +class QScxmlInvokableServiceFactory; + +namespace DocumentModel { +struct ScxmlDocument; +} + +namespace QScxmlInternal { +class Q_SCXML_EXPORT GeneratedTableData: public QScxmlTableData +{ +public: + typedef std::function< + int(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, + QSharedPointer<DocumentModel::ScxmlDocument> content) + > CreateFactoryId; + + struct MetaDataInfo { + QStringList outgoingEvents; + QStringList stateNames; + QStringList subStateMachineNames; + QStringList incomingEvents; + }; + + struct DataModelInfo { + QHash<QScxmlExecutableContent::EvaluatorId, QString> stringEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> boolEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> variantEvaluators; + QHash<QScxmlExecutableContent::EvaluatorId, QString> voidEvaluators; + }; + +public: + static void build(DocumentModel::ScxmlDocument *doc, GeneratedTableData *table, + MetaDataInfo *metaDataInfo, DataModelInfo *dataModelInfo, + CreateFactoryId func); + static QString toString(const int *stateMachineTable); + +public: + QString string(QScxmlExecutableContent::StringId id) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::Instructions instructions() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::EvaluatorInfo evaluatorInfo( + QScxmlExecutableContent::EvaluatorId evaluatorId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::AssignmentInfo assignmentInfo( + QScxmlExecutableContent::EvaluatorId assignmentId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::ForeachInfo foreachInfo( + QScxmlExecutableContent::EvaluatorId foreachId) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::StringId *dataNames(int *count) const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlExecutableContent::ContainerId initialSetup() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QString name() const Q_DECL_OVERRIDE Q_DECL_FINAL; + const qint32 *stateMachineTable() const Q_DECL_OVERRIDE Q_DECL_FINAL; + QScxmlInvokableServiceFactory *serviceFactory(int id) const Q_DECL_OVERRIDE; + int signalIndexForEvent(const QString &event) const Q_DECL_OVERRIDE; + +public: + QVector<qint32> theStateMachineTable; + QStringList theStrings; + QVector<qint32> theInstructions; + QVector<QScxmlExecutableContent::EvaluatorInfo> theEvaluators; + QVector<QScxmlExecutableContent::AssignmentInfo> theAssignments; + QVector<QScxmlExecutableContent::ForeachInfo> theForeaches; + QScxmlExecutableContent::StringIds theDataNameIds; + QScxmlExecutableContent::EvaluatorId theInitialSetup; + int theName; +}; +} // QScxmlInternal namespace + +QT_END_NAMESPACE + +#endif // QSCXMLTABLEDATA_P_H diff --git a/src/scxml/scxml.pro b/src/scxml/scxml.pro index 40601e3..4453a83 100644 --- a/src/scxml/scxml.pro +++ b/src/scxml/scxml.pro @@ -25,13 +25,12 @@ HEADERS += \ qscxmlevent_p.h \ qscxmldatamodel.h \ qscxmldatamodel_p.h \ - qscxmlqstates.h \ - qscxmlqstates_p.h \ qscxmlcppdatamodel_p.h \ qscxmlcppdatamodel.h \ qscxmlerror.h \ qscxmlinvokableservice.h \ - qscxmltabledata.h + qscxmltabledata.h \ + qscxmltabledata_p.h SOURCES += \ qscxmlparser.cpp \ @@ -42,7 +41,6 @@ SOURCES += \ qscxmlexecutablecontent.cpp \ qscxmlevent.cpp \ qscxmldatamodel.cpp \ - qscxmlqstates.cpp \ qscxmlcppdatamodel.cpp \ qscxmlerror.cpp \ qscxmlinvokableservice.cpp \ diff --git a/tests/auto/compiled/tst_compiled.cpp b/tests/auto/compiled/tst_compiled.cpp index 49174e3..c6ee894 100644 --- a/tests/auto/compiled/tst_compiled.cpp +++ b/tests/auto/compiled/tst_compiled.cpp @@ -54,15 +54,16 @@ void tst_Compiled::stateNames() { ids1 stateMachine; + // The states have to be appear in document order: QStringList ids1States({ - "_", - "_VALID", - "__valid", - "foo-bar", "foo.bar", + "foo-bar", "foo_bar", - "n_0xe4_l", + "_", "näl", + "n_0xe4_l", + "_VALID", + "__valid", "qÿ̀i", }); diff --git a/tests/auto/scion/tst_scion.cpp b/tests/auto/scion/tst_scion.cpp index c06ac1b..5b7bded 100644 --- a/tests/auto/scion/tst_scion.cpp +++ b/tests/auto/scion/tst_scion.cpp @@ -55,8 +55,6 @@ static QSet<QString> testFailOnRun = QSet<QString>() << QLatin1String("w3c-ecma/test178.txml") // We do not support the optional basic http event i/o processor. << QLatin1String("w3c-ecma/test201.txml") - << QLatin1String("w3c-ecma/test364.txml") // initial attribute on <state> - << QLatin1String("w3c-ecma/test388.txml") // Qt refuses to set an initial state to a "deep" state << QLatin1String("w3c-ecma/test230.txml") << QLatin1String("w3c-ecma/test250.txml") << QLatin1String("w3c-ecma/test307.txml") @@ -387,7 +385,10 @@ bool TestScion::runTest(QScxmlStateMachine *stateMachine, const QJsonObject &tes return playEvents(stateMachine, testDescription); } else { // Wait for all events (delayed or otherwise) to propagate. - finishedSpy.fastWait(); // Some tests don't have a final state, so don't check for the result. + if (stateMachine->isRunning()) { + finishedSpy.fastWait(); // Some tests don't have a final state, so don't check for the + // result + } return verifyStates(stateMachine, testDescription, QLatin1String("initialConfiguration"), 0); } } diff --git a/tests/auto/statemachine/eventoccurred.scxml b/tests/auto/statemachine/eventoccurred.scxml index 87aaf41..ffceb3f 100644 --- a/tests/auto/statemachine/eventoccurred.scxml +++ b/tests/auto/statemachine/eventoccurred.scxml @@ -41,13 +41,13 @@ <transition event="timeout" target="final"/> </state> <final id="final"/> - </state> - <!-- - The done.state.* events are internal, so expose them to the spy too by re-sending them as - external events: - --> - <transition event="done.state.*" cond="_event.type === 'internal'"> - <send eventexpr="_event.name"/> - </transition> + <!-- + The done.state.* events are internal, so expose them to the spy too by re-sending them as + external events: + --> + <transition event="done.state.*" cond="_event.type === 'internal'"> + <send eventexpr="_event.name"/> + </transition> + </state> </scxml> diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index fc20118..e2e0d22 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -62,18 +62,18 @@ void tst_StateMachine::stateNames_data() << (QStringList() << QString("a1") << QString("a2") << QString("final")); QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml") << false - << (QStringList() << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final") << QString("top")); + << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final")); QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml") << true << (QStringList() << QString("a") << QString("b")); QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml") << false - << (QStringList() << QString("a") << QString("b") << QString("super_top")); + << (QStringList() << QString("super_top") << QString("a") << QString("b")); QTest::newRow("ids1") << QString(":/tst_statemachine/ids1.scxml") << false - << (QStringList() << QString("_") << QString("foo-bar") - << QString("foo.bar") << QString("foo_bar")); + << (QStringList() << QString("foo.bar") << QString("foo-bar") + << QString("foo_bar") << QString("_")); } void tst_StateMachine::stateNames() @@ -100,13 +100,13 @@ void tst_StateMachine::activeStateNames_data() << (QStringList() << QString("a1") << QString("final")); QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml") << false - << (QStringList() << QString("a") << QString("a1") << QString("b") << QString("final") << QString("top")); + << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("b") << QString("final")); QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml") << true - << (QStringList() << QString("a")<< QString("b")); + << (QStringList() << QString("a") << QString("b")); QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml") << false - << (QStringList() << QString("a") << QString("b") << QString("super_top")); + << (QStringList() << QString("super_top") << QString("a") << QString("b")); } void tst_StateMachine::activeStateNames() @@ -150,12 +150,19 @@ void tst_StateMachine::eventOccurred() finishedSpy.wait(5000); - QCOMPARE(eventOccurredSpy.count(), 4); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(0).at(0)).name(), QLatin1String("internalEvent2")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(1).at(0)).name(), QLatin1String("externalEvent")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(2).at(0)).name(), QLatin1String("timeout")); - QCOMPARE(qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(3).at(0)).name(), QLatin1String("done.state.top")); + auto event = [&eventOccurredSpy](int eventIndex) -> QScxmlEvent { + return qvariant_cast<QScxmlEvent>(eventOccurredSpy.at(eventIndex).at(0)); + }; + QCOMPARE(eventOccurredSpy.count(), 4); + QCOMPARE(event(0).name(), QLatin1String("internalEvent2")); + QCOMPARE(event(0).eventType(), QScxmlEvent::ExternalEvent); + QCOMPARE(event(1).name(), QLatin1String("externalEvent")); + QCOMPARE(event(1).eventType(), QScxmlEvent::ExternalEvent); + QCOMPARE(event(2).name(), QLatin1String("timeout")); + QCOMPARE(event(2).eventType(), QScxmlEvent::ExternalEvent); + QCOMPARE(event(3).name(), QLatin1String("done.state.top")); + QCOMPARE(event(3).eventType(), QScxmlEvent::ExternalEvent); QCOMPARE(externalEventOccurredSpy.count(), 1); QCOMPARE(qvariant_cast<QScxmlEvent>(externalEventOccurredSpy.at(0).at(0)).name(), QLatin1String("externalEvent")); 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> |