/**************************************************************************** ** ** 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 "qscxmlexecutablecontent_p.h" #include "qscxmlcompiler_p.h" #include "qscxmlevent_p.h" QT_BEGIN_NAMESPACE using namespace QScxmlExecutableContent; #ifndef BUILD_QSCXMLC static int parseTime(const QString &t, bool *ok = 0) { 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(ip); switch (instr->instructionType) { case Instruction::Sequence: { qCDebug(qscxmlLog) << stateMachine << "Executing sequence step"; const InstructionSequence *sequence = reinterpret_cast(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(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(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(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(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) {} bool run() Q_DECL_OVERRIDE { bool ok = true; engine->step(loopStart, &ok); return ok; } }; qCDebug(qscxmlLog) << stateMachine << "Executing foreach step"; const Foreach *_foreach = reinterpret_cast(instr); const InstructionId *loopStart = _foreach->blockstart(); ip += _foreach->size(); LoopBody body(this, loopStart); *ok = dataModel->evaluateForeach(_foreach->doIt, ok, &body) && *ok; return ip; } case Instruction::Raise: { qCDebug(qscxmlLog) << stateMachine << "Executing raise step"; const Raise *raise = reinterpret_cast(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(instr); ip += log->size(); QString str = dataModel->evaluateToString(log->expr, ok); if (*ok) { 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(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(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(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(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