| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 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 "qscxmlexecutablecontent_p.h" |
| #include "qscxmlcompiler_p.h" |
| #include "qscxmlevent_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace QScxmlExecutableContent; |
| |
| /*! |
| \namespace QScxmlExecutableContent |
| \inmodule QtScxml |
| \since 5.8 |
| \brief The QScxmlExecutableContent namespace contains various types used |
| to interpret executable content in state machines. |
| */ |
| |
| /*! |
| \typedef QScxmlExecutableContent::ContainerId |
| \inmodule QtScxml |
| \since 5.8 |
| \brief ID for a container holding executable content. |
| */ |
| |
| /*! |
| \typedef QScxmlExecutableContent::EvaluatorId |
| \inmodule QtScxml |
| \since 5.8 |
| \brief ID for a unit of executable content. |
| */ |
| |
| /*! |
| \typedef QScxmlExecutableContent::InstructionId |
| \inmodule QtScxml |
| \since 5.8 |
| \brief ID for an instruction of executable content. |
| */ |
| |
| /*! |
| \typedef QScxmlExecutableContent::StringId |
| \inmodule QtScxml |
| \since 5.8 |
| \brief ID for a string contained in executable content. |
| */ |
| |
| /*! |
| \enum QScxmlExecutableContent::anonymous |
| \since 5.8 |
| |
| This enum type holds the invalid values for type definitions. |
| |
| \value NoContainer |
| \l ContainerId is unknown. |
| \value NoEvaluator |
| \l EvaluatorId is unknown. |
| \value NoInstruction |
| \l InstructionId is unknown. |
| \value NoString |
| \l StringId is unknown. |
| */ |
| |
| /*! |
| \class QScxmlExecutableContent::EvaluatorInfo |
| \brief The EvaluatorInfo class represents a unit of executable content. |
| \since 5.8 |
| \inmodule QtScxml |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::EvaluatorInfo::expr |
| \brief The expression to be evaluated |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::EvaluatorInfo::context |
| \brief The context for evaluating the expression |
| */ |
| |
| /*! |
| \class QScxmlExecutableContent::AssignmentInfo |
| \brief The AssingmentInfo class represents a data assignment. |
| \since 5.8 |
| \inmodule QtScxml |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::AssignmentInfo::expr |
| \brief The expression to be evaluated |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::AssignmentInfo::context |
| \brief The context for evaluating the expression |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::AssignmentInfo::dest |
| \brief The name of the data item to assign to |
| */ |
| |
| /*! |
| \class QScxmlExecutableContent::ForeachInfo |
| \brief The ForeachInfo class represents a foreach construct. |
| \since 5.8 |
| \inmodule QtScxml |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ForeachInfo::array |
| \brief The name of the array that is iterated over |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ForeachInfo::item |
| \brief The name of the iteration variable |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ForeachInfo::index |
| \brief The name of the index variable |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ForeachInfo::context |
| \brief The context for evaluating the expression |
| */ |
| |
| /*! |
| \class QScxmlExecutableContent::ParameterInfo |
| \brief The ParameterInfo class represents a parameter to a service |
| invocation. |
| \since 5.8 |
| \inmodule QtScxml |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ParameterInfo::name |
| \brief The name of the parameter |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ParameterInfo::expr |
| \brief The expression to be evaluated |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::ParameterInfo::location |
| \brief The data model name of the item to be passed as a parameter |
| */ |
| |
| /*! |
| \class QScxmlExecutableContent::InvokeInfo |
| \brief The InvokeInfo class represents a service invocation. |
| \since 5.8 |
| \inmodule QtScxml |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::id |
| \brief The ID specified by the \c id attribute in the \c <invoke> element. |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::prefix |
| \brief The unique prefix for this invocation in the context of the state |
| from which it is called |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::location |
| \brief The data model location to write the invocation ID to |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::context |
| \brief The context to interpret the location in |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::expr |
| \brief The expression representing the srcexpr of the invoke element |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::finalize |
| \brief The ID of the container of executable content to be run on finalizing |
| the invocation |
| */ |
| |
| /*! |
| \variable QScxmlExecutableContent::InvokeInfo::autoforward |
| \brief Whether events should automatically be forwarded to the invoked |
| service |
| */ |
| |
| |
| #ifndef BUILD_QSCXMLC |
| static int parseTime(const QString &t, bool *ok = nullptr) |
| { |
| if (t.isEmpty()) { |
| if (ok) |
| *ok = false; |
| return -1; |
| } |
| bool negative = false; |
| int startPos = 0; |
| if (t[0] == QLatin1Char('-')) { |
| negative = true; |
| ++startPos; |
| } else if (t[0] == QLatin1Char('+')) { |
| ++startPos; |
| } |
| int pos = startPos; |
| for (int endPos = t.length(); pos < endPos; ++pos) { |
| auto c = t[pos]; |
| if (c < QLatin1Char('0') || c > QLatin1Char('9')) |
| break; |
| } |
| if (pos == startPos) { |
| if (ok) *ok = false; |
| return -1; |
| } |
| int value = t.midRef(startPos, pos - startPos).toInt(ok); |
| if (ok && !*ok) return -1; |
| if (t.length() == pos + 1 && t[pos] == QLatin1Char('s')) { |
| value *= 1000; |
| } else if (t.length() != pos + 2 || t[pos] != QLatin1Char('m') || t[pos + 1] != QLatin1Char('s')) { |
| if (ok) *ok = false; |
| return -1; |
| } |
| return negative ? -value : value; |
| } |
| |
| QScxmlExecutionEngine::QScxmlExecutionEngine(QScxmlStateMachine *stateMachine) |
| : stateMachine(stateMachine) |
| { |
| Q_ASSERT(stateMachine); |
| } |
| |
| bool QScxmlExecutionEngine::execute(ContainerId id, const QVariant &extraData) |
| { |
| Q_ASSERT(stateMachine); |
| |
| if (id == NoInstruction) |
| return true; |
| |
| const InstructionId *ip = stateMachine->tableData()->instructions() + id; |
| this->extraData = extraData; |
| bool result = true; |
| step(ip, &result); |
| this->extraData = QVariant(); |
| return result; |
| } |
| |
| const InstructionId *QScxmlExecutionEngine::step(const InstructionId *ip, bool *ok) |
| { |
| auto dataModel = stateMachine->dataModel(); |
| auto tableData = stateMachine->tableData(); |
| |
| *ok = true; |
| auto instr = reinterpret_cast<const Instruction *>(ip); |
| switch (instr->instructionType) { |
| case Instruction::Sequence: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing sequence step"; |
| const InstructionSequence *sequence = reinterpret_cast<const InstructionSequence *>(instr); |
| ip = sequence->instructions(); |
| const InstructionId *end = ip + sequence->entryCount; |
| while (ip < end) { |
| ip = step(ip, ok); |
| if (!(*ok)) { |
| qCDebug(qscxmlLog) << stateMachine << "Finished sequence step UNsuccessfully"; |
| return end; |
| } |
| } |
| qCDebug(qscxmlLog) << stateMachine << "Finished sequence step successfully"; |
| return ip; |
| } |
| |
| case Instruction::Sequences: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing sequences step"; |
| const InstructionSequences *sequences |
| = reinterpret_cast<const InstructionSequences *>(instr); |
| ip += sequences->size(); |
| for (int i = 0; i != sequences->sequenceCount; ++i) { |
| bool ignored; |
| const InstructionId *sequence = sequences->at(i); |
| step(sequence, &ignored); |
| } |
| qCDebug(qscxmlLog) << stateMachine << "Finished sequences step"; |
| return ip; |
| } |
| |
| case Instruction::Send: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing send step"; |
| const Send *send = reinterpret_cast<const Send *>(instr); |
| ip += send->size(); |
| |
| QString delay = tableData->string(send->delay); |
| if (send->delayexpr != NoEvaluator) { |
| delay = stateMachine->dataModel()->evaluateToString(send->delayexpr, ok); |
| if (!(*ok)) |
| return ip; |
| } |
| |
| QScxmlEvent *event = QScxmlEventBuilder(stateMachine, *send).buildEvent(); |
| if (!event) { |
| *ok = false; |
| return ip; |
| } |
| |
| if (!delay.isEmpty()) { |
| int msecs = parseTime(delay); |
| if (msecs >= 0) { |
| event->setDelay(msecs); |
| } else { |
| qCDebug(qscxmlLog) << stateMachine << "failed to parse delay time" << delay; |
| *ok = false; |
| return ip; |
| } |
| } |
| |
| stateMachine->submitEvent(event); |
| return ip; |
| } |
| |
| case Instruction::JavaScript: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing script step"; |
| const JavaScript *javascript = reinterpret_cast<const JavaScript *>(instr); |
| ip += javascript->size(); |
| dataModel->evaluateToVoid(javascript->go, ok); |
| return ip; |
| } |
| |
| case Instruction::If: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing if step"; |
| const If *_if = reinterpret_cast<const If *>(instr); |
| ip += _if->size(); |
| auto blocks = _if->blocks(); |
| for (qint32 i = 0; i < _if->conditions.count; ++i) { |
| bool conditionOk = true; |
| if (dataModel->evaluateToBool(_if->conditions.at(i), &conditionOk) && conditionOk) { |
| const InstructionId *block = blocks->at(i); |
| step(block, ok); |
| qCDebug(qscxmlLog) << stateMachine << "Finished if step"; |
| return ip; |
| } |
| } |
| |
| if (_if->conditions.count < blocks->sequenceCount) |
| step(blocks->at(_if->conditions.count), ok); |
| |
| return ip; |
| } |
| |
| case Instruction::Foreach: { |
| class LoopBody: public QScxmlDataModel::ForeachLoopBody // If only we could put std::function in public API, we could use a lambda here. Alas.... |
| { |
| QScxmlExecutionEngine *engine; |
| const InstructionId *loopStart; |
| |
| public: |
| LoopBody(QScxmlExecutionEngine *engine, const InstructionId *loopStart) |
| : engine(engine) |
| , loopStart(loopStart) |
| {} |
| |
| void run(bool *ok) override |
| { |
| engine->step(loopStart, ok); |
| } |
| }; |
| |
| qCDebug(qscxmlLog) << stateMachine << "Executing foreach step"; |
| const Foreach *_foreach = reinterpret_cast<const Foreach *>(instr); |
| const InstructionId *loopStart = _foreach->blockstart(); |
| ip += _foreach->size(); |
| LoopBody body(this, loopStart); |
| dataModel->evaluateForeach(_foreach->doIt, ok, &body); |
| return ip; |
| } |
| |
| case Instruction::Raise: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing raise step"; |
| const Raise *raise = reinterpret_cast<const Raise *>(instr); |
| ip += raise->size(); |
| auto name = tableData->string(raise->event); |
| auto event = new QScxmlEvent; |
| event->setName(name); |
| event->setEventType(QScxmlEvent::InternalEvent); |
| stateMachine->submitEvent(event); |
| return ip; |
| } |
| |
| case Instruction::Log: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing log step"; |
| const Log *log = reinterpret_cast<const Log *>(instr); |
| ip += log->size(); |
| QString str; |
| if (log->expr != NoEvaluator) { |
| str = dataModel->evaluateToString(log->expr, ok); |
| if (!*ok) |
| qCWarning(qscxmlLog) << stateMachine << "Could not evaluate <log> expr to string."; |
| } |
| |
| const QString label = tableData->string(log->label); |
| qCDebug(scxmlLog) << label << ":" << str; |
| QMetaObject::invokeMethod(stateMachine, |
| "log", |
| Qt::QueuedConnection, |
| Q_ARG(QString, label), |
| Q_ARG(QString, str)); |
| return ip; |
| } |
| |
| case Instruction::Cancel: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing cancel step"; |
| const Cancel *cancel = reinterpret_cast<const Cancel *>(instr); |
| ip += cancel->size(); |
| QString e = tableData->string(cancel->sendid); |
| if (cancel->sendidexpr != NoEvaluator) |
| e = dataModel->evaluateToString(cancel->sendidexpr, ok); |
| if (*ok && !e.isEmpty()) |
| stateMachine->cancelDelayedEvent(e); |
| return ip; |
| } |
| |
| case Instruction::Assign: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing assign step"; |
| const Assign *assign = reinterpret_cast<const Assign *>(instr); |
| ip += assign->size(); |
| dataModel->evaluateAssignment(assign->expression, ok); |
| return ip; |
| } |
| |
| case Instruction::Initialize: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing initialize step"; |
| const Initialize *init = reinterpret_cast<const Initialize *>(instr); |
| ip += init->size(); |
| dataModel->evaluateInitialization(init->expression, ok); |
| return ip; |
| } |
| |
| case Instruction::DoneData: { |
| qCDebug(qscxmlLog) << stateMachine << "Executing DoneData step"; |
| const DoneData *doneData = reinterpret_cast<const DoneData *>(instr); |
| |
| 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(e); |
| return ip; |
| } |
| |
| default: |
| Q_UNREACHABLE(); |
| *ok = false; |
| return ip; |
| } |
| } |
| #endif // BUILD_QSCXMLC |
| |
| QT_END_NAMESPACE |