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

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