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 | |
45 | QT_BEGIN_NAMESPACE |
46 | |
47 | using 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 |
237 | static 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 | |
273 | QScxmlExecutionEngine::QScxmlExecutionEngine(QScxmlStateMachine *stateMachine) |
274 | : stateMachine(stateMachine) |
275 | { |
276 | Q_ASSERT(stateMachine); |
277 | } |
278 | |
279 | bool QScxmlExecutionEngine::execute(ContainerId id, const QVariant &) |
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 | |
294 | const 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 | |
503 | QT_END_NAMESPACE |
504 | |