1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 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 "qscxmltabledata_p.h" |
41 | #include "qscxmlcompiler_p.h" |
42 | #include "qscxmlexecutablecontent_p.h" |
43 | |
44 | QT_USE_NAMESPACE |
45 | |
46 | /*! |
47 | \class QScxmlTableData |
48 | \since 5.8 |
49 | \inmodule QtScxml |
50 | \brief The QScxmlTableData class is used by compiled state machines. |
51 | |
52 | QScxmlTableData is the interface to the compiled representation of SCXML |
53 | state machines. It should only be used internally and by state machines |
54 | compiled from SCXML documents. |
55 | */ |
56 | |
57 | /*! |
58 | \fn QScxmlTableData::string(QScxmlExecutableContent::StringId id) const |
59 | Returns a QString for the given \a id. |
60 | */ |
61 | |
62 | /*! |
63 | \fn QScxmlTableData::instructions() const |
64 | Returns a pointer to the instructions of executable content contained in |
65 | the state machine. |
66 | */ |
67 | |
68 | /*! |
69 | \fn QScxmlTableData::evaluatorInfo(QScxmlExecutableContent::EvaluatorId evaluatorId) const |
70 | Returns the QScxmlExecutableContent::EvaluatorInfo object for the given \a evaluatorId. |
71 | */ |
72 | |
73 | /*! |
74 | \fn QScxmlTableData::assignmentInfo(QScxmlExecutableContent::EvaluatorId assignmentId) const |
75 | Returns the QScxmlExecutableContent::AssignmentInfo object for the given \a assignmentId. |
76 | */ |
77 | |
78 | /*! |
79 | \fn QScxmlTableData::foreachInfo(QScxmlExecutableContent::EvaluatorId foreachId) const |
80 | Returns the QScxmlExecutableContent::ForeachInfo object for the given \a foreachId. |
81 | */ |
82 | |
83 | /*! |
84 | \fn QScxmlTableData::dataNames(int *count) const |
85 | Retrieves the string IDs for the names of data items in the data model. The |
86 | number of strings is saved into \a count and a pointer to an array of |
87 | string IDs is returned. |
88 | |
89 | Returns a pointer to an array of string IDs. |
90 | */ |
91 | |
92 | /*! |
93 | \fn QScxmlTableData::initialSetup() const |
94 | Initializes the table data. Returns the ID of the container with |
95 | instructions to be executed when initializing the state machine. |
96 | */ |
97 | |
98 | /*! |
99 | \fn QScxmlTableData::name() const |
100 | Returns the name of the state machine. |
101 | */ |
102 | |
103 | /*! |
104 | \fn QScxmlTableData::stateMachineTable() const |
105 | Returns a pointer to the complete state table, expressed as an opaque |
106 | sequence of integers. |
107 | */ |
108 | |
109 | /*! |
110 | \fn QScxmlTableData::serviceFactory(int id) const |
111 | Returns the service factory that creates invokable services for the state |
112 | with the ID \a id. |
113 | */ |
114 | |
115 | using namespace QScxmlInternal; |
116 | |
117 | namespace { |
118 | using namespace QScxmlExecutableContent; |
119 | |
120 | class TableDataBuilder: public DocumentModel::NodeVisitor |
121 | { |
122 | public: |
123 | TableDataBuilder(GeneratedTableData &tableData, |
124 | GeneratedTableData::MetaDataInfo &metaDataInfo, |
125 | GeneratedTableData::DataModelInfo &dataModelInfo, |
126 | GeneratedTableData::CreateFactoryId func) |
127 | : createFactoryId(func) |
128 | , m_tableData(tableData) |
129 | , m_dataModelInfo(dataModelInfo) |
130 | , m_stringTable(tableData.theStrings) |
131 | , m_instructions(tableData.theInstructions) |
132 | , m_evaluators(tableData.theEvaluators) |
133 | , m_assignments(tableData.theAssignments) |
134 | , m_foreaches(tableData.theForeaches) |
135 | , m_dataIds(tableData.theDataNameIds) |
136 | , m_stateNames(metaDataInfo.stateNames) |
137 | |
138 | { |
139 | m_activeSequences.reserve(asize: 4); |
140 | tableData.theInitialSetup = QScxmlExecutableContent::NoContainer; |
141 | } |
142 | |
143 | void buildTableData(DocumentModel::ScxmlDocument *doc) |
144 | { |
145 | m_isCppDataModel = doc->root->dataModel == DocumentModel::Scxml::CppDataModel; |
146 | m_parents.reserve(asize: 32); |
147 | m_allTransitions.resize(asize: doc->allTransitions.size()); |
148 | m_docTransitionIndices.reserve(asize: doc->allTransitions.size()); |
149 | for (auto *t : qAsConst(t&: doc->allTransitions)) { |
150 | m_docTransitionIndices.insert(akey: t, avalue: m_docTransitionIndices.size()); |
151 | } |
152 | m_docStatesIndices.reserve(asize: doc->allStates.size()); |
153 | m_transitionsForState.resize(asize: doc->allStates.size()); |
154 | m_allStates.resize(asize: doc->allStates.size()); |
155 | for (DocumentModel::AbstractState *s : qAsConst(t&: doc->allStates)) { |
156 | m_docStatesIndices.insert(akey: s, avalue: m_docStatesIndices.size()); |
157 | } |
158 | |
159 | doc->root->accept(visitor: this); |
160 | m_stateTable.version = Q_QSCXMLC_OUTPUT_REVISION; |
161 | generateStateMachineData(); |
162 | |
163 | m_tableData.theInstructions.squeeze(); |
164 | } |
165 | |
166 | void generateStateMachineData() |
167 | { |
168 | const int tableSize = sizeof(StateTable) / sizeof(qint32); |
169 | const int stateSize = qint32(sizeof(StateTable::State) / sizeof(qint32)); |
170 | const int transitionSize = qint32(sizeof(StateTable::Transition) / sizeof(qint32)); |
171 | |
172 | m_stateTable.stateOffset = tableSize; |
173 | m_stateTable.stateCount = m_allStates.size(); |
174 | m_stateTable.transitionOffset = m_stateTable.stateOffset + |
175 | m_stateTable.stateCount * stateSize; |
176 | m_stateTable.transitionCount = m_allTransitions.size(); |
177 | m_stateTable.arrayOffset = m_stateTable.transitionOffset + |
178 | m_stateTable.transitionCount * transitionSize; |
179 | m_stateTable.arraySize = m_arrays.size(); |
180 | |
181 | const qint32 dataSize = qint32(tableSize) |
182 | + (m_allStates.size() * stateSize) |
183 | + (m_allTransitions.size() * transitionSize) |
184 | + m_arrays.size() |
185 | + 1; |
186 | QVector<qint32> data(dataSize, -1); |
187 | qint32 *ptr = data.data(); |
188 | |
189 | memcpy(dest: ptr, src: &m_stateTable, n: sizeof(m_stateTable)); |
190 | ptr += tableSize; |
191 | |
192 | Q_ASSERT(ptr == data.constData() + m_stateTable.stateOffset); |
193 | memcpy(dest: ptr, src: m_allStates.constData(), |
194 | n: sizeof(StateTable::State) * size_t(m_allStates.size())); |
195 | ptr += stateSize * size_t(m_allStates.size()); |
196 | |
197 | Q_ASSERT(ptr == data.constData() + m_stateTable.transitionOffset); |
198 | memcpy(dest: ptr, src: m_allTransitions.constData(), |
199 | n: sizeof(StateTable::Transition) * size_t(m_allTransitions.size())); |
200 | ptr += transitionSize * size_t(m_allTransitions.size()); |
201 | |
202 | Q_ASSERT(ptr == data.constData() + m_stateTable.arrayOffset); |
203 | memcpy(dest: ptr, src: m_arrays.constData(), n: sizeof(qint32) * size_t(m_arrays.size())); |
204 | ptr += m_arrays.size(); |
205 | |
206 | *ptr++ = StateTable::terminator; |
207 | |
208 | Q_ASSERT(ptr == data.constData() + dataSize); |
209 | |
210 | m_tableData.theStateMachineTable = data; |
211 | } |
212 | |
213 | protected: // visitor |
214 | using NodeVisitor::visit; |
215 | |
216 | bool visit(DocumentModel::Scxml *node) override final |
217 | { |
218 | setName(node->name); |
219 | |
220 | switch (node->dataModel) { |
221 | case DocumentModel::Scxml::NullDataModel: |
222 | m_stateTable.dataModel = StateTable::NullDataModel; |
223 | break; |
224 | case DocumentModel::Scxml::JSDataModel: |
225 | m_stateTable.dataModel = StateTable::EcmaScriptDataModel; |
226 | break; |
227 | case DocumentModel::Scxml::CppDataModel: |
228 | m_stateTable.dataModel = StateTable::CppDataModel; |
229 | break; |
230 | default: |
231 | m_stateTable.dataModel = StateTable::InvalidDataModel; |
232 | break; |
233 | } |
234 | |
235 | switch (node->binding) { |
236 | case DocumentModel::Scxml::EarlyBinding: |
237 | m_stateTable.binding = StateTable::EarlyBinding; |
238 | break; |
239 | case DocumentModel::Scxml::LateBinding: |
240 | m_stateTable.binding = StateTable::LateBinding; |
241 | m_bindLate = true; |
242 | break; |
243 | default: |
244 | Q_UNREACHABLE(); |
245 | } |
246 | |
247 | m_stateTable.name = addString(str: node->name); |
248 | |
249 | m_parents.append(t: -1); |
250 | visit(children: node->children); |
251 | |
252 | m_dataElements.append(l: node->dataElements); |
253 | if (node->script || !m_dataElements.isEmpty() || !node->initialSetup.isEmpty()) { |
254 | setInitialSetup(startNewSequence()); |
255 | generate(dataElements: m_dataElements); |
256 | if (node->script) { |
257 | node->script->accept(visitor: this); |
258 | } |
259 | visit(sequence: &node->initialSetup); |
260 | endSequence(); |
261 | } |
262 | |
263 | QVector<DocumentModel::AbstractState *> childStates; |
264 | for (DocumentModel::StateOrTransition *sot : qAsConst(t&: node->children)) { |
265 | if (DocumentModel::AbstractState *s = sot->asAbstractState()) { |
266 | childStates.append(t: s); |
267 | } |
268 | } |
269 | m_stateTable.childStates = addStates(states: childStates); |
270 | if (node->initialTransition) { |
271 | visit(transition: node->initialTransition); |
272 | const int transitionIndex = m_docTransitionIndices.value(akey: node->initialTransition, adefaultValue: -1); |
273 | Q_ASSERT(transitionIndex != -1); |
274 | m_stateTable.initialTransition = transitionIndex; |
275 | } |
276 | m_parents.removeLast(); |
277 | |
278 | return false; |
279 | } |
280 | |
281 | bool visit(DocumentModel::State *state) override final |
282 | { |
283 | m_stateNames.add(s: state->id); |
284 | const int stateIndex = m_docStatesIndices.value(akey: state, adefaultValue: -1); |
285 | Q_ASSERT(stateIndex != -1); |
286 | StateTable::State &newState = m_allStates[stateIndex]; |
287 | newState.name = addString(str: state->id); |
288 | newState.parent = currentParent(); |
289 | |
290 | switch (state->type) { |
291 | case DocumentModel::State::Normal: |
292 | newState.type = StateTable::State::Normal; |
293 | break; |
294 | case DocumentModel::State::Parallel: |
295 | newState.type = StateTable::State::Parallel; |
296 | break; |
297 | case DocumentModel::State::Final: |
298 | newState.type = StateTable::State::Final; |
299 | newState.doneData = generate(node: state->doneData); |
300 | break; |
301 | default: |
302 | Q_UNREACHABLE(); |
303 | } |
304 | |
305 | m_parents.append(t: stateIndex); |
306 | |
307 | if (!state->dataElements.isEmpty()) { |
308 | if (m_bindLate) { |
309 | newState.initInstructions = startNewSequence(); |
310 | generate(dataElements: state->dataElements); |
311 | endSequence(); |
312 | } else { |
313 | m_dataElements.append(l: state->dataElements); |
314 | } |
315 | } |
316 | |
317 | newState.entryInstructions = generate(inSequences: state->onEntry); |
318 | newState.exitInstructions = generate(inSequences: state->onExit); |
319 | if (!state->invokes.isEmpty()) { |
320 | QVector<int> factoryIds; |
321 | for (DocumentModel::Invoke *invoke : qAsConst(t&: state->invokes)) { |
322 | auto ctxt = createContext(QStringLiteral("invoke" )); |
323 | QVector<QScxmlExecutableContent::StringId> namelist; |
324 | for (const QString &name : qAsConst(t&: invoke->namelist)) |
325 | namelist += addString(str: name); |
326 | QVector<QScxmlExecutableContent::ParameterInfo> params; |
327 | for (DocumentModel::Param *param : qAsConst(t&: invoke->params)) { |
328 | QScxmlExecutableContent::ParameterInfo p; |
329 | p.name = addString(str: param->name); |
330 | p.expr = createEvaluatorVariant(QStringLiteral("param" ), QStringLiteral("expr" ), |
331 | expr: param->expr); |
332 | p.location = addString(str: param->location); |
333 | params.append(t: p); |
334 | } |
335 | QScxmlExecutableContent::ContainerId finalize = |
336 | QScxmlExecutableContent::NoContainer; |
337 | if (!invoke->finalize.isEmpty()) { |
338 | finalize = startNewSequence(); |
339 | visit(sequence: &invoke->finalize); |
340 | endSequence(); |
341 | } |
342 | auto srcexpr = createEvaluatorString(QStringLiteral("invoke" ), |
343 | QStringLiteral("srcexpr" ), |
344 | expr: invoke->srcexpr); |
345 | QScxmlExecutableContent::InvokeInfo invokeInfo; |
346 | invokeInfo.id = addString(str: invoke->id); |
347 | invokeInfo.prefix = addString(str: state->id + QStringLiteral(".session-" )); |
348 | invokeInfo.location = addString(str: invoke->idLocation); |
349 | invokeInfo.context = ctxt; |
350 | invokeInfo.expr = srcexpr; |
351 | invokeInfo.finalize = finalize; |
352 | invokeInfo.autoforward = invoke->autoforward; |
353 | const int factoryId = createFactoryId(invokeInfo, namelist, params, |
354 | invoke->content); |
355 | Q_ASSERT(factoryId >= 0); |
356 | factoryIds.append(t: factoryId); |
357 | m_stateTable.maxServiceId = std::max(a: m_stateTable.maxServiceId, b: factoryId); |
358 | } |
359 | newState.serviceFactoryIds = addArray(array: factoryIds); |
360 | } |
361 | |
362 | visit(children: state->children); |
363 | |
364 | QVector<DocumentModel::AbstractState *> childStates; |
365 | for (DocumentModel::StateOrTransition *sot : qAsConst(t&: state->children)) { |
366 | if (auto s = sot->asAbstractState()) { |
367 | childStates.append(t: s); |
368 | } |
369 | } |
370 | newState.childStates = addStates(states: childStates); |
371 | newState.transitions = addArray(array: m_transitionsForState.at(i: stateIndex)); |
372 | if (state->initialTransition) { |
373 | visit(transition: state->initialTransition); |
374 | newState.initialTransition = m_transitionsForState.at(i: stateIndex).last(); |
375 | } |
376 | m_parents.removeLast(); |
377 | |
378 | return false; |
379 | } |
380 | |
381 | bool visit(DocumentModel::Transition *transition) override final |
382 | { |
383 | const int transitionIndex = m_docTransitionIndices.value(akey: transition, adefaultValue: -1); |
384 | Q_ASSERT(transitionIndex != -1); |
385 | StateTable::Transition &newTransition = m_allTransitions[transitionIndex]; |
386 | const int parentIndex = currentParent(); |
387 | if (parentIndex != -1) { |
388 | m_transitionsForState[parentIndex].append(t: transitionIndex); |
389 | } |
390 | newTransition.source = parentIndex; |
391 | |
392 | if (transition->condition) { |
393 | newTransition.condition = createEvaluatorBool(QStringLiteral("transition" ), |
394 | QStringLiteral("cond" ), |
395 | cond: *transition->condition.data()); |
396 | } |
397 | |
398 | switch (transition->type) { |
399 | case DocumentModel::Transition::External: |
400 | newTransition.type = StateTable::Transition::External; |
401 | break; |
402 | case DocumentModel::Transition::Internal: |
403 | newTransition.type = StateTable::Transition::Internal; |
404 | break; |
405 | case DocumentModel::Transition::Synthetic: |
406 | newTransition.type = StateTable::Transition::Synthetic; |
407 | break; |
408 | default: |
409 | Q_UNREACHABLE(); |
410 | } |
411 | |
412 | if (!transition->instructionsOnTransition.isEmpty()) { |
413 | m_currentTransition = transitionIndex; |
414 | newTransition.transitionInstructions = startNewSequence(); |
415 | visit(sequence: &transition->instructionsOnTransition); |
416 | endSequence(); |
417 | m_currentTransition = -1; |
418 | } |
419 | |
420 | newTransition.targets = addStates(states: transition->targetStates); |
421 | |
422 | QVector<int> eventIds; |
423 | for (const QString &event : qAsConst(t&: transition->events)) |
424 | eventIds.push_back(t: addString(str: event)); |
425 | |
426 | newTransition.events = addArray(array: eventIds); |
427 | |
428 | return false; |
429 | } |
430 | |
431 | bool visit(DocumentModel::HistoryState *historyState) override final |
432 | { |
433 | const int stateIndex = m_docStatesIndices.value(akey: historyState, adefaultValue: -1); |
434 | Q_ASSERT(stateIndex != -1); |
435 | StateTable::State &newState = m_allStates[stateIndex]; |
436 | newState.name = addString(str: historyState->id); |
437 | newState.parent = currentParent(); |
438 | |
439 | switch (historyState->type) { |
440 | case DocumentModel::HistoryState::Shallow: |
441 | newState.type = StateTable::State::ShallowHistory; |
442 | break; |
443 | case DocumentModel::HistoryState::Deep: |
444 | newState.type = StateTable::State::DeepHistory; |
445 | break; |
446 | default: |
447 | Q_UNREACHABLE(); |
448 | } |
449 | |
450 | m_parents.append(t: stateIndex); |
451 | visit(children: historyState->children); |
452 | m_parents.removeLast(); |
453 | newState.transitions = addArray(array: m_transitionsForState.at(i: stateIndex)); |
454 | return false; |
455 | } |
456 | |
457 | bool visit(DocumentModel::Send *node) override final |
458 | { |
459 | auto instr = m_instructions.add<Send>(extra: Send::calculateExtraSize(paramCount: node->params.size(), |
460 | nameCount: node->namelist.size())); |
461 | instr->instructionLocation = createContext(QStringLiteral("send" )); |
462 | instr->event = addString(str: node->event); |
463 | instr->eventexpr = createEvaluatorString(QStringLiteral("send" ), |
464 | QStringLiteral("eventexpr" ), |
465 | expr: node->eventexpr); |
466 | instr->type = addString(str: node->type); |
467 | instr->typeexpr = createEvaluatorString(QStringLiteral("send" ), |
468 | QStringLiteral("typeexpr" ), |
469 | expr: node->typeexpr); |
470 | instr->target = addString(str: node->target); |
471 | instr->targetexpr = createEvaluatorString(QStringLiteral("send" ), |
472 | QStringLiteral("targetexpr" ), |
473 | expr: node->targetexpr); |
474 | instr->id = addString(str: node->id); |
475 | instr->idLocation = addString(str: node->idLocation); |
476 | instr->delay = addString(str: node->delay); |
477 | instr->delayexpr = createEvaluatorString(QStringLiteral("send" ), |
478 | QStringLiteral("delayexpr" ), |
479 | expr: node->delayexpr); |
480 | instr->content = addString(str: node->content); |
481 | instr->contentexpr = createEvaluatorString(QStringLiteral("send" ), |
482 | QStringLiteral("contentexpr" ), |
483 | expr: node->contentexpr); |
484 | generate(out: &instr->namelist, in: node->namelist); |
485 | generate(out: instr->params(), in: node->params); |
486 | return false; |
487 | } |
488 | |
489 | void visit(DocumentModel::Raise *node) override final |
490 | { |
491 | auto instr = m_instructions.add<Raise>(); |
492 | instr->event = addString(str: node->event); |
493 | } |
494 | |
495 | void visit(DocumentModel::Log *node) override final |
496 | { |
497 | auto instr = m_instructions.add<Log>(); |
498 | instr->label = addString(str: node->label); |
499 | instr->expr = createEvaluatorString(QStringLiteral("log" ), |
500 | QStringLiteral("expr" ), |
501 | expr: node->expr); |
502 | } |
503 | |
504 | void visit(DocumentModel::Script *node) override final |
505 | { |
506 | auto instr = m_instructions.add<JavaScript>(); |
507 | instr->go = createEvaluatorVoid(QStringLiteral("script" ), |
508 | QStringLiteral("source" ), |
509 | stuff: node->content); |
510 | } |
511 | |
512 | void visit(DocumentModel::Assign *node) override final |
513 | { |
514 | auto instr = m_instructions.add<Assign>(); |
515 | auto ctxt = createContext(QStringLiteral("assign" ), QStringLiteral("expr" ), attrValue: node->expr); |
516 | instr->expression = addAssignment(dest: node->location, expr: node->expr, context: ctxt); |
517 | } |
518 | |
519 | bool visit(DocumentModel::If *node) override final |
520 | { |
521 | auto instr = m_instructions.add<If>(extra: node->conditions.size()); |
522 | instr->conditions.count = node->conditions.size(); |
523 | auto it = instr->conditions.data(); |
524 | QString tag = QStringLiteral("if" ); |
525 | for (int i = 0, ei = node->conditions.size(); i != ei; ++i) { |
526 | *it++ = createEvaluatorBool(instrName: tag, QStringLiteral("cond" ), cond: node->conditions.at(i)); |
527 | if (i == 0) { |
528 | tag = QStringLiteral("elif" ); |
529 | } |
530 | } |
531 | auto outSequences = m_instructions.add<InstructionSequences>(); |
532 | generate(outSequences, inSequences: node->blocks); |
533 | return false; |
534 | } |
535 | |
536 | bool visit(DocumentModel::Foreach *node) override final |
537 | { |
538 | auto instr = m_instructions.add<Foreach>(); |
539 | auto ctxt = createContextString(QStringLiteral("foreach" )); |
540 | instr->doIt = addForeach(array: node->array, item: node->item, index: node->index, context: ctxt); |
541 | startSequence(sequence: &instr->block); |
542 | visit(sequence: &node->block); |
543 | endSequence(); |
544 | return false; |
545 | } |
546 | |
547 | void visit(DocumentModel::Cancel *node) override final |
548 | { |
549 | auto instr = m_instructions.add<Cancel>(); |
550 | instr->sendid = addString(str: node->sendid); |
551 | instr->sendidexpr = createEvaluatorString(QStringLiteral("cancel" ), |
552 | QStringLiteral("sendidexpr" ), |
553 | expr: node->sendidexpr); |
554 | } |
555 | |
556 | protected: |
557 | static int paramSize() { return sizeof(ParameterInfo) / sizeof(qint32); } |
558 | |
559 | ContainerId generate(const DocumentModel::DoneData *node) |
560 | { |
561 | auto id = m_instructions.newContainerId(); |
562 | DoneData *doneData; |
563 | if (node) { |
564 | doneData = m_instructions.add<DoneData>(extra: node->params.size() * paramSize()); |
565 | doneData->contents = addString(str: node->contents); |
566 | doneData->expr = createEvaluatorString(QStringLiteral("donedata" ), |
567 | QStringLiteral("expr" ), |
568 | expr: node->expr); |
569 | generate(out: &doneData->params, in: node->params); |
570 | } else { |
571 | doneData = m_instructions.add<DoneData>(); |
572 | doneData->contents = NoString; |
573 | doneData->expr = NoEvaluator; |
574 | doneData->params.count = 0; |
575 | } |
576 | doneData->location = createContext(QStringLiteral("final" )); |
577 | return id; |
578 | } |
579 | |
580 | StringId createContext(const QString &instrName) |
581 | { |
582 | return addString(str: createContextString(instrName)); |
583 | } |
584 | |
585 | void generate(const QVector<DocumentModel::DataElement *> &dataElements) |
586 | { |
587 | for (DocumentModel::DataElement *el : dataElements) { |
588 | auto ctxt = createContext(QStringLiteral("data" ), QStringLiteral("expr" ), attrValue: el->expr); |
589 | auto evaluator = addDataElement(id: el->id, expr: el->expr, context: ctxt); |
590 | if (evaluator != NoEvaluator) { |
591 | auto instr = m_instructions.add<QScxmlExecutableContent::Initialize>(); |
592 | instr->expression = evaluator; |
593 | } |
594 | } |
595 | } |
596 | |
597 | ContainerId generate(const DocumentModel::InstructionSequences &inSequences) |
598 | { |
599 | if (inSequences.isEmpty()) |
600 | return NoContainer; |
601 | |
602 | auto id = m_instructions.newContainerId(); |
603 | auto outSequences = m_instructions.add<InstructionSequences>(); |
604 | generate(outSequences, inSequences); |
605 | return id; |
606 | } |
607 | |
608 | void generate(Array<ParameterInfo> *out, const QVector<DocumentModel::Param *> &in) |
609 | { |
610 | out->count = in.size(); |
611 | ParameterInfo *it = out->data(); |
612 | for (DocumentModel::Param *f : in) { |
613 | it->name = addString(str: f->name); |
614 | it->expr = createEvaluatorVariant(QStringLiteral("param" ), QStringLiteral("expr" ), |
615 | expr: f->expr); |
616 | it->location = addString(str: f->location); |
617 | ++it; |
618 | } |
619 | } |
620 | |
621 | void generate(InstructionSequences *outSequences, |
622 | const DocumentModel::InstructionSequences &inSequences) |
623 | { |
624 | int sequencesOffset = m_instructions.offset(instr: outSequences); |
625 | int sequenceCount = 0; |
626 | int entryCount = 0; |
627 | for (DocumentModel::InstructionSequence *sequence : inSequences) { |
628 | ++sequenceCount; |
629 | startNewSequence(); |
630 | visit(sequence); |
631 | entryCount += endSequence()->size(); |
632 | } |
633 | outSequences = m_instructions.at<InstructionSequences>(offset: sequencesOffset); |
634 | outSequences->sequenceCount = sequenceCount; |
635 | outSequences->entryCount = entryCount; |
636 | } |
637 | |
638 | void generate(Array<StringId> *out, const QStringList &in) |
639 | { |
640 | out->count = in.size(); |
641 | StringId *it = out->data(); |
642 | for (const QString &str : in) { |
643 | *it++ = addString(str); |
644 | } |
645 | } |
646 | |
647 | ContainerId startNewSequence() |
648 | { |
649 | auto id = m_instructions.newContainerId(); |
650 | auto sequence = m_instructions.add<InstructionSequence>(); |
651 | startSequence(sequence); |
652 | return id; |
653 | } |
654 | |
655 | void startSequence(InstructionSequence *sequence) |
656 | { |
657 | SequenceInfo info; |
658 | info.location = m_instructions.offset(instr: sequence); |
659 | info.entryCount = 0; |
660 | m_activeSequences.push_back(t: info); |
661 | m_instructions.setSequenceInfo(&m_activeSequences.last()); |
662 | sequence->instructionType = Instruction::Sequence; |
663 | sequence->entryCount = -1; // checked in endSequence |
664 | } |
665 | |
666 | InstructionSequence *endSequence() |
667 | { |
668 | SequenceInfo info = m_activeSequences.back(); |
669 | m_activeSequences.pop_back(); |
670 | m_instructions.setSequenceInfo(m_activeSequences.isEmpty() ? nullptr : |
671 | &m_activeSequences.last()); |
672 | |
673 | auto sequence = m_instructions.at<InstructionSequence>(offset: info.location); |
674 | Q_ASSERT(sequence->entryCount == -1); // set in startSequence |
675 | sequence->entryCount = info.entryCount; |
676 | if (!m_activeSequences.isEmpty()) |
677 | m_activeSequences.last().entryCount += info.entryCount; |
678 | return sequence; |
679 | } |
680 | |
681 | EvaluatorId createEvaluatorString(const QString &instrName, const QString &attrName, |
682 | const QString &expr) |
683 | { |
684 | if (!expr.isEmpty()) { |
685 | if (isCppDataModel()) { |
686 | auto id = m_evaluators.add(s: EvaluatorInfo(), uniqueOnly: false); |
687 | m_dataModelInfo.stringEvaluators.insert(akey: id, avalue: expr); |
688 | return id; |
689 | } else { |
690 | return addEvaluator(expr, context: createContext(instrName, attrName, attrValue: expr)); |
691 | } |
692 | } |
693 | |
694 | return NoEvaluator; |
695 | } |
696 | |
697 | EvaluatorId createEvaluatorBool(const QString &instrName, const QString &attrName, |
698 | const QString &cond) |
699 | { |
700 | if (!cond.isEmpty()) { |
701 | if (isCppDataModel()) { |
702 | auto id = m_evaluators.add(s: EvaluatorInfo(), uniqueOnly: false); |
703 | m_dataModelInfo.boolEvaluators.insert(akey: id, avalue: cond); |
704 | return id; |
705 | } else { |
706 | return addEvaluator(expr: cond, context: createContext(instrName, attrName, attrValue: cond)); |
707 | } |
708 | } |
709 | |
710 | return NoEvaluator; |
711 | } |
712 | |
713 | EvaluatorId createEvaluatorVariant(const QString &instrName, const QString &attrName, |
714 | const QString &expr) |
715 | { |
716 | if (!expr.isEmpty()) { |
717 | if (isCppDataModel()) { |
718 | auto id = m_evaluators.add(s: EvaluatorInfo(), uniqueOnly: false); |
719 | m_dataModelInfo.variantEvaluators.insert(akey: id, avalue: expr); |
720 | return id; |
721 | } else { |
722 | return addEvaluator(expr, context: createContext(instrName, attrName, attrValue: expr)); |
723 | } |
724 | } |
725 | |
726 | return NoEvaluator; |
727 | } |
728 | |
729 | EvaluatorId createEvaluatorVoid(const QString &instrName, const QString &attrName, |
730 | const QString &stuff) |
731 | { |
732 | if (!stuff.isEmpty()) { |
733 | if (isCppDataModel()) { |
734 | auto id = m_evaluators.add(s: EvaluatorInfo(), uniqueOnly: false); |
735 | m_dataModelInfo.voidEvaluators.insert(akey: id, avalue: stuff); |
736 | return id; |
737 | } else { |
738 | return addEvaluator(expr: stuff, context: createContext(instrName, attrName, attrValue: stuff)); |
739 | } |
740 | } |
741 | |
742 | return NoEvaluator; |
743 | } |
744 | |
745 | GeneratedTableData *tableData(const QVector<int> &stateMachineTable); |
746 | |
747 | StringId addString(const QString &str) |
748 | { return str.isEmpty() ? NoString : m_stringTable.add(s: str); } |
749 | |
750 | void setInitialSetup(ContainerId id) |
751 | { m_tableData.theInitialSetup = id; } |
752 | |
753 | void setName(const QString &name) |
754 | { m_tableData.theName = addString(str: name); } |
755 | |
756 | bool isCppDataModel() const |
757 | { return m_isCppDataModel; } |
758 | |
759 | int addStates(const QVector<DocumentModel::AbstractState *> &states) |
760 | { |
761 | QVector<int> array; |
762 | for (auto *s : states) { |
763 | int si = m_docStatesIndices.value(akey: s, adefaultValue: -1); |
764 | Q_ASSERT(si != -1); |
765 | array.push_back(t: si); |
766 | } |
767 | |
768 | return addArray(array); |
769 | } |
770 | |
771 | int addArray(const QVector<int> &array) |
772 | { |
773 | if (array.isEmpty()) |
774 | return -1; |
775 | |
776 | const int res = m_arrays.size(); |
777 | m_arrays.push_back(t: array.size()); |
778 | m_arrays.append(l: array); |
779 | return res; |
780 | } |
781 | |
782 | int currentParent() const |
783 | { |
784 | return m_parents.last(); |
785 | } |
786 | |
787 | QString createContextString(const QString &instrName) const |
788 | { |
789 | if (m_currentTransition != -1) { |
790 | QString state; |
791 | int parent = m_allTransitions.at(i: m_currentTransition).source; |
792 | if (parent != -1) { |
793 | QString parentName = QStringLiteral("(none)" ); |
794 | int name = m_allStates.at(i: parent).name; |
795 | if (name != -1) { |
796 | parentName = m_stringTable.item(pos: name); |
797 | } |
798 | state = QStringLiteral(" of state '%1'" ).arg(a: parentName); |
799 | } |
800 | return QStringLiteral("%1 instruction in transition %3" ).arg(args: instrName, args&: state); |
801 | } else { |
802 | QString parentName = QStringLiteral("(none)" ); |
803 | const int parent = currentParent(); |
804 | if (parent != -1) { |
805 | const int name = m_allStates.at(i: parent).name; |
806 | if (name != -1) { |
807 | parentName = m_stringTable.item(pos: name); |
808 | } |
809 | } |
810 | return QStringLiteral("%1 instruction in state %2" ).arg(args: instrName, args&: parentName); |
811 | } |
812 | } |
813 | |
814 | QString createContext(const QString &instrName, const QString &attrName, |
815 | const QString &attrValue) const |
816 | { |
817 | const QString location = createContextString(instrName); |
818 | return QStringLiteral("%1 with %2=\"%3\"" ).arg(a1: location, a2: attrName, a3: attrValue); |
819 | } |
820 | |
821 | EvaluatorId addEvaluator(const QString &expr, const QString &context) |
822 | { |
823 | EvaluatorInfo ei; |
824 | ei.expr = addString(str: expr); |
825 | ei.context = addString(str: context); |
826 | return m_evaluators.add(s: ei); |
827 | } |
828 | |
829 | EvaluatorId addAssignment(const QString &dest, const QString &expr, const QString &context) |
830 | { |
831 | AssignmentInfo ai; |
832 | ai.dest = addString(str: dest); |
833 | ai.expr = addString(str: expr); |
834 | ai.context = addString(str: context); |
835 | return m_assignments.add(s: ai); |
836 | } |
837 | |
838 | EvaluatorId addForeach(const QString &array, const QString &item, const QString &index, |
839 | const QString &context) |
840 | { |
841 | ForeachInfo fi; |
842 | fi.array = addString(str: array); |
843 | fi.item = addString(str: item); |
844 | fi.index = addString(str: index); |
845 | fi.context = addString(str: context); |
846 | return m_foreaches.add(s: fi); |
847 | } |
848 | |
849 | EvaluatorId addDataElement(const QString &id, const QString &expr, const QString &context) |
850 | { |
851 | auto str = addString(str: id); |
852 | if (!m_dataIds.contains(t: str)) |
853 | m_dataIds.append(t: str); |
854 | if (expr.isEmpty()) |
855 | return NoEvaluator; |
856 | |
857 | return addAssignment(dest: id, expr, context); |
858 | } |
859 | |
860 | private: |
861 | template <class Container, typename T, typename U> |
862 | class Table { |
863 | Container &elements; |
864 | QMap<T, int> indexForElement; |
865 | |
866 | public: |
867 | Table(Container &storage) |
868 | : elements(storage) |
869 | {} |
870 | |
871 | U add(const T &s, bool uniqueOnly = true) { |
872 | int pos = uniqueOnly ? indexForElement.value(s, -1) : -1; |
873 | if (pos == -1) { |
874 | pos = elements.size(); |
875 | elements.append(s); |
876 | indexForElement.insert(s, pos); |
877 | } |
878 | return pos; |
879 | } |
880 | |
881 | Container data() { |
882 | return elements; |
883 | } |
884 | |
885 | const T &item(U pos) const { |
886 | return elements.at(pos); |
887 | } |
888 | }; |
889 | |
890 | struct SequenceInfo { |
891 | int location; |
892 | qint32 entryCount; // the amount of qint32's that the instructions take up |
893 | }; |
894 | |
895 | class InstructionStorage { |
896 | public: |
897 | InstructionStorage(QVector<qint32> &storage) |
898 | : m_instr(storage) |
899 | , m_info(nullptr) |
900 | {} |
901 | |
902 | ContainerId newContainerId() const { return m_instr.size(); } |
903 | |
904 | template <typename T> |
905 | T *add(int = 0) |
906 | { |
907 | const int pos = m_instr.size(); |
908 | const int size = sizeof(T) / sizeof(qint32) + extra; |
909 | if (m_info) |
910 | m_info->entryCount += size; |
911 | m_instr.resize(asize: pos + size); |
912 | T *instr = at<T>(pos); |
913 | Q_ASSERT(instr->instructionType == 0); |
914 | instr->instructionType = T::kind(); |
915 | return instr; |
916 | } |
917 | |
918 | int offset(Instruction *instr) const |
919 | { |
920 | return reinterpret_cast<qint32 *>(instr) - m_instr.data(); |
921 | } |
922 | |
923 | template <typename T> |
924 | T *at(int offset) |
925 | { |
926 | return reinterpret_cast<T *>(&m_instr[offset]); |
927 | } |
928 | |
929 | void setSequenceInfo(SequenceInfo *info) |
930 | { |
931 | m_info = info; |
932 | } |
933 | |
934 | private: |
935 | QVector<qint32> &m_instr; |
936 | SequenceInfo *m_info; |
937 | }; |
938 | |
939 | QVector<SequenceInfo> m_activeSequences; |
940 | |
941 | GeneratedTableData::CreateFactoryId createFactoryId; |
942 | GeneratedTableData &m_tableData; |
943 | GeneratedTableData::DataModelInfo &m_dataModelInfo; |
944 | Table<QStringList, QString, StringId> m_stringTable; |
945 | InstructionStorage m_instructions; |
946 | Table<QVector<EvaluatorInfo>, EvaluatorInfo, EvaluatorId> m_evaluators; |
947 | Table<QVector<AssignmentInfo>, AssignmentInfo, EvaluatorId> m_assignments; |
948 | Table<QVector<ForeachInfo>, ForeachInfo, EvaluatorId> m_foreaches; |
949 | QVector<StringId> &m_dataIds; |
950 | bool m_isCppDataModel = false; |
951 | |
952 | StateTable m_stateTable; |
953 | QVector<int> m_parents; |
954 | QVector<qint32> m_arrays; |
955 | |
956 | QVector<StateTable::Transition> m_allTransitions; |
957 | QHash<DocumentModel::Transition *, int> m_docTransitionIndices; |
958 | QVector<StateTable::State> m_allStates; |
959 | QHash<DocumentModel::AbstractState *, int> m_docStatesIndices; |
960 | QVector<QVector<int>> m_transitionsForState; |
961 | |
962 | int m_currentTransition = StateTable::InvalidIndex; |
963 | bool m_bindLate = false; |
964 | QVector<DocumentModel::DataElement *> m_dataElements; |
965 | Table<QStringList, QString, int> m_stateNames; |
966 | }; |
967 | |
968 | } // anonymous namespace |
969 | |
970 | /*! |
971 | \fn QScxmlTableData::~QScxmlTableData() |
972 | Destroys the SXCML table data. |
973 | */ |
974 | QScxmlTableData::~QScxmlTableData() |
975 | {} |
976 | |
977 | void GeneratedTableData::build(DocumentModel::ScxmlDocument *doc, |
978 | GeneratedTableData *table, |
979 | MetaDataInfo *metaDataInfo, |
980 | DataModelInfo *dataModelInfo, |
981 | GeneratedTableData::CreateFactoryId func) |
982 | { |
983 | TableDataBuilder builder(*table, *metaDataInfo, *dataModelInfo, func); |
984 | builder.buildTableData(doc); |
985 | } |
986 | |
987 | QString GeneratedTableData::toString(const int *stateMachineTable) |
988 | { |
989 | QString result; |
990 | QTextStream out(&result); |
991 | |
992 | const StateTable *st = reinterpret_cast<const StateTable *>(stateMachineTable); |
993 | |
994 | out << "{" << Qt::endl |
995 | << "\t0x" << Qt::hex << st->version << Qt::dec << ", // version" << Qt::endl |
996 | << "\t" << st->name << ", // name" << Qt::endl |
997 | << "\t" << st->dataModel << ", // data-model" << Qt::endl |
998 | << "\t" << st->childStates << ", // child states array offset" << Qt::endl |
999 | << "\t" << st->initialTransition << ", // transition to initial states" << Qt::endl |
1000 | << "\t" << st->initialSetup << ", // initial setup" << Qt::endl |
1001 | << "\t" << st->binding << ", // binding" << Qt::endl |
1002 | << "\t" << st->maxServiceId << ", // maxServiceId" << Qt::endl |
1003 | << "\t" << st->stateOffset << ", " << st->stateCount |
1004 | << ", // state offset and count" << Qt::endl |
1005 | << "\t" << st->transitionOffset << ", " << st->transitionCount |
1006 | << ", // transition offset and count" << Qt::endl |
1007 | << "\t" << st->arrayOffset << ", " << st->arraySize << ", // array offset and size" << Qt::endl |
1008 | << Qt::endl; |
1009 | |
1010 | out << "\t// States:" << Qt::endl; |
1011 | for (int i = 0; i < st->stateCount; ++i) { |
1012 | const StateTable::State &s = st->state(idx: i); |
1013 | out << "\t" |
1014 | << s.name << ", " |
1015 | << s.parent << ", " |
1016 | << s.type << ", " |
1017 | << s.initialTransition << ", " |
1018 | << s.initInstructions << ", " |
1019 | << s.entryInstructions << ", " |
1020 | << s.exitInstructions << ", " |
1021 | << s.doneData << ", " |
1022 | << s.childStates << ", " |
1023 | << s.transitions << ", " |
1024 | << s.serviceFactoryIds << "," |
1025 | << Qt::endl; |
1026 | } |
1027 | |
1028 | out << Qt::endl |
1029 | << "\t// Transitions:" << Qt::endl; |
1030 | for (int i = 0; i < st->transitionCount; ++i) { |
1031 | auto t = st->transition(idx: i); |
1032 | out << "\t" |
1033 | << t.events << ", " |
1034 | << t.condition << ", " |
1035 | << t.type << ", " |
1036 | << t.source << ", " |
1037 | << t.targets << ", " |
1038 | << t.transitionInstructions << ", " |
1039 | << Qt::endl ; |
1040 | } |
1041 | |
1042 | out << Qt::endl |
1043 | << "\t// Arrays:" << Qt::endl; |
1044 | int nextStart = 0; |
1045 | while (nextStart < st->arraySize) { |
1046 | const StateTable::Array a = st->array(idx: nextStart); |
1047 | out << "\t" << a.size() << ", " ; |
1048 | for (int j = 0; j < a.size(); ++j) { |
1049 | out << a[j] << ", " ; |
1050 | } |
1051 | out << Qt::endl; |
1052 | nextStart += a.size() + 1; |
1053 | } |
1054 | |
1055 | out << Qt::hex; |
1056 | out << Qt::endl |
1057 | << "\t0x" << StateTable::terminator << " // terminator" << Qt::endl |
1058 | << "}" ; |
1059 | |
1060 | return result; |
1061 | } |
1062 | |
1063 | QString GeneratedTableData::string(StringId id) const |
1064 | { |
1065 | return id == NoString ? QString() : theStrings.at(i: id); |
1066 | } |
1067 | |
1068 | InstructionId *GeneratedTableData::instructions() const |
1069 | { |
1070 | return const_cast<InstructionId *>(theInstructions.data()); |
1071 | } |
1072 | |
1073 | EvaluatorInfo GeneratedTableData::evaluatorInfo(EvaluatorId evaluatorId) const |
1074 | { |
1075 | return theEvaluators[evaluatorId]; |
1076 | } |
1077 | |
1078 | AssignmentInfo GeneratedTableData::assignmentInfo(EvaluatorId assignmentId) const |
1079 | { |
1080 | return theAssignments[assignmentId]; |
1081 | } |
1082 | |
1083 | ForeachInfo GeneratedTableData::foreachInfo(EvaluatorId foreachId) const |
1084 | { |
1085 | return theForeaches[foreachId]; |
1086 | } |
1087 | |
1088 | StringId *GeneratedTableData::dataNames(int *count) const |
1089 | { |
1090 | Q_ASSERT(count); |
1091 | *count = theDataNameIds.size(); |
1092 | return const_cast<StringId *>(theDataNameIds.data()); |
1093 | } |
1094 | |
1095 | ContainerId GeneratedTableData::initialSetup() const |
1096 | { |
1097 | return theInitialSetup; |
1098 | } |
1099 | |
1100 | QString GeneratedTableData::name() const |
1101 | { |
1102 | return string(id: theName); |
1103 | } |
1104 | |
1105 | const qint32 *GeneratedTableData::stateMachineTable() const |
1106 | { |
1107 | return theStateMachineTable.constData(); |
1108 | } |
1109 | |
1110 | QScxmlInvokableServiceFactory *GeneratedTableData::serviceFactory(int id) const |
1111 | { |
1112 | Q_UNUSED(id); |
1113 | return nullptr; |
1114 | } |
1115 | |