1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtScxml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qscxmlglobals_p.h"
41#include "qscxmlexecutablecontent_p.h"
42#include "qscxmlcompiler_p.h"
43#include "qscxmlevent_p.h"
44
45QT_BEGIN_NAMESPACE
46
47using namespace QScxmlExecutableContent;
48
49/*!
50 \namespace QScxmlExecutableContent
51 \inmodule QtScxml
52 \since 5.8
53 \brief The QScxmlExecutableContent namespace contains various types used
54 to interpret executable content in state machines.
55 */
56
57/*!
58 \typedef QScxmlExecutableContent::ContainerId
59 \inmodule QtScxml
60 \since 5.8
61 \brief ID for a container holding executable content.
62 */
63
64/*!
65 \typedef QScxmlExecutableContent::EvaluatorId
66 \inmodule QtScxml
67 \since 5.8
68 \brief ID for a unit of executable content.
69 */
70
71/*!
72 \typedef QScxmlExecutableContent::InstructionId
73 \inmodule QtScxml
74 \since 5.8
75 \brief ID for an instruction of executable content.
76 */
77
78/*!
79 \typedef QScxmlExecutableContent::StringId
80 \inmodule QtScxml
81 \since 5.8
82 \brief ID for a string contained in executable content.
83 */
84
85/*!
86 \enum QScxmlExecutableContent::anonymous
87 \since 5.8
88
89 This enum type holds the invalid values for type definitions.
90
91 \value NoContainer
92 \l ContainerId is unknown.
93 \value NoEvaluator
94 \l EvaluatorId is unknown.
95 \value NoInstruction
96 \l InstructionId is unknown.
97 \value NoString
98 \l StringId is unknown.
99*/
100
101/*!
102 \class QScxmlExecutableContent::EvaluatorInfo
103 \brief The EvaluatorInfo class represents a unit of executable content.
104 \since 5.8
105 \inmodule QtScxml
106 */
107
108/*!
109 \variable QScxmlExecutableContent::EvaluatorInfo::expr
110 \brief The expression to be evaluated
111 */
112
113/*!
114 \variable QScxmlExecutableContent::EvaluatorInfo::context
115 \brief The context for evaluating the expression
116 */
117
118/*!
119 \class QScxmlExecutableContent::AssignmentInfo
120 \brief The AssingmentInfo class represents a data assignment.
121 \since 5.8
122 \inmodule QtScxml
123 */
124
125/*!
126 \variable QScxmlExecutableContent::AssignmentInfo::expr
127 \brief The expression to be evaluated
128 */
129
130/*!
131 \variable QScxmlExecutableContent::AssignmentInfo::context
132 \brief The context for evaluating the expression
133 */
134
135/*!
136 \variable QScxmlExecutableContent::AssignmentInfo::dest
137 \brief The name of the data item to assign to
138 */
139
140/*!
141 \class QScxmlExecutableContent::ForeachInfo
142 \brief The ForeachInfo class represents a foreach construct.
143 \since 5.8
144 \inmodule QtScxml
145 */
146
147/*!
148 \variable QScxmlExecutableContent::ForeachInfo::array
149 \brief The name of the array that is iterated over
150 */
151
152/*!
153 \variable QScxmlExecutableContent::ForeachInfo::item
154 \brief The name of the iteration variable
155 */
156
157/*!
158 \variable QScxmlExecutableContent::ForeachInfo::index
159 \brief The name of the index variable
160 */
161
162/*!
163 \variable QScxmlExecutableContent::ForeachInfo::context
164 \brief The context for evaluating the expression
165 */
166
167/*!
168 \class QScxmlExecutableContent::ParameterInfo
169 \brief The ParameterInfo class represents a parameter to a service
170 invocation.
171 \since 5.8
172 \inmodule QtScxml
173 */
174
175/*!
176 \variable QScxmlExecutableContent::ParameterInfo::name
177 \brief The name of the parameter
178 */
179
180/*!
181 \variable QScxmlExecutableContent::ParameterInfo::expr
182 \brief The expression to be evaluated
183 */
184
185/*!
186 \variable QScxmlExecutableContent::ParameterInfo::location
187 \brief The data model name of the item to be passed as a parameter
188 */
189
190/*!
191 \class QScxmlExecutableContent::InvokeInfo
192 \brief The InvokeInfo class represents a service invocation.
193 \since 5.8
194 \inmodule QtScxml
195 */
196
197/*!
198 \variable QScxmlExecutableContent::InvokeInfo::id
199 \brief The ID specified by the \c id attribute in the \c <invoke> element.
200 */
201
202/*!
203 \variable QScxmlExecutableContent::InvokeInfo::prefix
204 \brief The unique prefix for this invocation in the context of the state
205 from which it is called
206 */
207
208/*!
209 \variable QScxmlExecutableContent::InvokeInfo::location
210 \brief The data model location to write the invocation ID to
211 */
212
213/*!
214 \variable QScxmlExecutableContent::InvokeInfo::context
215 \brief The context to interpret the location in
216 */
217
218/*!
219 \variable QScxmlExecutableContent::InvokeInfo::expr
220 \brief The expression representing the srcexpr of the invoke element
221 */
222
223/*!
224 \variable QScxmlExecutableContent::InvokeInfo::finalize
225 \brief The ID of the container of executable content to be run on finalizing
226 the invocation
227 */
228
229/*!
230 \variable QScxmlExecutableContent::InvokeInfo::autoforward
231 \brief Whether events should automatically be forwarded to the invoked
232 service
233 */
234
235
236#ifndef BUILD_QSCXMLC
237static int parseTime(const QString &t, bool *ok = nullptr)
238{
239 if (t.isEmpty()) {
240 if (ok)
241 *ok = false;
242 return -1;
243 }
244 bool negative = false;
245 int startPos = 0;
246 if (t[0] == QLatin1Char('-')) {
247 negative = true;
248 ++startPos;
249 } else if (t[0] == QLatin1Char('+')) {
250 ++startPos;
251 }
252 int pos = startPos;
253 for (int endPos = t.length(); pos < endPos; ++pos) {
254 auto c = t[pos];
255 if (c < QLatin1Char('0') || c > QLatin1Char('9'))
256 break;
257 }
258 if (pos == startPos) {
259 if (ok) *ok = false;
260 return -1;
261 }
262 int value = t.midRef(position: startPos, n: pos - startPos).toInt(ok);
263 if (ok && !*ok) return -1;
264 if (t.length() == pos + 1 && t[pos] == QLatin1Char('s')) {
265 value *= 1000;
266 } else if (t.length() != pos + 2 || t[pos] != QLatin1Char('m') || t[pos + 1] != QLatin1Char('s')) {
267 if (ok) *ok = false;
268 return -1;
269 }
270 return negative ? -value : value;
271}
272
273QScxmlExecutionEngine::QScxmlExecutionEngine(QScxmlStateMachine *stateMachine)
274 : stateMachine(stateMachine)
275{
276 Q_ASSERT(stateMachine);
277}
278
279bool QScxmlExecutionEngine::execute(ContainerId id, const QVariant &extraData)
280{
281 Q_ASSERT(stateMachine);
282
283 if (id == NoInstruction)
284 return true;
285
286 const InstructionId *ip = stateMachine->tableData()->instructions() + id;
287 this->extraData = extraData;
288 bool result = true;
289 step(ip, ok: &result);
290 this->extraData = QVariant();
291 return result;
292}
293
294const InstructionId *QScxmlExecutionEngine::step(const InstructionId *ip, bool *ok)
295{
296 auto dataModel = stateMachine->dataModel();
297 auto tableData = stateMachine->tableData();
298
299 *ok = true;
300 auto instr = reinterpret_cast<const Instruction *>(ip);
301 switch (instr->instructionType) {
302 case Instruction::Sequence: {
303 qCDebug(qscxmlLog) << stateMachine << "Executing sequence step";
304 const InstructionSequence *sequence = reinterpret_cast<const InstructionSequence *>(instr);
305 ip = sequence->instructions();
306 const InstructionId *end = ip + sequence->entryCount;
307 while (ip < end) {
308 ip = step(ip, ok);
309 if (!(*ok)) {
310 qCDebug(qscxmlLog) << stateMachine << "Finished sequence step UNsuccessfully";
311 return end;
312 }
313 }
314 qCDebug(qscxmlLog) << stateMachine << "Finished sequence step successfully";
315 return ip;
316 }
317
318 case Instruction::Sequences: {
319 qCDebug(qscxmlLog) << stateMachine << "Executing sequences step";
320 const InstructionSequences *sequences
321 = reinterpret_cast<const InstructionSequences *>(instr);
322 ip += sequences->size();
323 for (int i = 0; i != sequences->sequenceCount; ++i) {
324 bool ignored;
325 const InstructionId *sequence = sequences->at(pos: i);
326 step(ip: sequence, ok: &ignored);
327 }
328 qCDebug(qscxmlLog) << stateMachine << "Finished sequences step";
329 return ip;
330 }
331
332 case Instruction::Send: {
333 qCDebug(qscxmlLog) << stateMachine << "Executing send step";
334 const Send *send = reinterpret_cast<const Send *>(instr);
335 ip += send->size();
336
337 QString delay = tableData->string(id: send->delay);
338 if (send->delayexpr != NoEvaluator) {
339 delay = stateMachine->dataModel()->evaluateToString(id: send->delayexpr, ok);
340 if (!(*ok))
341 return ip;
342 }
343
344 QScxmlEvent *event = QScxmlEventBuilder(stateMachine, *send).buildEvent();
345 if (!event) {
346 *ok = false;
347 return ip;
348 }
349
350 if (!delay.isEmpty()) {
351 int msecs = parseTime(t: delay);
352 if (msecs >= 0) {
353 event->setDelay(msecs);
354 } else {
355 qCDebug(qscxmlLog) << stateMachine << "failed to parse delay time" << delay;
356 *ok = false;
357 return ip;
358 }
359 }
360
361 stateMachine->submitEvent(event);
362 return ip;
363 }
364
365 case Instruction::JavaScript: {
366 qCDebug(qscxmlLog) << stateMachine << "Executing script step";
367 const JavaScript *javascript = reinterpret_cast<const JavaScript *>(instr);
368 ip += javascript->size();
369 dataModel->evaluateToVoid(id: javascript->go, ok);
370 return ip;
371 }
372
373 case Instruction::If: {
374 qCDebug(qscxmlLog) << stateMachine << "Executing if step";
375 const If *_if = reinterpret_cast<const If *>(instr);
376 ip += _if->size();
377 auto blocks = _if->blocks();
378 for (qint32 i = 0; i < _if->conditions.count; ++i) {
379 bool conditionOk = true;
380 if (dataModel->evaluateToBool(id: _if->conditions.at(pos: i), ok: &conditionOk) && conditionOk) {
381 const InstructionId *block = blocks->at(pos: i);
382 step(ip: block, ok);
383 qCDebug(qscxmlLog) << stateMachine << "Finished if step";
384 return ip;
385 }
386 }
387
388 if (_if->conditions.count < blocks->sequenceCount)
389 step(ip: blocks->at(pos: _if->conditions.count), ok);
390
391 return ip;
392 }
393
394 case Instruction::Foreach: {
395 class LoopBody: public QScxmlDataModel::ForeachLoopBody // If only we could put std::function in public API, we could use a lambda here. Alas....
396 {
397 QScxmlExecutionEngine *engine;
398 const InstructionId *loopStart;
399
400 public:
401 LoopBody(QScxmlExecutionEngine *engine, const InstructionId *loopStart)
402 : engine(engine)
403 , loopStart(loopStart)
404 {}
405
406 void run(bool *ok) override
407 {
408 engine->step(ip: loopStart, ok);
409 }
410 };
411
412 qCDebug(qscxmlLog) << stateMachine << "Executing foreach step";
413 const Foreach *_foreach = reinterpret_cast<const Foreach *>(instr);
414 const InstructionId *loopStart = _foreach->blockstart();
415 ip += _foreach->size();
416 LoopBody body(this, loopStart);
417 dataModel->evaluateForeach(id: _foreach->doIt, ok, body: &body);
418 return ip;
419 }
420
421 case Instruction::Raise: {
422 qCDebug(qscxmlLog) << stateMachine << "Executing raise step";
423 const Raise *raise = reinterpret_cast<const Raise *>(instr);
424 ip += raise->size();
425 auto name = tableData->string(id: raise->event);
426 auto event = new QScxmlEvent;
427 event->setName(name);
428 event->setEventType(QScxmlEvent::InternalEvent);
429 stateMachine->submitEvent(event);
430 return ip;
431 }
432
433 case Instruction::Log: {
434 qCDebug(qscxmlLog) << stateMachine << "Executing log step";
435 const Log *log = reinterpret_cast<const Log *>(instr);
436 ip += log->size();
437 QString str;
438 if (log->expr != NoEvaluator) {
439 str = dataModel->evaluateToString(id: log->expr, ok);
440 if (!*ok)
441 qCWarning(qscxmlLog) << stateMachine << "Could not evaluate <log> expr to string.";
442 }
443
444 const QString label = tableData->string(id: log->label);
445 qCDebug(scxmlLog) << label << ":" << str;
446 QMetaObject::invokeMethod(obj: stateMachine,
447 member: "log",
448 type: Qt::QueuedConnection,
449 Q_ARG(QString, label),
450 Q_ARG(QString, str));
451 return ip;
452 }
453
454 case Instruction::Cancel: {
455 qCDebug(qscxmlLog) << stateMachine << "Executing cancel step";
456 const Cancel *cancel = reinterpret_cast<const Cancel *>(instr);
457 ip += cancel->size();
458 QString e = tableData->string(id: cancel->sendid);
459 if (cancel->sendidexpr != NoEvaluator)
460 e = dataModel->evaluateToString(id: cancel->sendidexpr, ok);
461 if (*ok && !e.isEmpty())
462 stateMachine->cancelDelayedEvent(sendId: e);
463 return ip;
464 }
465
466 case Instruction::Assign: {
467 qCDebug(qscxmlLog) << stateMachine << "Executing assign step";
468 const Assign *assign = reinterpret_cast<const Assign *>(instr);
469 ip += assign->size();
470 dataModel->evaluateAssignment(id: assign->expression, ok);
471 return ip;
472 }
473
474 case Instruction::Initialize: {
475 qCDebug(qscxmlLog) << stateMachine << "Executing initialize step";
476 const Initialize *init = reinterpret_cast<const Initialize *>(instr);
477 ip += init->size();
478 dataModel->evaluateInitialization(id: init->expression, ok);
479 return ip;
480 }
481
482 case Instruction::DoneData: {
483 qCDebug(qscxmlLog) << stateMachine << "Executing DoneData step";
484 const DoneData *doneData = reinterpret_cast<const DoneData *>(instr);
485
486 QString eventName = QStringLiteral("done.state.") + extraData.toString();
487 QScxmlEventBuilder event(stateMachine, eventName, doneData);
488 auto e = event();
489 e->setEventType(QScxmlEvent::InternalEvent);
490 qCDebug(qscxmlLog) << stateMachine << "submitting event" << eventName;
491 stateMachine->submitEvent(event: e);
492 return ip;
493 }
494
495 default:
496 Q_UNREACHABLE();
497 *ok = false;
498 return ip;
499 }
500}
501#endif // BUILD_QSCXMLC
502
503QT_END_NAMESPACE
504

source code of qtscxml/src/scxml/qscxmlexecutablecontent.cpp