Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qscxmltabledata_p.h"
++ #include "qscxmlcompiler_p.h"
++ #include "qscxmlexecutablecontent_p.h"
++
++ #include <QtCore/qmap.h>
++
++ QT_USE_NAMESPACE
++
++ /*!
++ \class QScxmlTableData
++ \since 5.8
++ \inmodule QtScxml
++ \brief The QScxmlTableData class is used by compiled state machines.
++
++ QScxmlTableData is the interface to the compiled representation of SCXML
++ state machines. It should only be used internally and by state machines
++ compiled from SCXML documents.
++ */
++
++ /*!
++ \fn QScxmlTableData::string(QScxmlExecutableContent::StringId id) const
++ Returns a QString for the given \a id.
++ */
++
++ /*!
++ \fn QScxmlTableData::instructions() const
++ Returns a pointer to the instructions of executable content contained in
++ the state machine.
++ */
++
++ /*!
++ \fn QScxmlTableData::evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const
++ Returns the QScxmlExecutableContent::EvaluatorInfo object for the given \a evaluatorId.
++ */
++
++ /*!
++ \fn QScxmlTableData::assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const
++ Returns the QScxmlExecutableContent::AssignmentInfo object for the given \a assignmentId.
++ */
++
++ /*!
++ \fn QScxmlTableData::foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const
++ Returns the QScxmlExecutableContent::ForeachInfo object for the given \a foreachId.
++ */
++
++ /*!
++ \fn QScxmlTableData::dataNames(int *count) const
++ Retrieves the string IDs for the names of data items in the data model. The
++ number of strings is saved into \a count and a pointer to an array of
++ string IDs is returned.
++
++ Returns a pointer to an array of string IDs.
++ */
++
++ /*!
++ \fn QScxmlTableData::initialSetup() const
++ Initializes the table data. Returns the ID of the container with
++ instructions to be executed when initializing the state machine.
++ */
++
++ /*!
++ \fn QScxmlTableData::name() const
++ Returns the name of the state machine.
++ */
++
++ /*!
++ \fn QScxmlTableData::stateMachineTable() const
++ Returns a pointer to the complete state table, expressed as an opaque
++ sequence of integers.
++ */
++
++ /*!
++ \fn QScxmlTableData::serviceFactory(int id) const
++ Returns the service factory that creates invokable services for the state
++ with the ID \a id.
++ */
++
++ 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_stateNames(metaDataInfo.stateNames)
++
++ {
++ m_activeSequences.reserve(4);
++ tableData.theInitialSetup = QScxmlExecutableContent::NoContainer;
++ }
++
++ void buildTableData(DocumentModel::ScxmlDocument *doc)
++ {
++ m_isCppDataModel = doc->root->dataModel == DocumentModel::Scxml::CppDataModel;
++ m_parents.reserve(32);
++ m_allTransitions.resize(doc->allTransitions.size());
++ m_docTransitionIndices.reserve(doc->allTransitions.size());
++ for (auto *t : std::as_const(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());
++ for (DocumentModel::AbstractState *s : std::as_const(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;
++ QList<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) override 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();
++ }
++
++ QList<DocumentModel::AbstractState *> childStates;
++ for (DocumentModel::StateOrTransition *sot : std::as_const(node->children)) {
++ if (DocumentModel::AbstractState *s = sot->asAbstractState()) {
++ childStates.append(s);
++ }
++ }
++ m_stateTable.childStates = addStates(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) override 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()) {
++ QList<int> factoryIds;
++ for (DocumentModel::Invoke *invoke : std::as_const(state->invokes)) {
++ auto ctxt = createContext(QStringLiteral("invoke"));
++ QList<QScxmlExecutableContent::StringId> namelist;
++ for (const QString &name : std::as_const(invoke->namelist))
++ namelist += addString(name);
++ QList<QScxmlExecutableContent::ParameterInfo> params;
++ for (DocumentModel::Param *param : std::as_const(invoke->params)) {
++ QScxmlExecutableContent::ParameterInfo 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::NoContainer;
++ if (!invoke->finalize.isEmpty()) {
++ finalize = startNewSequence();
++ visit(&invoke->finalize);
++ endSequence();
++ }
++ auto srcexpr = createEvaluatorString(QStringLiteral("invoke"),
++ QStringLiteral("srcexpr"),
++ invoke->srcexpr);
++ QScxmlExecutableContent::InvokeInfo invokeInfo;
++ invokeInfo.id = addString(invoke->id);
++ invokeInfo.prefix = addString(state->id + QStringLiteral(".session-"));
++ invokeInfo.location = addString(invoke->idLocation);
++ invokeInfo.context = ctxt;
++ invokeInfo.expr = srcexpr;
++ invokeInfo.finalize = finalize;
++ invokeInfo.autoforward = invoke->autoforward;
++ const int factoryId = createFactoryId(invokeInfo, namelist, params,
++ invoke->content);
++ Q_ASSERT(factoryId >= 0);
++ factoryIds.append(factoryId);
++ m_stateTable.maxServiceId = std::max(m_stateTable.maxServiceId, factoryId);
++ }
++ newState.serviceFactoryIds = addArray(factoryIds);
++ }
++
++ visit(state->children);
++
++ QList<DocumentModel::AbstractState *> childStates;
++ for (DocumentModel::StateOrTransition *sot : std::as_const(state->children)) {
++ if (auto s = sot->asAbstractState()) {
++ childStates.append(s);
++ }
++ }
++ newState.childStates = addStates(childStates);
++ newState.transitions = addArray(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) override 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);
++ }
++
++ switch (transition->type) {
++ case DocumentModel::Transition::External:
++ newTransition.type = StateTable::Transition::External;
++ break;
++ case DocumentModel::Transition::Internal:
++ newTransition.type = StateTable::Transition::Internal;
++ break;
++ case DocumentModel::Transition::Synthetic:
++ newTransition.type = StateTable::Transition::Synthetic;
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++
++ if (!transition->instructionsOnTransition.isEmpty()) {
++ m_currentTransition = transitionIndex;
++ newTransition.transitionInstructions = startNewSequence();
++ visit(&transition->instructionsOnTransition);
++ endSequence();
++ m_currentTransition = -1;
++ }
++
++ newTransition.targets = addStates(transition->targetStates);
++
++ QList<int> eventIds;
++ for (const QString &event : std::as_const(transition->events))
++ eventIds.push_back(addString(event));
++
++ newTransition.events = addArray(eventIds);
++
++ return false;
++ }
++
++ bool visit(DocumentModel::HistoryState *historyState) override 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();
++ newState.transitions = addArray(m_transitionsForState.at(stateIndex));
++ return false;
++ }
++
++ bool visit(DocumentModel::Send *node) override final
++ {
++ 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);
++ instr->contentexpr = createEvaluatorString(QStringLiteral("send"),
++ QStringLiteral("contentexpr"),
++ node->contentexpr);
++ generate(&instr->namelist, node->namelist);
++ generate(instr->params(), node->params);
++ return false;
++ }
++
++ void visit(DocumentModel::Raise *node) override final
++ {
++ auto instr = m_instructions.add<Raise>();
++ instr->event = addString(node->event);
++ }
++
++ void visit(DocumentModel::Log *node) override 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) override final
++ {
++ auto instr = m_instructions.add<JavaScript>();
++ instr->go = createEvaluatorVoid(QStringLiteral("script"),
++ QStringLiteral("source"),
++ node->content);
++ }
++
++ void visit(DocumentModel::Assign *node) override 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) override 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) override 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) override final
++ {
++ auto instr = m_instructions.add<Cancel>();
++ instr->sendid = addString(node->sendid);
++ instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel"),
++ QStringLiteral("sendidexpr"),
++ node->sendidexpr);
++ }
++
++ protected:
++ static int paramSize() { return sizeof(ParameterInfo) / sizeof(qint32); }
++
++ ContainerId generate(const DocumentModel::DoneData *node)
++ {
++ auto id = m_instructions.newContainerId();
++ DoneData *doneData;
++ if (node) {
++ doneData = m_instructions.add<DoneData>(node->params.size() * paramSize());
++ 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 QList<DocumentModel::DataElement *> &dataElements)
++ {
++ for (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 NoContainer;
++
++ auto id = m_instructions.newContainerId();
++ auto outSequences = m_instructions.add<InstructionSequences>();
++ generate(outSequences, inSequences);
++ return id;
++ }
++
++ void generate(Array<ParameterInfo> *out, const QList<DocumentModel::Param *> &in)
++ {
++ out->count = in.size();
++ ParameterInfo *it = out->data();
++ for (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;
++ for (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();
++ for (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() ? 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 QList<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; }
++
++ int addStates(const QList<DocumentModel::AbstractState *> &states)
++ {
++ QList<int> array;
++ for (auto *s : states) {
++ int si = m_docStatesIndices.value(s, -1);
++ Q_ASSERT(si != -1);
++ array.push_back(si);
++ }
++
++ return addArray(array);
++ }
++
++ int addArray(const QList<int> &array)
++ {
++ if (array.isEmpty())
++ return -1;
++
++ const int res = m_arrays.size();
++ m_arrays.push_back(array.size());
++ m_arrays.append(array);
++ return res;
++ }
++
++ 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(QList<qint32> &storage)
++ : m_instr(storage)
++ , m_info(nullptr)
++ {}
++
++ ContainerId newContainerId() const { return m_instr.size(); }
++
++ template <typename T>
++ T *add(int extra = 0)
++ {
++ const int pos = m_instr.size();
++ const 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:
++ QList<qint32> &m_instr;
++ SequenceInfo *m_info;
++ };
++
++ QList<SequenceInfo> m_activeSequences;
++
++ GeneratedTableData::CreateFactoryId createFactoryId;
++ GeneratedTableData &m_tableData;
++ GeneratedTableData::DataModelInfo &m_dataModelInfo;
++ Table<QStringList, QString, StringId> m_stringTable;
++ InstructionStorage m_instructions;
++ Table<QList<EvaluatorInfo>, EvaluatorInfo, EvaluatorId> m_evaluators;
++ Table<QList<AssignmentInfo>, AssignmentInfo, EvaluatorId> m_assignments;
++ Table<QList<ForeachInfo>, ForeachInfo, EvaluatorId> m_foreaches;
++ QList<StringId> &m_dataIds;
++ bool m_isCppDataModel = false;
++
++ StateTable m_stateTable;
++ QList<int> m_parents;
++ QList<qint32> m_arrays;
++
++ QList<StateTable::Transition> m_allTransitions;
++ QHash<DocumentModel::Transition *, int> m_docTransitionIndices;
++ QList<StateTable::State> m_allStates;
++ QHash<DocumentModel::AbstractState *, int> m_docStatesIndices;
++ QList<QList<int>> m_transitionsForState;
++
++ int m_currentTransition = StateTable::InvalidIndex;
++ bool m_bindLate = false;
++ QList<DocumentModel::DataElement *> m_dataElements;
++ Table<QStringList, QString, int> m_stateNames;
++ };
++
++ } // anonymous namespace
++
++ /*!
++ \fn QScxmlTableData::~QScxmlTableData()
++ Destroys the SXCML table data.
++ */
++ QScxmlTableData::~QScxmlTableData()
++ {}
++
++ 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 << "{" << Qt::endl
++ << "\t0x" << Qt::hex << st->version << Qt::dec << ", // version" << Qt::endl
++ << "\t" << st->name << ", // name" << Qt::endl
++ << "\t" << st->dataModel << ", // data-model" << Qt::endl
++ << "\t" << st->childStates << ", // child states array offset" << Qt::endl
++ << "\t" << st->initialTransition << ", // transition to initial states" << Qt::endl
++ << "\t" << st->initialSetup << ", // initial setup" << Qt::endl
++ << "\t" << st->binding << ", // binding" << Qt::endl
++ << "\t" << st->maxServiceId << ", // maxServiceId" << Qt::endl
++ << "\t" << st->stateOffset << ", " << st->stateCount
++ << ", // state offset and count" << Qt::endl
++ << "\t" << st->transitionOffset << ", " << st->transitionCount
++ << ", // transition offset and count" << Qt::endl
++ << "\t" << st->arrayOffset << ", " << st->arraySize << ", // array offset and size" << Qt::endl
++ << Qt::endl;
++
++ out << "\t// States:" << Qt::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 << ","
++ << Qt::endl;
++ }
++
++ out << Qt::endl
++ << "\t// Transitions:" << Qt::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 << ", "
++ << Qt::endl ;
++ }
++
++ out << Qt::endl
++ << "\t// Arrays:" << Qt::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 << Qt::endl;
++ nextStart += a.size() + 1;
++ }
++
++ out << Qt::hex;
++ out << Qt::endl
++ << "\t0x" << StateTable::terminator << " // terminator" << Qt::endl
++ << "}";
++
++ return result;
++ }
++
++ QString GeneratedTableData::string(StringId id) const
++ {
++ return id == NoString ? QString() : theStrings.at(id);
++ }
++
++ InstructionId *GeneratedTableData::instructions() const
++ {
++ return const_cast<InstructionId *>(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;
++ }