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 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | using 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 |
201 | static 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 | |
237 | QScxmlExecutionEngine::QScxmlExecutionEngine(QScxmlStateMachine *stateMachine) |
238 | : stateMachine(stateMachine) |
239 | { |
240 | Q_ASSERT(stateMachine); |
241 | } |
242 | |
243 | bool QScxmlExecutionEngine::execute(ContainerId id, const QVariant &) |
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 | |
258 | const 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 | |
467 | QT_END_NAMESPACE |
468 | |